react-native/Libraries/Image/RCTImageUtils.m

196 lines
5.8 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)
};
}
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};
}
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
return (CGRect){CGPointZero, RCTCeilSize(destSize, destScale)};
case UIViewContentModeScaleAspectFit: // contain
if (targetAspect <= aspect) { // target is taller than content
sourceSize.width = destSize.width = destSize.width;
sourceSize.height = sourceSize.width / aspect;
} else { // target is wider than content
sourceSize.height = destSize.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 = 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 = 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)};
}
}
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
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;
}
}