Fixed image clipping / resizing logic

This commit is contained in:
Nick Lockwood
2015-08-12 13:07:24 -01:00
parent 672b77f355
commit 7232b1bbc7
7 changed files with 107 additions and 78 deletions

View File

@@ -15,9 +15,6 @@
#import "RCTNetworking.h"
#import "RCTUtils.h"
CGSize RCTTargetSizeForClipRect(CGRect);
CGRect RCTClipRect(CGSize, CGFloat, CGSize, CGFloat, UIViewContentMode);
@implementation RCTImageDownloader
{
NSURLCache *_cache;
@@ -131,14 +128,14 @@ RCT_EXPORT_MODULE()
UIImage *image = [UIImage imageWithData:data scale:scale];
if (image && !CGSizeEqualToSize(size, CGSizeZero)) {
// Get scale and size
CGRect imageRect = RCTClipRect(image.size, scale, size, scale, resizeMode);
CGSize destSize = RCTTargetSizeForClipRect(imageRect);
// Get destination size
CGSize targetSize = RCTTargetSize(image.size, image.scale,
size, scale, resizeMode, NO);
// Decompress image at required size
BOOL opaque = !RCTImageHasAlpha(image.CGImage);
UIGraphicsBeginImageContextWithOptions(destSize, opaque, scale);
[image drawInRect:imageRect];
UIGraphicsBeginImageContextWithOptions(targetSize, opaque, scale);
[image drawInRect:(CGRect){CGPointZero, targetSize}];
image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
}

View File

@@ -85,8 +85,8 @@ static UIImage *RCTScaledImageForAsset(ALAssetRepresentation *representation,
}
CGSize sourceSize = representation.dimensions;
CGRect targetRect = RCTClipRect(sourceSize, representation.scale, size, scale, resizeMode);
CGSize targetSize = targetRect.size;
CGSize targetSize = RCTTargetSize(sourceSize, representation.scale,
size, scale, resizeMode, NO);
NSDictionary *options = @{
(id)kCGImageSourceShouldAllowFloat: @YES,

View File

@@ -13,20 +13,24 @@
#import "RCTDefines.h"
/**
* Returns the optimal context size for an image drawn using the clip rect
* returned by RCTClipRect.
* This function takes an input content size (typically from an image), a target
* size and scale that it will be drawn at (typically in a CGContext) and then
* calculates the rectangle to draw the image into so that it will be sized and
* positioned correctly if drawn using the specified content mode.
*/
RCT_EXTERN CGSize RCTTargetSizeForClipRect(CGRect clipRect);
RCT_EXTERN CGRect RCTTargetRect(CGSize sourceSize, CGSize destSize,
CGFloat destScale, UIViewContentMode resizeMode);
/**
* This function takes an input content size & scale (typically from an image),
* a target size & scale that it will be drawn into (typically a CGContext) and
* then calculates the optimal rectangle to draw the image into so that it will
* be sized and positioned correctly if drawn using the specified content mode.
* a target size & scale at which it will be displayed (typically in a
* UIImageView) and then calculates the optimal size at which to redraw the
* image so that it will be displayed correctly with the specified content mode.
*/
RCT_EXTERN CGRect RCTClipRect(CGSize sourceSize, CGFloat sourceScale,
CGSize destSize, CGFloat destScale,
UIViewContentMode resizeMode);
RCT_EXTERN CGSize RCTTargetSize(CGSize sourceSize, CGFloat sourceScale,
CGSize destSize, CGFloat destScale,
UIViewContentMode resizeMode,
BOOL allowUpscaling);
/**
* This function takes an input content size & scale (typically from an image),

View File

@@ -29,28 +29,14 @@ static CGSize RCTCeilSize(CGSize size, CGFloat scale)
};
}
CGSize RCTTargetSizeForClipRect(CGRect clipRect)
{
return (CGSize){
clipRect.size.width + clipRect.origin.x * 2,
clipRect.size.height + clipRect.origin.y * 2
};
}
CGRect RCTClipRect(CGSize sourceSize, CGFloat sourceScale,
CGSize destSize, CGFloat destScale,
UIViewContentMode resizeMode)
CGRect RCTTargetRect(CGSize sourceSize, CGSize destSize,
CGFloat destScale, UIViewContentMode resizeMode)
{
if (CGSizeEqualToSize(destSize, CGSizeZero)) {
// Assume we require the largest size available
return (CGRect){CGPointZero, sourceSize};
}
// Precompensate for scale
CGFloat scale = sourceScale / destScale;
sourceSize.width *= scale;
sourceSize.height *= scale;
CGFloat aspect = sourceSize.width / sourceSize.height;
// If only one dimension in destSize is non-zero (for example, an Image
// with `flex: 1` whose height is indeterminate), calculate the unknown
@@ -61,7 +47,7 @@ CGRect RCTClipRect(CGSize sourceSize, CGFloat sourceScale,
if (destSize.height == 0) {
destSize.height = destSize.width / aspect;
}
// Calculate target aspect ratio if needed (don't bother if resizeMode == stretch)
CGFloat targetAspect = 0.0;
if (resizeMode != UIViewContentModeScaleToFill) {
@@ -74,20 +60,18 @@ CGRect RCTClipRect(CGSize sourceSize, CGFloat sourceScale,
switch (resizeMode) {
case UIViewContentModeScaleToFill: // stretch
sourceSize.width = MIN(destSize.width, sourceSize.width);
sourceSize.height = MIN(destSize.height, sourceSize.height);
return (CGRect){CGPointZero, RCTCeilSize(sourceSize, destScale)};
return (CGRect){CGPointZero, RCTCeilSize(destSize, destScale)};
case UIViewContentModeScaleAspectFit: // contain
if (targetAspect <= aspect) { // target is taller than content
sourceSize.width = destSize.width = MIN(sourceSize.width, destSize.width);
sourceSize.width = destSize.width = destSize.width;
sourceSize.height = sourceSize.width / aspect;
} else { // target is wider than content
sourceSize.height = destSize.height = MIN(sourceSize.height, destSize.height);
sourceSize.height = destSize.height = destSize.height;
sourceSize.width = sourceSize.height * aspect;
}
return (CGRect){CGPointZero, RCTCeilSize(sourceSize, destScale)};
@@ -96,7 +80,7 @@ CGRect RCTClipRect(CGSize sourceSize, CGFloat sourceScale,
if (targetAspect <= aspect) { // target is taller than content
sourceSize.height = destSize.height = MIN(sourceSize.height, destSize.height);
sourceSize.height = destSize.height = destSize.height;
sourceSize.width = sourceSize.height * aspect;
destSize.width = destSize.height * targetAspect;
return (CGRect){
@@ -106,7 +90,7 @@ CGRect RCTClipRect(CGSize sourceSize, CGFloat sourceScale,
} else { // target is wider than content
sourceSize.width = destSize.width = MIN(sourceSize.width, destSize.width);
sourceSize.width = destSize.width = destSize.width;
sourceSize.height = sourceSize.width / aspect;
destSize.height = destSize.width / targetAspect;
return (CGRect){
@@ -122,9 +106,39 @@ CGRect RCTClipRect(CGSize sourceSize, CGFloat sourceScale,
}
}
RCT_EXTERN BOOL RCTUpscalingRequired(CGSize sourceSize, CGFloat sourceScale,
CGSize destSize, CGFloat destScale,
UIViewContentMode resizeMode)
CGSize RCTTargetSize(CGSize sourceSize, CGFloat sourceScale,
CGSize destSize, CGFloat destScale,
UIViewContentMode resizeMode,
BOOL allowUpscaling)
{
switch (resizeMode) {
case UIViewContentModeScaleToFill: // stretch
if (!allowUpscaling) {
CGFloat scale = sourceScale / destScale;
destSize.width = MIN(sourceSize.width * scale, destSize.width);
destSize.height = MIN(sourceSize.height * scale, destSize.height);
}
return RCTCeilSize(destSize, destScale);
default: {
// Get target size
CGSize size = RCTTargetRect(sourceSize, destSize, destScale, resizeMode).size;
if (!allowUpscaling) {
// return sourceSize if target size is larger
if (sourceSize.width * sourceScale < size.width * destScale) {
return sourceSize;
}
}
return size;
}
}
}
BOOL RCTUpscalingRequired(CGSize sourceSize, CGFloat sourceScale,
CGSize destSize, CGFloat destScale,
UIViewContentMode resizeMode)
{
if (CGSizeEqualToSize(destSize, CGSizeZero)) {
// Assume we require the largest size available

View File

@@ -14,6 +14,7 @@
#import "RCTEventDispatcher.h"
#import "RCTGIFImage.h"
#import "RCTImageLoader.h"
#import "RCTImageUtils.h"
#import "RCTUtils.h"
#import "UIView+React.h"
@@ -43,7 +44,7 @@
RCT_NOT_IMPLEMENTED(-init)
- (void)_updateImage
- (void)updateImage
{
UIImage *image = self.image;
if (!image) {
@@ -72,7 +73,7 @@ RCT_NOT_IMPLEMENTED(-init)
image = image ?: _defaultImage;
if (image != super.image) {
super.image = image;
[self _updateImage];
[self updateImage];
}
}
@@ -80,7 +81,7 @@ RCT_NOT_IMPLEMENTED(-init)
{
if (!UIEdgeInsetsEqualToEdgeInsets(_capInsets, capInsets)) {
_capInsets = capInsets;
[self _updateImage];
[self updateImage];
}
}
@@ -88,7 +89,7 @@ RCT_NOT_IMPLEMENTED(-init)
{
if (_renderingMode != renderingMode) {
_renderingMode = renderingMode;
[self _updateImage];
[self updateImage];
}
}
@@ -100,6 +101,16 @@ RCT_NOT_IMPLEMENTED(-init)
}
}
- (void)setContentMode:(UIViewContentMode)contentMode
{
if (self.contentMode != contentMode) {
super.contentMode = contentMode;
if ([RCTImageLoader isAssetLibraryImage:_src] || [RCTImageLoader isRemoteImage:_src]) {
[self reloadImage];
}
}
}
- (void)reloadImage
{
if (_src && !CGSizeEqualToSize(self.frame.size, CGSizeZero)) {
@@ -165,12 +176,15 @@ RCT_NOT_IMPLEMENTED(-init)
if (self.image == nil) {
[self reloadImage];
} else if ([RCTImageLoader isAssetLibraryImage:_src] || [RCTImageLoader isRemoteImage:_src]) {
CGSize imageSize = {
self.image.size.width / RCTScreenScale(),
self.image.size.height / RCTScreenScale()
};
CGFloat widthChangeFraction = imageSize.width ? ABS(imageSize.width - frame.size.width) / imageSize.width : 1;
CGFloat heightChangeFraction = imageSize.height ? ABS(imageSize.height - frame.size.height) / imageSize.height : 1;
// Get optimal image size
CGSize currentSize = self.image.size;
CGSize idealSize = RCTTargetSize(self.image.size, self.image.scale, frame.size,
RCTScreenScale(), self.contentMode, YES);
CGFloat widthChangeFraction = ABS(currentSize.width - idealSize.width) / currentSize.width;
CGFloat heightChangeFraction = ABS(currentSize.height - idealSize.height) / currentSize.height;
// If the combined change is more than 20%, reload the asset in case there is a better size.
if (widthChangeFraction + heightChangeFraction > 0.2) {
[self reloadImage];