Improved and generalized image thumbnail decoding logic

Reviewed By: jspahrsummers

Differential Revision: D2631934

fb-gh-sync-id: 3ddea328dcb0fc84b9d7b20708324f0b515f1b7f
This commit is contained in:
Nick Lockwood 2015-11-10 05:03:07 -08:00 committed by facebook-github-bot-2
parent 4b78ed2123
commit 2eb8068cf1
3 changed files with 104 additions and 48 deletions

View File

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

View File

@ -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);

View File

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