Improved and generalized image thumbnail decoding logic
Reviewed By: jspahrsummers Differential Revision: D2631934 fb-gh-sync-id: 3ddea328dcb0fc84b9d7b20708324f0b515f1b7f
This commit is contained in:
parent
4b78ed2123
commit
2eb8068cf1
|
@ -21,7 +21,6 @@
|
|||
#import "RCTUtils.h"
|
||||
|
||||
static dispatch_queue_t RCTAssetsLibraryImageLoaderQueue(void);
|
||||
static UIImage *RCTScaledImageForAsset(ALAssetRepresentation *representation, CGSize size, CGFloat scale, UIViewContentMode resizeMode, NSError **error);
|
||||
|
||||
@implementation RCTAssetsLibraryImageLoader
|
||||
{
|
||||
|
@ -99,14 +98,30 @@ RCT_EXPORT_MODULE()
|
|||
|
||||
#endif
|
||||
|
||||
UIImage *image;
|
||||
UIImage *image = nil;
|
||||
NSError *error = nil;
|
||||
if (useMaximumSize) {
|
||||
|
||||
image = [UIImage imageWithCGImage:representation.fullResolutionImage
|
||||
scale:scale
|
||||
orientation:(UIImageOrientation)representation.orientation];
|
||||
} else {
|
||||
image = RCTScaledImageForAsset(representation, size, scale, resizeMode, &error);
|
||||
|
||||
NSUInteger length = (NSUInteger)representation.size;
|
||||
uint8_t *buffer = (uint8_t *)malloc((size_t)length);
|
||||
if ([representation getBytes:buffer
|
||||
fromOffset:0
|
||||
length:length
|
||||
error:&error]) {
|
||||
|
||||
NSData *data = [[NSData alloc] initWithBytesNoCopy:buffer
|
||||
length:length
|
||||
freeWhenDone:YES];
|
||||
|
||||
image = RCTDecodeImageWithData(data, size, scale, resizeMode);
|
||||
} else {
|
||||
free(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
completionHandler(error, image);
|
||||
|
@ -151,48 +166,3 @@ static dispatch_queue_t RCTAssetsLibraryImageLoaderQueue(void)
|
|||
|
||||
return queue;
|
||||
}
|
||||
|
||||
// Why use a custom scaling method? Greater efficiency, reduced memory overhead:
|
||||
// http://www.mindsea.com/2012/12/downscaling-huge-alassets-without-fear-of-sigkill
|
||||
|
||||
static UIImage *RCTScaledImageForAsset(ALAssetRepresentation *representation,
|
||||
CGSize size,
|
||||
CGFloat scale,
|
||||
UIViewContentMode resizeMode,
|
||||
NSError **error)
|
||||
{
|
||||
NSUInteger length = (NSUInteger)representation.size;
|
||||
NSMutableData *data = [NSMutableData dataWithLength:length];
|
||||
if (![representation getBytes:data.mutableBytes
|
||||
fromOffset:0
|
||||
length:length
|
||||
error:error]) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
CGSize sourceSize = representation.dimensions;
|
||||
CGSize targetSize = RCTTargetSize(sourceSize, representation.scale,
|
||||
size, scale, resizeMode, NO);
|
||||
|
||||
NSDictionary *options = @{
|
||||
(id)kCGImageSourceShouldAllowFloat: @YES,
|
||||
(id)kCGImageSourceCreateThumbnailWithTransform: @YES,
|
||||
(id)kCGImageSourceCreateThumbnailFromImageAlways: @YES,
|
||||
(id)kCGImageSourceThumbnailMaxPixelSize: @(MAX(targetSize.width, targetSize.height) * scale)
|
||||
};
|
||||
|
||||
CGImageSourceRef sourceRef = CGImageSourceCreateWithData((__bridge CFDataRef)data, nil);
|
||||
CGImageRef imageRef = CGImageSourceCreateThumbnailAtIndex(sourceRef, 0, (__bridge CFDictionaryRef)options);
|
||||
if (sourceRef) {
|
||||
CFRelease(sourceRef);
|
||||
}
|
||||
|
||||
if (imageRef) {
|
||||
UIImage *image = [UIImage imageWithCGImage:imageRef scale:scale
|
||||
orientation:UIImageOrientationUp];
|
||||
CGImageRelease(imageRef);
|
||||
return image;
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
|
|
@ -40,3 +40,20 @@ RCT_EXTERN CGSize RCTTargetSize(CGSize sourceSize, CGFloat sourceScale,
|
|||
RCT_EXTERN BOOL RCTUpscalingRequired(CGSize sourceSize, CGFloat sourceScale,
|
||||
CGSize destSize, CGFloat destScale,
|
||||
UIViewContentMode resizeMode);
|
||||
/**
|
||||
* This function takes a source size and scale and returns the size in pixels.
|
||||
* Note that the pixel width/height is rounded up to the nearest integral size.
|
||||
*/
|
||||
RCT_EXTERN CGSize RCTSizeInPixels(CGSize pointSize, CGFloat scale);
|
||||
|
||||
/**
|
||||
* This function takes the source data 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 *RCTDecodeImageWithData(NSData *data,
|
||||
CGSize destSize,
|
||||
CGFloat destScale,
|
||||
UIViewContentMode resizeMode);
|
||||
|
|
|
@ -9,6 +9,9 @@
|
|||
|
||||
#import "RCTImageUtils.h"
|
||||
|
||||
#import <ImageIO/ImageIO.h>
|
||||
#import <tgmath.h>
|
||||
|
||||
#import "RCTLog.h"
|
||||
|
||||
static CGFloat RCTCeilValue(CGFloat value, CGFloat scale)
|
||||
|
@ -193,3 +196,69 @@ BOOL RCTUpscalingRequired(CGSize sourceSize, CGFloat sourceScale,
|
|||
return NO;
|
||||
}
|
||||
}
|
||||
|
||||
RCT_EXTERN CGSize RCTSizeInPixels(CGSize pointSize, CGFloat scale)
|
||||
{
|
||||
return (CGSize){
|
||||
ceil(pointSize.width * scale),
|
||||
ceil(pointSize.height * scale),
|
||||
};
|
||||
}
|
||||
|
||||
RCT_EXTERN UIImage *RCTDecodeImageWithData(NSData *data,
|
||||
CGSize destSize,
|
||||
CGFloat destScale,
|
||||
UIViewContentMode resizeMode)
|
||||
{
|
||||
CGImageSourceRef sourceRef = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL);
|
||||
if (!sourceRef) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
// get original image size
|
||||
CGSize sourceSize;
|
||||
CFDictionaryRef imageProperties = CGImageSourceCopyPropertiesAtIndex(sourceRef, 0, NULL);
|
||||
if (!imageProperties) {
|
||||
CFRelease(sourceRef);
|
||||
return nil;
|
||||
}
|
||||
NSNumber *width = CFDictionaryGetValue(imageProperties, kCGImagePropertyPixelWidth);
|
||||
NSNumber *height = CFDictionaryGetValue(imageProperties, kCGImagePropertyPixelHeight);
|
||||
sourceSize = (CGSize){width.doubleValue, height.doubleValue};
|
||||
CFRelease(imageProperties);
|
||||
|
||||
if (CGSizeEqualToSize(destSize, CGSizeZero)) {
|
||||
destSize = sourceSize;
|
||||
}
|
||||
|
||||
// calculate target size
|
||||
CGSize targetSize = RCTTargetSize(sourceSize, 1, destSize, destScale, resizeMode, YES);
|
||||
CGSize targetPixelSize = RCTSizeInPixels(targetSize, destScale);
|
||||
CGFloat maxPixelSize = fmax(fmin(sourceSize.width, targetPixelSize.width),
|
||||
fmin(sourceSize.height, targetPixelSize.height));
|
||||
|
||||
NSDictionary *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;
|
||||
}
|
||||
|
||||
//adjust scale
|
||||
size_t actualWidth = CGImageGetWidth(imageRef);
|
||||
CGFloat scale = actualWidth / targetSize.width;
|
||||
|
||||
// return image
|
||||
UIImage *image = [UIImage imageWithCGImage:imageRef
|
||||
scale:scale
|
||||
orientation:UIImageOrientationUp];
|
||||
CGImageRelease(imageRef);
|
||||
return image;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue