diff --git a/Libraries/CameraRoll/RCTAssetsLibraryImageLoader.m b/Libraries/CameraRoll/RCTAssetsLibraryImageLoader.m index ebec0764e..9b49742ac 100644 --- a/Libraries/CameraRoll/RCTAssetsLibraryImageLoader.m +++ b/Libraries/CameraRoll/RCTAssetsLibraryImageLoader.m @@ -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; -} diff --git a/Libraries/Image/RCTImageUtils.h b/Libraries/Image/RCTImageUtils.h index 1bca23241..e71288f5d 100644 --- a/Libraries/Image/RCTImageUtils.h +++ b/Libraries/Image/RCTImageUtils.h @@ -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); diff --git a/Libraries/Image/RCTImageUtils.m b/Libraries/Image/RCTImageUtils.m index 78008dcad..9456bd273 100644 --- a/Libraries/Image/RCTImageUtils.m +++ b/Libraries/Image/RCTImageUtils.m @@ -9,6 +9,9 @@ #import "RCTImageUtils.h" +#import +#import + #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; +}