react-native/Libraries/Image/RCTImageUtils.m
Yusef Napora 2fe1ac2a83 [Image] Fix invalid CGContext when one dimension of destination Image size == 0
Summary:
Addresses #1534

When an image has a known width, but a height of 0 (which can happen if `flex: 1` is set on the `Image` element), `RCTDownloadManager` attempts to scale it to an invalid size, which results in a `NULL` `CGContextRef` and some scary warnings from UIKit:

```
 <Error>: CGContextTranslateCTM: invalid context 0x0. This is a serious error. This application, or a library it uses, is using an invalid context  and is thereby contributing to an overall degradation of system stability and reliability. This notice is a courtesy: please fix this problem. It will become a fatal error in an upcoming update.
```

This adds a check for zero width or height to the `RCTClipRect` function.  If either dimension is zero, it is calculated based on the aspect ratio of the source image.  This ensures that we don't try to create an invalid `CGContextRef`, and that images with an unknown dimension are still scaled, blended, etc.
Closes https://github.com/facebook/react-native/pull/2278
Github Author: Yusef Napora <yusef@napora.org>
2015-08-11 06:30:34 -08:00

182 lines
5.5 KiB
Objective-C

/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "RCTImageUtils.h"
#import "RCTLog.h"
static CGFloat RCTCeilValue(CGFloat value, CGFloat scale)
{
return ceil(value * scale) / scale;
}
static CGFloat RCTFloorValue(CGFloat value, CGFloat scale)
{
return floor(value * scale) / scale;
}
static CGSize RCTCeilSize(CGSize size, CGFloat scale)
{
return (CGSize){
RCTCeilValue(size.width, scale),
RCTCeilValue(size.height, 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)
{
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
// dimension based on the aspect ratio of sourceSize
if (destSize.width == 0) {
destSize.width = destSize.height * aspect;
}
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) {
targetAspect = destSize.width / destSize.height;
if (aspect == targetAspect) {
resizeMode = UIViewContentModeScaleToFill;
}
}
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)};
case UIViewContentModeScaleAspectFit: // contain
if (targetAspect <= aspect) { // target is taller than content
sourceSize.width = destSize.width = MIN(sourceSize.width, destSize.width);
sourceSize.height = sourceSize.width / aspect;
} else { // target is wider than content
sourceSize.height = destSize.height = MIN(sourceSize.height, destSize.height);
sourceSize.width = sourceSize.height * aspect;
}
return (CGRect){CGPointZero, RCTCeilSize(sourceSize, destScale)};
case UIViewContentModeScaleAspectFill: // cover
if (targetAspect <= aspect) { // target is taller than content
sourceSize.height = destSize.height = MIN(sourceSize.height, destSize.height);
sourceSize.width = sourceSize.height * aspect;
destSize.width = destSize.height * targetAspect;
return (CGRect){
{RCTFloorValue((destSize.width - sourceSize.width) / 2, destScale), 0},
RCTCeilSize(sourceSize, destScale)
};
} else { // target is wider than content
sourceSize.width = destSize.width = MIN(sourceSize.width, destSize.width);
sourceSize.height = sourceSize.width / aspect;
destSize.height = destSize.width / targetAspect;
return (CGRect){
{0, RCTFloorValue((destSize.height - sourceSize.height) / 2, destScale)},
RCTCeilSize(sourceSize, destScale)
};
}
default:
RCTLogError(@"A resizeMode value of %zd is not supported", resizeMode);
return (CGRect){CGPointZero, RCTCeilSize(destSize, destScale)};
}
}
RCT_EXTERN BOOL RCTUpscalingRequired(CGSize sourceSize, CGFloat sourceScale,
CGSize destSize, CGFloat destScale,
UIViewContentMode resizeMode)
{
if (CGSizeEqualToSize(destSize, CGSizeZero)) {
// Assume we require the largest size available
return YES;
}
// Precompensate for scale
CGFloat scale = sourceScale / destScale;
sourceSize.width *= scale;
sourceSize.height *= scale;
// Calculate aspect ratios if needed (don't bother if resizeMode == stretch)
CGFloat aspect = 0.0, targetAspect = 0.0;
if (resizeMode != UIViewContentModeScaleToFill) {
aspect = sourceSize.width / sourceSize.height;
targetAspect = destSize.width / destSize.height;
if (aspect == targetAspect) {
resizeMode = UIViewContentModeScaleToFill;
}
}
switch (resizeMode) {
case UIViewContentModeScaleToFill: // stretch
return destSize.width > sourceSize.width || destSize.height > sourceSize.height;
case UIViewContentModeScaleAspectFit: // contain
if (targetAspect <= aspect) { // target is taller than content
return destSize.width > sourceSize.width;
} else { // target is wider than content
return destSize.height > sourceSize.height;
}
case UIViewContentModeScaleAspectFill: // cover
if (targetAspect <= aspect) { // target is taller than content
return destSize.height > sourceSize.height;
} else { // target is wider than content
return destSize.width > sourceSize.width;
}
default:
RCTLogError(@"A resizeMode value of %zd is not supported", resizeMode);
return NO;
}
}