Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[iOS]:- fixed image resize mode in release mode for iOS #44763

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#import <memory>

#import <React/RCTUtils.h>
#import <React/RCTImageUtils.h>
#import <ReactCommon/RCTTurboModule.h>

#import "RCTImagePlugins.h"
Expand Down Expand Up @@ -49,7 +50,7 @@ - (nullable RCTImageLoaderCancellationBlock)loadImageForURL:(NSURL *)imageURL
partialLoadHandler:(RCTImageLoaderPartialLoadBlock)partialLoadHandler
completionHandler:(RCTImageLoaderCompletionBlock)completionHandler
{
UIImage *image = RCTImageFromLocalAssetURL(imageURL);
UIImage *image = RCTDecodeImageWithLocalAssetURL(imageURL, size, scale, resizeMode);
if (image) {
if (progressHandler) {
progressHandler(1, 1);
Expand Down
10 changes: 10 additions & 0 deletions packages/react-native/Libraries/Image/RCTImageUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,16 @@ RCT_EXTERN BOOL RCTUpscalingRequired(
RCT_EXTERN UIImage *__nullable
RCTDecodeImageWithData(NSData *data, CGSize destSize, CGFloat destScale, RCTResizeMode resizeMode);

/**
* This function takes the source url for an image and decodes it at the
* specified size. If the original image is smaller than the destination size,
* the resultant image's scale will be decreased to compensate, so the
* width/height of the returned image is guaranteed to be >= destSize.
* Pass a destSize of CGSizeZero to decode the image at its original size.
*/
RCT_EXTERN UIImage *__nullable
RCTDecodeImageWithLocalAssetURL(NSURL *url, CGSize destSize, CGFloat destScale, RCTResizeMode resizeMode);

/**
* This function takes the source data for an image and decodes just the
* metadata, without decompressing the image itself.
Expand Down
117 changes: 71 additions & 46 deletions packages/react-native/Libraries/Image/RCTImageUtils.mm
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,68 @@ static CGImagePropertyOrientation CGImagePropertyOrientationFromUIImageOrientati
}
}

static UIImage* decodeImageFromCGImageSourceRef(
CGImageSourceRef sourceRef,
CGSize destSize,
CGFloat destScale,
RCTResizeMode resizeMode)
{
if (!sourceRef) {
Biki-das marked this conversation as resolved.
Show resolved Hide resolved
return nil;
}

// Get original image size
CFDictionaryRef imageProperties = CGImageSourceCopyPropertiesAtIndex(sourceRef, 0, NULL);
if (!imageProperties) {
CFRelease(sourceRef);
return nil;
}
NSNumber *width = (NSNumber *)CFDictionaryGetValue(imageProperties, kCGImagePropertyPixelWidth);
NSNumber *height = (NSNumber *)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 == RCTResizeModeStretch) {
// 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;
}

CGRect RCTTargetRect(CGSize sourceSize, CGSize destSize, CGFloat destScale, RCTResizeMode resizeMode)
{
if (CGSizeEqualToSize(destSize, CGSizeZero)) {
Expand Down Expand Up @@ -257,59 +319,22 @@ BOOL RCTUpscalingRequired(
UIImage *__nullable RCTDecodeImageWithData(NSData *data, CGSize destSize, CGFloat destScale, RCTResizeMode resizeMode)
{
CGImageSourceRef sourceRef = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL);
if (!sourceRef) {
return nil;
}
return decodeImageFromCGImageSourceRef(sourceRef, destSize, destScale, resizeMode);

// Get original image size
CFDictionaryRef imageProperties = CGImageSourceCopyPropertiesAtIndex(sourceRef, 0, NULL);
if (!imageProperties) {
CFRelease(sourceRef);
return nil;
}
NSNumber *width = (NSNumber *)CFDictionaryGetValue(imageProperties, kCGImagePropertyPixelWidth);
NSNumber *height = (NSNumber *)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 == RCTResizeModeStretch) {
// 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));
UIImage *__nullable RCTDecodeImageWithLocalAssetURL(NSURL *url, CGSize destSize, CGFloat destScale, RCTResizeMode resizeMode)
{
CGImageSourceRef sourceRef = CGImageSourceCreateWithURL((__bridge CFURLRef)url, NULL);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for the late reviewev. A concern has been raised internallt that this implementation might bypass [UIImage imageNamed:] which also works as a cache and it is more efficient for small images like icons.

A suggestion is to use decodeImageFromCGImageSourceRef only if the resizeMode is different from the default.

So, the logic would be (pseudocode here):

if (resizeMode is the default) {
  return RCTImageFromLocalAssetURL(url);
}

//rest of the current method

what do you think?

UIImage* image = decodeImageFromCGImageSourceRef(sourceRef, destSize, destScale, resizeMode);

NSDictionary<NSString *, NSNumber *> *options = @{
(id)kCGImageSourceShouldAllowFloat : @YES,
(id)kCGImageSourceCreateThumbnailWithTransform : @YES,
(id)kCGImageSourceCreateThumbnailFromImageAlways : @YES,
(id)kCGImageSourceThumbnailMaxPixelSize : @(maxPixelSize),
};
if (!image) {
image = RCTImageFromLocalAssetURL(url);

// 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;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#import <memory>

#import <React/RCTUtils.h>
#import <React/RCTImageUtils.h>
#import <ReactCommon/RCTTurboModule.h>

#import "RCTImagePlugins.h"
Expand Down Expand Up @@ -49,7 +50,7 @@ - (nullable RCTImageLoaderCancellationBlock)loadImageForURL:(NSURL *)imageURL
partialLoadHandler:(RCTImageLoaderPartialLoadBlock)partialLoadHandler
completionHandler:(RCTImageLoaderCompletionBlock)completionHandler
{
UIImage *image = RCTImageFromLocalAssetURL(imageURL);
UIImage *image = RCTDecodeImageWithLocalAssetURL(imageURL, size, scale, resizeMode);
if (image) {
if (progressHandler) {
progressHandler(1, 1);
Expand Down
Loading