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