2015-07-14 11:06:17 +00:00
|
|
|
/**
|
|
|
|
* 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"
|
|
|
|
|
2015-07-21 12:40:06 +00:00
|
|
|
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)
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2015-08-12 14:07:24 +00:00
|
|
|
CGRect RCTTargetRect(CGSize sourceSize, CGSize destSize,
|
|
|
|
CGFloat destScale, UIViewContentMode resizeMode)
|
2015-07-14 11:06:17 +00:00
|
|
|
{
|
|
|
|
if (CGSizeEqualToSize(destSize, CGSizeZero)) {
|
|
|
|
// Assume we require the largest size available
|
|
|
|
return (CGRect){CGPointZero, sourceSize};
|
|
|
|
}
|
|
|
|
|
2015-08-11 13:21:12 +00:00
|
|
|
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;
|
|
|
|
}
|
2015-08-12 14:07:24 +00:00
|
|
|
|
2015-08-11 13:21:12 +00:00
|
|
|
// Calculate target aspect ratio if needed (don't bother if resizeMode == stretch)
|
|
|
|
CGFloat targetAspect = 0.0;
|
2015-07-14 11:06:17 +00:00
|
|
|
if (resizeMode != UIViewContentModeScaleToFill) {
|
|
|
|
targetAspect = destSize.width / destSize.height;
|
|
|
|
if (aspect == targetAspect) {
|
|
|
|
resizeMode = UIViewContentModeScaleToFill;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (resizeMode) {
|
|
|
|
case UIViewContentModeScaleToFill: // stretch
|
|
|
|
|
2015-08-12 14:07:24 +00:00
|
|
|
return (CGRect){CGPointZero, RCTCeilSize(destSize, destScale)};
|
2015-07-14 11:06:17 +00:00
|
|
|
|
|
|
|
case UIViewContentModeScaleAspectFit: // contain
|
|
|
|
|
|
|
|
if (targetAspect <= aspect) { // target is taller than content
|
|
|
|
|
2015-08-12 14:07:24 +00:00
|
|
|
sourceSize.width = destSize.width = destSize.width;
|
2015-07-14 11:06:17 +00:00
|
|
|
sourceSize.height = sourceSize.width / aspect;
|
|
|
|
|
|
|
|
} else { // target is wider than content
|
|
|
|
|
2015-08-12 14:07:24 +00:00
|
|
|
sourceSize.height = destSize.height = destSize.height;
|
2015-07-14 11:06:17 +00:00
|
|
|
sourceSize.width = sourceSize.height * aspect;
|
|
|
|
}
|
2015-07-21 12:40:06 +00:00
|
|
|
return (CGRect){CGPointZero, RCTCeilSize(sourceSize, destScale)};
|
2015-07-14 11:06:17 +00:00
|
|
|
|
|
|
|
case UIViewContentModeScaleAspectFill: // cover
|
|
|
|
|
|
|
|
if (targetAspect <= aspect) { // target is taller than content
|
|
|
|
|
2015-08-12 14:07:24 +00:00
|
|
|
sourceSize.height = destSize.height = destSize.height;
|
2015-07-14 11:06:17 +00:00
|
|
|
sourceSize.width = sourceSize.height * aspect;
|
|
|
|
destSize.width = destSize.height * targetAspect;
|
2015-07-21 12:40:06 +00:00
|
|
|
return (CGRect){
|
|
|
|
{RCTFloorValue((destSize.width - sourceSize.width) / 2, destScale), 0},
|
|
|
|
RCTCeilSize(sourceSize, destScale)
|
|
|
|
};
|
2015-07-14 11:06:17 +00:00
|
|
|
|
|
|
|
} else { // target is wider than content
|
|
|
|
|
2015-08-12 14:07:24 +00:00
|
|
|
sourceSize.width = destSize.width = destSize.width;
|
2015-07-14 11:06:17 +00:00
|
|
|
sourceSize.height = sourceSize.width / aspect;
|
|
|
|
destSize.height = destSize.width / targetAspect;
|
2015-07-21 12:40:06 +00:00
|
|
|
return (CGRect){
|
|
|
|
{0, RCTFloorValue((destSize.height - sourceSize.height) / 2, destScale)},
|
|
|
|
RCTCeilSize(sourceSize, destScale)
|
|
|
|
};
|
2015-07-14 11:06:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
|
|
|
RCTLogError(@"A resizeMode value of %zd is not supported", resizeMode);
|
2015-07-21 12:40:06 +00:00
|
|
|
return (CGRect){CGPointZero, RCTCeilSize(destSize, destScale)};
|
2015-07-14 11:06:17 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-08-12 14:07:24 +00:00
|
|
|
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)
|
2015-07-14 11:06:17 +00:00
|
|
|
{
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|