diff --git a/Examples/UIExplorer/ImageExample.js b/Examples/UIExplorer/ImageExample.js index 50c96ffd2..163396f89 100644 --- a/Examples/UIExplorer/ImageExample.js +++ b/Examples/UIExplorer/ImageExample.js @@ -68,8 +68,6 @@ var NetworkImageCallbackExample = React.createClass({ }); var NetworkImageExample = React.createClass({ - watchID: (null: ?number), - getInitialState: function() { return { error: false, @@ -97,6 +95,38 @@ var NetworkImageExample = React.createClass({ } }); +var ImageSizeExample = React.createClass({ + getInitialState: function() { + return { + width: 0, + height: 0, + }; + }, + componentDidMount: function() { + Image.getSize(this.props.source.uri, (width, height) => { + this.setState({width, height}); + }); + }, + render: function() { + return ( + + + + Actual dimensions:{'\n'} + Width: {this.state.width}, Height: {this.state.height} + + + ); + }, +}); + exports.displayName = (undefined: ?string); exports.framework = 'React'; exports.title = ''; @@ -408,6 +438,12 @@ exports.examples = [ }, platform: 'ios', }, + { + title: 'Image Size', + render: function() { + return ; + } + }, ]; var fullImage = {uri: 'http://facebook.github.io/react/img/logo_og.png'}; diff --git a/Libraries/Image/Image.ios.js b/Libraries/Image/Image.ios.js index 8eda0cc8c..91293c07f 100644 --- a/Libraries/Image/Image.ios.js +++ b/Libraries/Image/Image.ios.js @@ -15,7 +15,6 @@ var EdgeInsetsPropType = require('EdgeInsetsPropType'); var ImageResizeMode = require('ImageResizeMode'); var ImageStylePropTypes = require('ImageStylePropTypes'); var NativeMethodsMixin = require('NativeMethodsMixin'); -var NativeModules = require('NativeModules'); var PropTypes = require('ReactPropTypes'); var React = require('React'); var ReactNativeViewAttributes = require('ReactNativeViewAttributes'); @@ -29,6 +28,11 @@ var requireNativeComponent = require('requireNativeComponent'); var resolveAssetSource = require('resolveAssetSource'); var warning = require('warning'); +var { + ImageViewManager, + NetworkImageViewManager, +} = require('NativeModules'); + /** * A React component for displaying different types of images, * including network images, static resources, temporary local images, and @@ -197,7 +201,7 @@ var Image = React.createClass({ /> ); } - } + }, }); var styles = StyleSheet.create({ @@ -207,7 +211,30 @@ var styles = StyleSheet.create({ }); var RCTImageView = requireNativeComponent('RCTImageView', Image); -var RCTNetworkImageView = NativeModules.NetworkImageViewManager ? requireNativeComponent('RCTNetworkImageView', Image) : RCTImageView; +var RCTNetworkImageView = NetworkImageViewManager ? requireNativeComponent('RCTNetworkImageView', Image) : RCTImageView; var RCTVirtualImage = requireNativeComponent('RCTVirtualImage', Image); +/** + * Retrieve the width and height (in pixels) of an image prior to displaying it. + * This method can fail if the image cannot be found, or fails to download. + * + * In order to retrieve the image dimensions, the image may first need to be + * loaded or downloaded, after which it will be cached. This means that in + * principle you could use this method to preload images, however it is not + * optimized for that purpose, and may in future be implemented in a way that + * does not fully load/download the image data. A proper, supported way to + * preload images will be provided as a separate API. + * + * @platform ios + */ +Image.getSize = function( + uri: string, + success: (width: number, height: number) => void, + failure: (error: any) => void, +) { + ImageViewManager.getSize(uri, success, failure || function() { + console.warn('Failed to get size for image: ' + uri); + }); +}; + module.exports = Image; diff --git a/Libraries/Image/RCTImageLoader.h b/Libraries/Image/RCTImageLoader.h index 770747452..c9bea5437 100644 --- a/Libraries/Image/RCTImageLoader.h +++ b/Libraries/Image/RCTImageLoader.h @@ -55,6 +55,13 @@ typedef void (^RCTImageLoaderCancellationBlock)(void); resizeMode:(UIViewContentMode)resizeMode completionBlock:(RCTImageLoaderCompletionBlock)completionBlock; +/** + * Get image size, in pixels. This method will do the least work possible to get + * the information, and won't decode the image if it doesn't have to. + */ +- (RCTImageLoaderCancellationBlock)getImageSize:(NSString *)imageTag + block:(void(^)(NSError *error, CGSize size))completionBlock; + @end @interface RCTBridge (RCTImageLoader) diff --git a/Libraries/Image/RCTImageLoader.m b/Libraries/Image/RCTImageLoader.m index acee9dbec..cd681b113 100644 --- a/Libraries/Image/RCTImageLoader.m +++ b/Libraries/Image/RCTImageLoader.m @@ -11,6 +11,7 @@ #import #import +#import #import "RCTConvert.h" #import "RCTDefines.h" @@ -183,29 +184,34 @@ RCT_EXPORT_MODULE() completionBlock:callback]; } -- (RCTImageLoaderCancellationBlock)loadImageWithTag:(NSString *)imageTag - size:(CGSize)size - scale:(CGFloat)scale - resizeMode:(UIViewContentMode)resizeMode - progressBlock:(RCTImageLoaderProgressBlock)progressHandler - completionBlock:(RCTImageLoaderCompletionBlock)completionBlock +/** + * This returns either an image, or raw image data, depending on the loading + * path taken. This is useful if you want to skip decoding, e.g. when preloading + * the image, or retrieving metadata. + */ +- (RCTImageLoaderCancellationBlock)loadImageOrDataWithTag:(NSString *)imageTag + size:(CGSize)size + scale:(CGFloat)scale + resizeMode:(UIViewContentMode)resizeMode + progressBlock:(RCTImageLoaderProgressBlock)progressHandler + completionBlock:(void (^)(NSError *error, id imageOrData))completionBlock { __block volatile uint32_t cancelled = 0; __block void(^cancelLoad)(void) = nil; __weak RCTImageLoader *weakSelf = self; - RCTImageLoaderCompletionBlock completionHandler = ^(NSError *error, UIImage *image) { + void (^completionHandler)(NSError *error, id imageOrData) = ^(NSError *error, id imageOrData) { if ([NSThread isMainThread]) { // Most loaders do not return on the main thread, so caller is probably not // expecting it, and may do expensive post-processing in the callback dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ if (!cancelled) { - completionBlock(error, image); + completionBlock(error, imageOrData); } }); } else if (!cancelled) { - completionBlock(error, image); + completionBlock(error, imageOrData); } }; @@ -259,7 +265,6 @@ RCT_EXPORT_MODULE() } // Use networking module to load image - __block RCTImageLoaderCancellationBlock cancelDecode = nil; RCTURLRequestCompletionBlock processResponse = ^(NSURLResponse *response, NSData *data, NSError *error) { @@ -283,12 +288,8 @@ RCT_EXPORT_MODULE() } } - // Decode image - cancelDecode = [strongSelf decodeImageData:data - size:size - scale:scale - resizeMode:resizeMode - completionBlock:completionHandler]; + // Call handler + completionHandler(nil, data); }; // Add missing png extension @@ -325,7 +326,6 @@ RCT_EXPORT_MODULE() userInfo:nil storagePolicy:isHTTPRequest ? NSURLCacheStorageAllowed: NSURLCacheStorageAllowedInMemoryOnly] forRequest:request]; - // Process image data processResponse(response, data, nil); @@ -337,9 +337,6 @@ RCT_EXPORT_MODULE() cancelLoad = ^{ [task cancel]; - if (cancelDecode) { - cancelDecode(); - } }; }); @@ -352,6 +349,45 @@ RCT_EXPORT_MODULE() }; } +- (RCTImageLoaderCancellationBlock)loadImageWithTag:(NSString *)imageTag + size:(CGSize)size + scale:(CGFloat)scale + resizeMode:(UIViewContentMode)resizeMode + progressBlock:(RCTImageLoaderProgressBlock)progressHandler + completionBlock:(RCTImageLoaderCompletionBlock)completionBlock +{ + __block volatile uint32_t cancelled = 0; + __block void(^cancelLoad)(void) = nil; + __weak RCTImageLoader *weakSelf = self; + + void (^completionHandler)(NSError *error, id imageOrData) = ^(NSError *error, id imageOrData) { + if (!cancelled) { + if (!imageOrData || [imageOrData isKindOfClass:[UIImage class]]) { + completionBlock(error, imageOrData); + } else { + cancelLoad = [weakSelf decodeImageData:imageOrData + size:size + scale:scale + resizeMode:resizeMode + completionBlock:completionBlock] ?: ^{}; + } + } + }; + + cancelLoad = [self loadImageOrDataWithTag:imageTag + size:size + scale:scale + resizeMode:resizeMode + progressBlock:progressHandler + completionBlock:completionHandler] ?: ^{}; + return ^{ + if (cancelLoad) { + cancelLoad(); + } + OSAtomicOr32Barrier(1, &cancelled); + }; +} + - (RCTImageLoaderCancellationBlock)decodeImageData:(NSData *)data size:(CGSize)size scale:(CGFloat)scale @@ -394,6 +430,33 @@ RCT_EXPORT_MODULE() } } +- (RCTImageLoaderCancellationBlock)getImageSize:(NSString *)imageTag + block:(void(^)(NSError *error, CGSize size))completionBlock +{ + return [self loadImageOrDataWithTag:imageTag + size:CGSizeZero + scale:1 + resizeMode:UIViewContentModeScaleToFill + progressBlock:nil + completionBlock:^(NSError *error, id imageOrData) { + CGSize size; + if ([imageOrData isKindOfClass:[NSData class]]) { + NSDictionary *meta = RCTGetImageMetadata(imageOrData); + size = (CGSize){ + [meta[(id)kCGImagePropertyPixelWidth] doubleValue], + [meta[(id)kCGImagePropertyPixelHeight] doubleValue], + }; + } else { + UIImage *image = imageOrData; + size = (CGSize){ + image.size.width * image.scale, + image.size.height * image.scale, + }; + } + completionBlock(error, size); + }]; +} + #pragma mark - RCTURLRequestHandler - (BOOL)canHandleRequest:(NSURLRequest *)request diff --git a/Libraries/Image/RCTImageUtils.h b/Libraries/Image/RCTImageUtils.h index 901a876ab..b9a118ec1 100644 --- a/Libraries/Image/RCTImageUtils.h +++ b/Libraries/Image/RCTImageUtils.h @@ -58,6 +58,12 @@ RCT_EXTERN UIImage *RCTDecodeImageWithData(NSData *data, CGFloat destScale, UIViewContentMode resizeMode); +/** + * This function takes the source data for an image and decodes just the + * metadata, without decompressing the image itself. + */ +RCT_EXTERN NSDictionary *RCTGetImageMetadata(NSData *data); + /** * Convert an image back into data. Images with an alpha channel will be * converted to lossless PNG data. Images without alpha will be converted to diff --git a/Libraries/Image/RCTImageUtils.m b/Libraries/Image/RCTImageUtils.m index a6c2ccd46..a9476ff51 100644 --- a/Libraries/Image/RCTImageUtils.m +++ b/Libraries/Image/RCTImageUtils.m @@ -218,7 +218,6 @@ UIImage *RCTDecodeImageWithData(NSData *data, } // get original image size - CGSize sourceSize; CFDictionaryRef imageProperties = CGImageSourceCopyPropertiesAtIndex(sourceRef, 0, NULL); if (!imageProperties) { CFRelease(sourceRef); @@ -226,7 +225,7 @@ UIImage *RCTDecodeImageWithData(NSData *data, } NSNumber *width = CFDictionaryGetValue(imageProperties, kCGImagePropertyPixelWidth); NSNumber *height = CFDictionaryGetValue(imageProperties, kCGImagePropertyPixelHeight); - sourceSize = (CGSize){width.doubleValue, height.doubleValue}; + CGSize sourceSize = {width.doubleValue, height.doubleValue}; CFRelease(imageProperties); if (CGSizeEqualToSize(destSize, CGSizeZero)) { @@ -266,6 +265,17 @@ UIImage *RCTDecodeImageWithData(NSData *data, return image; } +NSDictionary *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 *RCTGetImageData(CGImageRef image, float quality) { NSDictionary *properties; diff --git a/Libraries/Image/RCTImageViewManager.m b/Libraries/Image/RCTImageViewManager.m index d318d1f04..7ef593195 100644 --- a/Libraries/Image/RCTImageViewManager.m +++ b/Libraries/Image/RCTImageViewManager.m @@ -12,6 +12,7 @@ #import #import "RCTConvert.h" +#import "RCTImageLoader.h" #import "RCTImageSource.h" #import "RCTImageView.h" @@ -42,4 +43,18 @@ RCT_CUSTOM_VIEW_PROPERTY(tintColor, UIColor, RCTImageView) view.renderingMode = json ? UIImageRenderingModeAlwaysTemplate : defaultView.renderingMode; } +RCT_EXPORT_METHOD(getSize:(NSURL *)imageURL + successBlock:(RCTResponseSenderBlock)successBlock + errorBlock:(RCTResponseErrorBlock)errorBlock) +{ + [self.bridge.imageLoader getImageSize:imageURL.absoluteString + block:^(NSError *error, CGSize size) { + if (error) { + errorBlock(error); + } else { + successBlock(@[@(size.width), @(size.height)]); + } + }]; +} + @end