123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392 |
- /*
- * Copyright (c) Facebook, Inc. and its affiliates.
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE file in the root directory of this source tree.
- */
- #import <React/RCTImageUtils.h>
- #import <tgmath.h>
- #import <ImageIO/ImageIO.h>
- #import <MobileCoreServices/UTCoreTypes.h>
- #import <React/RCTLog.h>
- #import <React/RCTUtils.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)
- };
- }
- static CGImagePropertyOrientation CGImagePropertyOrientationFromUIImageOrientation(UIImageOrientation imageOrientation)
- {
- // see https://stackoverflow.com/a/6699649/496389
- switch (imageOrientation) {
- case UIImageOrientationUp: return kCGImagePropertyOrientationUp;
- case UIImageOrientationDown: return kCGImagePropertyOrientationDown;
- case UIImageOrientationLeft: return kCGImagePropertyOrientationLeft;
- case UIImageOrientationRight: return kCGImagePropertyOrientationRight;
- case UIImageOrientationUpMirrored: return kCGImagePropertyOrientationUpMirrored;
- case UIImageOrientationDownMirrored: return kCGImagePropertyOrientationDownMirrored;
- case UIImageOrientationLeftMirrored: return kCGImagePropertyOrientationLeftMirrored;
- case UIImageOrientationRightMirrored: return kCGImagePropertyOrientationRightMirrored;
- default: return kCGImagePropertyOrientationUp;
- }
- }
- CGRect RCTTargetRect(CGSize sourceSize, CGSize destSize,
- CGFloat destScale, RCTResizeMode 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
- CGFloat targetAspect = 0.0;
- if (resizeMode != RCTResizeModeCenter &&
- resizeMode != RCTResizeModeStretch) {
- targetAspect = destSize.width / destSize.height;
- if (aspect == targetAspect) {
- resizeMode = RCTResizeModeStretch;
- }
- }
- switch (resizeMode) {
- case RCTResizeModeStretch:
- case RCTResizeModeRepeat:
- return (CGRect){CGPointZero, RCTCeilSize(destSize, destScale)};
- case RCTResizeModeContain:
- if (targetAspect <= aspect) { // target is taller than content
- sourceSize.width = destSize.width;
- sourceSize.height = sourceSize.width / aspect;
- } else { // target is wider than content
- sourceSize.height = destSize.height;
- sourceSize.width = sourceSize.height * aspect;
- }
- return (CGRect){
- {
- RCTFloorValue((destSize.width - sourceSize.width) / 2, destScale),
- RCTFloorValue((destSize.height - sourceSize.height) / 2, destScale),
- },
- RCTCeilSize(sourceSize, destScale)
- };
- case RCTResizeModeCover:
- if (targetAspect <= aspect) { // target is taller than content
- 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;
- sourceSize.height = sourceSize.width / aspect;
- destSize.height = destSize.width / targetAspect;
- return (CGRect){
- {0, RCTFloorValue((destSize.height - sourceSize.height) / 2, destScale)},
- RCTCeilSize(sourceSize, destScale)
- };
- }
- case RCTResizeModeCenter:
- // Make sure the image is not clipped by the target.
- if (sourceSize.height > destSize.height) {
- sourceSize.width = destSize.width;
- sourceSize.height = sourceSize.width / aspect;
- }
- if (sourceSize.width > destSize.width) {
- sourceSize.height = destSize.height;
- sourceSize.width = sourceSize.height * aspect;
- }
- return (CGRect){
- {
- RCTFloorValue((destSize.width - sourceSize.width) / 2, destScale),
- RCTFloorValue((destSize.height - sourceSize.height) / 2, destScale),
- },
- RCTCeilSize(sourceSize, destScale)
- };
- }
- }
- CGAffineTransform RCTTransformFromTargetRect(CGSize sourceSize, CGRect targetRect)
- {
- CGAffineTransform transform = CGAffineTransformIdentity;
- transform = CGAffineTransformTranslate(transform,
- targetRect.origin.x,
- targetRect.origin.y);
- transform = CGAffineTransformScale(transform,
- targetRect.size.width / sourceSize.width,
- targetRect.size.height / sourceSize.height);
- return transform;
- }
- CGSize RCTTargetSize(CGSize sourceSize, CGFloat sourceScale,
- CGSize destSize, CGFloat destScale,
- RCTResizeMode resizeMode,
- BOOL allowUpscaling)
- {
- switch (resizeMode) {
- case RCTResizeModeCenter:
- return RCTTargetRect(sourceSize, destSize, destScale, resizeMode).size;
- case RCTResizeModeStretch:
- 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,
- RCTResizeMode 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 = RCTResizeModeStretch;
- }
- }
- switch (resizeMode) {
- case RCTResizeModeStretch:
- return destSize.width > sourceSize.width || destSize.height > sourceSize.height;
- case RCTResizeModeContain:
- 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 RCTResizeModeCover:
- if (targetAspect <= aspect) { // target is taller than content
- return destSize.height > sourceSize.height;
- } else { // target is wider than content
- return destSize.width > sourceSize.width;
- }
- case RCTResizeModeRepeat:
- case RCTResizeModeCenter:
- return NO;
- }
- }
- UIImage *__nullable RCTDecodeImageWithData(NSData *data,
- CGSize destSize,
- CGFloat destScale,
- RCTResizeMode resizeMode)
- {
- CGImageSourceRef sourceRef = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL);
- if (!sourceRef) {
- return nil;
- }
- // Get original image size
- CFDictionaryRef imageProperties = CGImageSourceCopyPropertiesAtIndex(sourceRef, 0, NULL);
- if (!imageProperties) {
- CFRelease(sourceRef);
- return nil;
- }
- NSNumber *width = CFDictionaryGetValue(imageProperties, kCGImagePropertyPixelWidth);
- NSNumber *height = CFDictionaryGetValue(imageProperties, kCGImagePropertyPixelHeight);
- CGSize sourceSize = {width.doubleValue, height.doubleValue};
- CFRelease(imageProperties);
- if (CGSizeEqualToSize(destSize, CGSizeZero)) {
- destSize = sourceSize;
- if (!destScale) {
- destScale = 1;
- }
- } else if (!destScale) {
- destScale = RCTScreenScale();
- }
- if (resizeMode == UIViewContentModeScaleToFill) {
- // Decoder cannot change aspect ratio, so RCTResizeModeStretch is equivalent
- // to RCTResizeModeCover for our purposes
- resizeMode = RCTResizeModeCover;
- }
- // Calculate target size
- CGSize targetSize = RCTTargetSize(sourceSize, 1, destSize, destScale, resizeMode, NO);
- CGSize targetPixelSize = RCTSizeInPixels(targetSize, destScale);
- CGFloat maxPixelSize = fmax(fmin(sourceSize.width, targetPixelSize.width),
- fmin(sourceSize.height, targetPixelSize.height));
- NSDictionary<NSString *, NSNumber *> *options = @{
- (id)kCGImageSourceShouldAllowFloat: @YES,
- (id)kCGImageSourceCreateThumbnailWithTransform: @YES,
- (id)kCGImageSourceCreateThumbnailFromImageAlways: @YES,
- (id)kCGImageSourceThumbnailMaxPixelSize: @(maxPixelSize),
- };
- // Get thumbnail
- CGImageRef imageRef = CGImageSourceCreateThumbnailAtIndex(sourceRef, 0, (__bridge CFDictionaryRef)options);
- CFRelease(sourceRef);
- if (!imageRef) {
- return nil;
- }
- // Return image
- UIImage *image = [UIImage imageWithCGImage:imageRef
- scale:destScale
- orientation:UIImageOrientationUp];
- CGImageRelease(imageRef);
- return image;
- }
- NSDictionary<NSString *, id> *__nullable RCTGetImageMetadata(NSData *data)
- {
- CGImageSourceRef sourceRef = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL);
- if (!sourceRef) {
- return nil;
- }
- CFDictionaryRef imageProperties = CGImageSourceCopyPropertiesAtIndex(sourceRef, 0, NULL);
- CFRelease(sourceRef);
- return (__bridge_transfer id)imageProperties;
- }
- NSData *__nullable RCTGetImageData(UIImage *image, float quality)
- {
- CGImageRef cgImage = image.CGImage;
- if (!cgImage) {
- return NULL;
- }
- NSMutableDictionary *properties = [[NSMutableDictionary alloc] initWithDictionary:@{
- (id)kCGImagePropertyOrientation : @(CGImagePropertyOrientationFromUIImageOrientation(image.imageOrientation))
- }];
- CGImageDestinationRef destination;
- CFMutableDataRef imageData = CFDataCreateMutable(NULL, 0);
- if (RCTImageHasAlpha(cgImage)) {
- // get png data
- destination = CGImageDestinationCreateWithData(imageData, kUTTypePNG, 1, NULL);
- } else {
- // get jpeg data
- destination = CGImageDestinationCreateWithData(imageData, kUTTypeJPEG, 1, NULL);
- [properties setValue:@(quality) forKey:(id)kCGImageDestinationLossyCompressionQuality];
- }
- if (!destination) {
- CFRelease(imageData);
- return NULL;
- }
- CGImageDestinationAddImage(destination, cgImage, (__bridge CFDictionaryRef)properties);
- if (!CGImageDestinationFinalize(destination)) {
- CFRelease(imageData);
- imageData = NULL;
- }
- CFRelease(destination);
- return (__bridge_transfer NSData *)imageData;
- }
- UIImage *__nullable RCTTransformImage(UIImage *image,
- CGSize destSize,
- CGFloat destScale,
- CGAffineTransform transform)
- {
- if (destSize.width <= 0 | destSize.height <= 0 || destScale <= 0) {
- return nil;
- }
- BOOL opaque = !RCTImageHasAlpha(image.CGImage);
- UIGraphicsBeginImageContextWithOptions(destSize, opaque, destScale);
- CGContextRef currentContext = UIGraphicsGetCurrentContext();
- CGContextConcatCTM(currentContext, transform);
- [image drawAtPoint:CGPointZero];
- UIImage *result = UIGraphicsGetImageFromCurrentImageContext();
- UIGraphicsEndImageContext();
- return result;
- }
- BOOL RCTImageHasAlpha(CGImageRef image)
- {
- switch (CGImageGetAlphaInfo(image)) {
- case kCGImageAlphaNone:
- case kCGImageAlphaNoneSkipLast:
- case kCGImageAlphaNoneSkipFirst:
- return NO;
- default:
- return YES;
- }
- }
|