2015-03-23 20:28:42 +00:00
|
|
|
/**
|
|
|
|
* Copyright (c) 2015-present, Facebook, Inc.
|
|
|
|
* All rights reserved.
|
|
|
|
*
|
|
|
|
* This source code is licensed under the BSD-style license found in the
|
|
|
|
* LICENSE file in the root directory of this source tree. An additional grant
|
|
|
|
* of patent rights can be found in the PATENTS file in the same directory.
|
|
|
|
*/
|
2015-03-10 00:08:01 +00:00
|
|
|
|
|
|
|
#import "RCTImageLoader.h"
|
|
|
|
|
2015-10-20 12:00:50 +00:00
|
|
|
#import <libkern/OSAtomic.h>
|
2015-03-10 00:08:01 +00:00
|
|
|
#import <UIKit/UIKit.h>
|
Added getImageSize method
Summary:
public
This diff adds a `getSize()` method to `Image` to retrieve the width and height of an image prior to displaying it. This is useful when working with images from uncontrolled sources, and has been a much-requested feature.
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 fully supported way to preload images will be provided in a future diff.
The API (separate success and failure callbacks) is far from ideal, but until we agree on a unified standard, this was the most conventional way I could think of to implement it. If it returned a promise or something similar, it would be unique among all such APIS in the framework.
Please note that this has been a long time coming, in part due to much bikeshedding about what the API should look like, so while it's not unlikely that the API may change in future, I think having *some* way to do this is better than waiting until we can define the "perfect" way.
Reviewed By: vjeux
Differential Revision: D2797365
fb-gh-sync-id: 11eb1b8547773b1f8be0bc55ddf6dfedebf7fc0a
2016-01-01 02:50:26 +00:00
|
|
|
#import <ImageIO/ImageIO.h>
|
2015-03-10 00:08:01 +00:00
|
|
|
|
|
|
|
#import "RCTConvert.h"
|
2015-06-09 19:25:33 +00:00
|
|
|
#import "RCTDefines.h"
|
2015-10-08 18:32:11 +00:00
|
|
|
#import "RCTImageUtils.h"
|
2015-03-10 00:08:01 +00:00
|
|
|
#import "RCTLog.h"
|
2015-10-19 16:04:54 +00:00
|
|
|
#import "RCTNetworking.h"
|
2015-06-09 19:25:24 +00:00
|
|
|
#import "RCTUtils.h"
|
2015-03-10 00:08:01 +00:00
|
|
|
|
2015-09-04 11:35:44 +00:00
|
|
|
@implementation UIImage (React)
|
|
|
|
|
|
|
|
- (CAKeyframeAnimation *)reactKeyframeAnimation
|
|
|
|
{
|
|
|
|
return objc_getAssociatedObject(self, _cmd);
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)setReactKeyframeAnimation:(CAKeyframeAnimation *)reactKeyframeAnimation
|
|
|
|
{
|
|
|
|
objc_setAssociatedObject(self, @selector(reactKeyframeAnimation), reactKeyframeAnimation, OBJC_ASSOCIATION_COPY_NONATOMIC);
|
|
|
|
}
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
2015-03-10 00:08:01 +00:00
|
|
|
@implementation RCTImageLoader
|
2015-10-19 16:04:54 +00:00
|
|
|
{
|
2015-11-03 22:45:46 +00:00
|
|
|
NSArray<id<RCTImageURLLoader>> *_loaders;
|
|
|
|
NSArray<id<RCTImageDataDecoder>> *_decoders;
|
2015-11-20 13:15:49 +00:00
|
|
|
dispatch_queue_t _URLCacheQueue;
|
|
|
|
NSURLCache *_URLCache;
|
2015-10-19 16:04:54 +00:00
|
|
|
}
|
2015-03-10 00:08:01 +00:00
|
|
|
|
2015-07-27 15:48:31 +00:00
|
|
|
@synthesize bridge = _bridge;
|
|
|
|
|
|
|
|
RCT_EXPORT_MODULE()
|
|
|
|
|
2015-11-25 11:09:00 +00:00
|
|
|
- (void)setUp
|
2015-07-14 11:06:17 +00:00
|
|
|
{
|
2015-10-19 16:04:54 +00:00
|
|
|
// Get image loaders and decoders
|
2015-11-03 22:45:46 +00:00
|
|
|
NSMutableArray<id<RCTImageURLLoader>> *loaders = [NSMutableArray array];
|
|
|
|
NSMutableArray<id<RCTImageDataDecoder>> *decoders = [NSMutableArray array];
|
2015-11-25 11:09:00 +00:00
|
|
|
for (Class moduleClass in _bridge.moduleClasses) {
|
|
|
|
if ([moduleClass conformsToProtocol:@protocol(RCTImageURLLoader)]) {
|
|
|
|
[loaders addObject:[_bridge moduleForClass:moduleClass]];
|
2015-10-19 16:04:54 +00:00
|
|
|
}
|
2015-11-25 11:09:00 +00:00
|
|
|
if ([moduleClass conformsToProtocol:@protocol(RCTImageDataDecoder)]) {
|
|
|
|
[decoders addObject:[_bridge moduleForClass:moduleClass]];
|
2015-09-02 15:25:10 +00:00
|
|
|
}
|
2015-07-21 12:39:57 +00:00
|
|
|
}
|
2015-10-19 16:04:54 +00:00
|
|
|
|
|
|
|
// Sort loaders in reverse priority order (highest priority first)
|
|
|
|
[loaders sortUsingComparator:^NSComparisonResult(id<RCTImageURLLoader> a, id<RCTImageURLLoader> b) {
|
|
|
|
float priorityA = [a respondsToSelector:@selector(loaderPriority)] ? [a loaderPriority] : 0;
|
|
|
|
float priorityB = [b respondsToSelector:@selector(loaderPriority)] ? [b loaderPriority] : 0;
|
|
|
|
if (priorityA > priorityB) {
|
2015-09-02 15:25:10 +00:00
|
|
|
return NSOrderedAscending;
|
2015-10-19 16:04:54 +00:00
|
|
|
} else if (priorityA < priorityB) {
|
2015-09-02 15:25:10 +00:00
|
|
|
return NSOrderedDescending;
|
|
|
|
} else {
|
2015-10-19 16:04:54 +00:00
|
|
|
return NSOrderedSame;
|
|
|
|
}
|
|
|
|
}];
|
2015-07-21 12:39:57 +00:00
|
|
|
|
2015-10-19 16:04:54 +00:00
|
|
|
// Sort decoders in reverse priority order (highest priority first)
|
|
|
|
[decoders sortUsingComparator:^NSComparisonResult(id<RCTImageDataDecoder> a, id<RCTImageDataDecoder> b) {
|
|
|
|
float priorityA = [a respondsToSelector:@selector(decoderPriority)] ? [a decoderPriority] : 0;
|
|
|
|
float priorityB = [b respondsToSelector:@selector(decoderPriority)] ? [b decoderPriority] : 0;
|
|
|
|
if (priorityA > priorityB) {
|
|
|
|
return NSOrderedAscending;
|
|
|
|
} else if (priorityA < priorityB) {
|
|
|
|
return NSOrderedDescending;
|
|
|
|
} else {
|
2015-09-02 15:25:10 +00:00
|
|
|
return NSOrderedSame;
|
|
|
|
}
|
|
|
|
}];
|
2015-10-19 16:04:54 +00:00
|
|
|
|
|
|
|
_loaders = loaders;
|
|
|
|
_decoders = decoders;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (id<RCTImageURLLoader>)imageURLLoaderForURL:(NSURL *)URL
|
|
|
|
{
|
2015-11-25 11:09:00 +00:00
|
|
|
if (!_loaders) {
|
|
|
|
[self setUp];
|
|
|
|
}
|
|
|
|
|
2015-10-19 16:04:54 +00:00
|
|
|
if (RCT_DEBUG) {
|
|
|
|
// Check for handler conflicts
|
|
|
|
float previousPriority = 0;
|
|
|
|
id<RCTImageURLLoader> previousLoader = nil;
|
|
|
|
for (id<RCTImageURLLoader> loader in _loaders) {
|
2015-11-17 15:18:55 +00:00
|
|
|
float priority = [loader respondsToSelector:@selector(loaderPriority)] ? [loader loaderPriority] : 0;
|
|
|
|
if (previousLoader && priority < previousPriority) {
|
|
|
|
return previousLoader;
|
|
|
|
}
|
2015-10-19 16:04:54 +00:00
|
|
|
if ([loader canLoadImageURL:URL]) {
|
|
|
|
if (previousLoader) {
|
|
|
|
if (priority == previousPriority) {
|
|
|
|
RCTLogError(@"The RCTImageURLLoaders %@ and %@ both reported that"
|
|
|
|
" they can load the URL %@, and have equal priority"
|
|
|
|
" (%g). This could result in non-deterministic behavior.",
|
|
|
|
loader, previousLoader, URL, priority);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
previousLoader = loader;
|
|
|
|
previousPriority = priority;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2015-11-17 15:18:55 +00:00
|
|
|
return previousLoader;
|
2015-10-19 16:04:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Normal code path
|
|
|
|
for (id<RCTImageURLLoader> loader in _loaders) {
|
|
|
|
if ([loader canLoadImageURL:URL]) {
|
|
|
|
return loader;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (id<RCTImageDataDecoder>)imageDataDecoderForData:(NSData *)data
|
|
|
|
{
|
2015-11-25 11:09:00 +00:00
|
|
|
if (!_decoders) {
|
|
|
|
[self setUp];
|
|
|
|
}
|
|
|
|
|
2015-10-19 16:04:54 +00:00
|
|
|
if (RCT_DEBUG) {
|
|
|
|
// Check for handler conflicts
|
|
|
|
float previousPriority = 0;
|
|
|
|
id<RCTImageDataDecoder> previousDecoder = nil;
|
|
|
|
for (id<RCTImageDataDecoder> decoder in _decoders) {
|
2015-11-17 15:18:55 +00:00
|
|
|
float priority = [decoder respondsToSelector:@selector(decoderPriority)] ? [decoder decoderPriority] : 0;
|
|
|
|
if (previousDecoder && priority < previousPriority) {
|
|
|
|
return previousDecoder;
|
|
|
|
}
|
2015-10-19 16:04:54 +00:00
|
|
|
if ([decoder canDecodeImageData:data]) {
|
|
|
|
if (previousDecoder) {
|
|
|
|
if (priority == previousPriority) {
|
|
|
|
RCTLogError(@"The RCTImageDataDecoders %@ and %@ both reported that"
|
|
|
|
" they can decode the data <NSData %p; %tu bytes>, and"
|
|
|
|
" have equal priority (%g). This could result in"
|
|
|
|
" non-deterministic behavior.",
|
|
|
|
decoder, previousDecoder, data, data.length, priority);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
previousDecoder = decoder;
|
|
|
|
previousPriority = priority;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2015-11-17 15:18:55 +00:00
|
|
|
return previousDecoder;
|
2015-10-19 16:04:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Normal code path
|
|
|
|
for (id<RCTImageDataDecoder> decoder in _decoders) {
|
|
|
|
if ([decoder canDecodeImageData:data]) {
|
|
|
|
return decoder;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (RCTImageLoaderCancellationBlock)loadImageWithTag:(NSString *)imageTag
|
|
|
|
callback:(RCTImageLoaderCompletionBlock)callback
|
|
|
|
{
|
|
|
|
return [self loadImageWithTag:imageTag
|
|
|
|
size:CGSizeZero
|
|
|
|
scale:1
|
|
|
|
resizeMode:UIViewContentModeScaleToFill
|
|
|
|
progressBlock:nil
|
|
|
|
completionBlock:callback];
|
2015-07-27 15:48:31 +00:00
|
|
|
}
|
|
|
|
|
Added getImageSize method
Summary:
public
This diff adds a `getSize()` method to `Image` to retrieve the width and height of an image prior to displaying it. This is useful when working with images from uncontrolled sources, and has been a much-requested feature.
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 fully supported way to preload images will be provided in a future diff.
The API (separate success and failure callbacks) is far from ideal, but until we agree on a unified standard, this was the most conventional way I could think of to implement it. If it returned a promise or something similar, it would be unique among all such APIS in the framework.
Please note that this has been a long time coming, in part due to much bikeshedding about what the API should look like, so while it's not unlikely that the API may change in future, I think having *some* way to do this is better than waiting until we can define the "perfect" way.
Reviewed By: vjeux
Differential Revision: D2797365
fb-gh-sync-id: 11eb1b8547773b1f8be0bc55ddf6dfedebf7fc0a
2016-01-01 02:50:26 +00:00
|
|
|
/**
|
|
|
|
* 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
|
2015-03-10 00:08:01 +00:00
|
|
|
{
|
2015-10-20 12:00:50 +00:00
|
|
|
__block volatile uint32_t cancelled = 0;
|
2015-11-20 13:15:49 +00:00
|
|
|
__block void(^cancelLoad)(void) = nil;
|
|
|
|
__weak RCTImageLoader *weakSelf = self;
|
|
|
|
|
Added getImageSize method
Summary:
public
This diff adds a `getSize()` method to `Image` to retrieve the width and height of an image prior to displaying it. This is useful when working with images from uncontrolled sources, and has been a much-requested feature.
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 fully supported way to preload images will be provided in a future diff.
The API (separate success and failure callbacks) is far from ideal, but until we agree on a unified standard, this was the most conventional way I could think of to implement it. If it returned a promise or something similar, it would be unique among all such APIS in the framework.
Please note that this has been a long time coming, in part due to much bikeshedding about what the API should look like, so while it's not unlikely that the API may change in future, I think having *some* way to do this is better than waiting until we can define the "perfect" way.
Reviewed By: vjeux
Differential Revision: D2797365
fb-gh-sync-id: 11eb1b8547773b1f8be0bc55ddf6dfedebf7fc0a
2016-01-01 02:50:26 +00:00
|
|
|
void (^completionHandler)(NSError *error, id imageOrData) = ^(NSError *error, id imageOrData) {
|
2015-10-20 12:00:50 +00:00
|
|
|
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) {
|
Added getImageSize method
Summary:
public
This diff adds a `getSize()` method to `Image` to retrieve the width and height of an image prior to displaying it. This is useful when working with images from uncontrolled sources, and has been a much-requested feature.
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 fully supported way to preload images will be provided in a future diff.
The API (separate success and failure callbacks) is far from ideal, but until we agree on a unified standard, this was the most conventional way I could think of to implement it. If it returned a promise or something similar, it would be unique among all such APIS in the framework.
Please note that this has been a long time coming, in part due to much bikeshedding about what the API should look like, so while it's not unlikely that the API may change in future, I think having *some* way to do this is better than waiting until we can define the "perfect" way.
Reviewed By: vjeux
Differential Revision: D2797365
fb-gh-sync-id: 11eb1b8547773b1f8be0bc55ddf6dfedebf7fc0a
2016-01-01 02:50:26 +00:00
|
|
|
completionBlock(error, imageOrData);
|
2015-10-20 12:00:50 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
} else if (!cancelled) {
|
Added getImageSize method
Summary:
public
This diff adds a `getSize()` method to `Image` to retrieve the width and height of an image prior to displaying it. This is useful when working with images from uncontrolled sources, and has been a much-requested feature.
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 fully supported way to preload images will be provided in a future diff.
The API (separate success and failure callbacks) is far from ideal, but until we agree on a unified standard, this was the most conventional way I could think of to implement it. If it returned a promise or something similar, it would be unique among all such APIS in the framework.
Please note that this has been a long time coming, in part due to much bikeshedding about what the API should look like, so while it's not unlikely that the API may change in future, I think having *some* way to do this is better than waiting until we can define the "perfect" way.
Reviewed By: vjeux
Differential Revision: D2797365
fb-gh-sync-id: 11eb1b8547773b1f8be0bc55ddf6dfedebf7fc0a
2016-01-01 02:50:26 +00:00
|
|
|
completionBlock(error, imageOrData);
|
2015-10-20 12:00:50 +00:00
|
|
|
}
|
2015-10-19 16:04:54 +00:00
|
|
|
};
|
|
|
|
|
2015-11-20 13:15:49 +00:00
|
|
|
if (imageTag.length == 0) {
|
|
|
|
completionHandler(RCTErrorWithMessage(@"source.uri should not be an empty string"), nil);
|
2015-10-12 11:14:21 +00:00
|
|
|
return ^{};
|
2015-09-02 15:25:10 +00:00
|
|
|
}
|
2015-07-14 11:06:17 +00:00
|
|
|
|
2015-11-20 13:15:49 +00:00
|
|
|
// All access to URL cache must be serialized
|
2015-11-25 11:09:00 +00:00
|
|
|
if (!_URLCacheQueue) {
|
|
|
|
_URLCacheQueue = dispatch_queue_create("com.facebook.react.ImageLoaderURLCacheQueue", DISPATCH_QUEUE_SERIAL);
|
|
|
|
}
|
2015-11-20 13:15:49 +00:00
|
|
|
dispatch_async(_URLCacheQueue, ^{
|
2015-11-25 11:09:00 +00:00
|
|
|
|
|
|
|
if (!_URLCache) {
|
|
|
|
_URLCache = [[NSURLCache alloc] initWithMemoryCapacity:5 * 1024 * 1024 // 5MB
|
|
|
|
diskCapacity:200 * 1024 * 1024 // 200MB
|
|
|
|
diskPath:@"React/RCTImageDownloader"];
|
|
|
|
}
|
|
|
|
|
2015-11-20 13:15:49 +00:00
|
|
|
RCTImageLoader *strongSelf = weakSelf;
|
|
|
|
if (cancelled || !strongSelf) {
|
|
|
|
return;
|
|
|
|
}
|
2015-10-19 16:04:54 +00:00
|
|
|
|
2015-11-20 13:15:49 +00:00
|
|
|
// Find suitable image URL loader
|
|
|
|
NSURLRequest *request = [RCTConvert NSURLRequest:imageTag];
|
|
|
|
id<RCTImageURLLoader> loadHandler = [strongSelf imageURLLoaderForURL:request.URL];
|
|
|
|
if (loadHandler) {
|
|
|
|
cancelLoad = [loadHandler loadImageForURL:request.URL
|
|
|
|
size:size
|
|
|
|
scale:scale
|
|
|
|
resizeMode:resizeMode
|
|
|
|
progressHandler:progressHandler
|
|
|
|
completionHandler:completionHandler] ?: ^{};
|
|
|
|
return;
|
|
|
|
}
|
2015-11-17 15:18:55 +00:00
|
|
|
|
2015-11-20 13:15:49 +00:00
|
|
|
// Check if networking module is available
|
|
|
|
if (RCT_DEBUG && ![_bridge respondsToSelector:@selector(networking)]) {
|
|
|
|
RCTLogError(@"No suitable image URL loader found for %@. You may need to "
|
|
|
|
" import the RCTNetworking library in order to load images.",
|
|
|
|
imageTag);
|
2015-11-17 15:18:55 +00:00
|
|
|
return;
|
2015-11-20 13:15:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Check if networking module can load image
|
|
|
|
if (RCT_DEBUG && ![_bridge.networking canHandleRequest:request]) {
|
|
|
|
RCTLogError(@"No suitable image URL loader found for %@", imageTag);
|
2015-11-17 15:18:55 +00:00
|
|
|
return;
|
|
|
|
}
|
2015-11-05 17:06:53 +00:00
|
|
|
|
2015-11-20 13:15:49 +00:00
|
|
|
// Use networking module to load image
|
|
|
|
RCTURLRequestCompletionBlock processResponse =
|
|
|
|
^(NSURLResponse *response, NSData *data, NSError *error) {
|
|
|
|
|
|
|
|
// Check for system errors
|
|
|
|
if (error) {
|
|
|
|
completionHandler(error, nil);
|
|
|
|
return;
|
|
|
|
} else if (!data) {
|
|
|
|
completionHandler(RCTErrorWithMessage(@"Unknown image download error"), nil);
|
2015-11-05 17:06:53 +00:00
|
|
|
return;
|
|
|
|
}
|
2015-11-20 13:15:49 +00:00
|
|
|
|
|
|
|
// Check for http errors
|
|
|
|
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
|
|
|
|
NSInteger statusCode = ((NSHTTPURLResponse *)response).statusCode;
|
|
|
|
if (statusCode != 200) {
|
|
|
|
completionHandler([[NSError alloc] initWithDomain:NSURLErrorDomain
|
|
|
|
code:statusCode
|
|
|
|
userInfo:nil], nil);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
Added getImageSize method
Summary:
public
This diff adds a `getSize()` method to `Image` to retrieve the width and height of an image prior to displaying it. This is useful when working with images from uncontrolled sources, and has been a much-requested feature.
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 fully supported way to preload images will be provided in a future diff.
The API (separate success and failure callbacks) is far from ideal, but until we agree on a unified standard, this was the most conventional way I could think of to implement it. If it returned a promise or something similar, it would be unique among all such APIS in the framework.
Please note that this has been a long time coming, in part due to much bikeshedding about what the API should look like, so while it's not unlikely that the API may change in future, I think having *some* way to do this is better than waiting until we can define the "perfect" way.
Reviewed By: vjeux
Differential Revision: D2797365
fb-gh-sync-id: 11eb1b8547773b1f8be0bc55ddf6dfedebf7fc0a
2016-01-01 02:50:26 +00:00
|
|
|
// Call handler
|
|
|
|
completionHandler(nil, data);
|
2015-11-20 13:15:49 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
// Add missing png extension
|
|
|
|
if (request.URL.fileURL && request.URL.pathExtension.length == 0) {
|
|
|
|
NSMutableURLRequest *mutableRequest = [request mutableCopy];
|
|
|
|
mutableRequest.URL = [NSURL fileURLWithPath:[request.URL.path stringByAppendingPathExtension:@"png"]];
|
|
|
|
request = mutableRequest;
|
2015-11-17 15:18:55 +00:00
|
|
|
}
|
2015-10-19 16:04:54 +00:00
|
|
|
|
2015-11-20 13:15:49 +00:00
|
|
|
// Check for cached response before reloading
|
|
|
|
// TODO: move URL cache out of RCTImageLoader into its own module
|
|
|
|
NSCachedURLResponse *cachedResponse = [_URLCache cachedResponseForRequest:request];
|
|
|
|
if (cachedResponse) {
|
|
|
|
processResponse(cachedResponse.response, cachedResponse.data, nil);
|
2015-11-23 12:18:20 +00:00
|
|
|
return;
|
2015-11-20 13:15:49 +00:00
|
|
|
}
|
2015-10-19 16:04:54 +00:00
|
|
|
|
2015-11-20 13:15:49 +00:00
|
|
|
// Download image
|
|
|
|
RCTNetworkTask *task = [_bridge.networking networkTaskWithRequest:request completionBlock:
|
|
|
|
^(NSURLResponse *response, NSData *data, NSError *error) {
|
|
|
|
if (error) {
|
|
|
|
completionHandler(error, nil);
|
|
|
|
return;
|
|
|
|
}
|
2015-10-19 16:04:54 +00:00
|
|
|
|
2015-11-20 13:15:49 +00:00
|
|
|
dispatch_async(_URLCacheQueue, ^{
|
2015-09-03 12:53:16 +00:00
|
|
|
|
2015-11-20 13:15:49 +00:00
|
|
|
// Cache the response
|
|
|
|
// TODO: move URL cache out of RCTImageLoader into its own module
|
|
|
|
BOOL isHTTPRequest = [request.URL.scheme hasPrefix:@"http"];
|
|
|
|
[strongSelf->_URLCache storeCachedResponse:
|
|
|
|
[[NSCachedURLResponse alloc] initWithResponse:response
|
|
|
|
data:data
|
|
|
|
userInfo:nil
|
|
|
|
storagePolicy:isHTTPRequest ? NSURLCacheStorageAllowed: NSURLCacheStorageAllowedInMemoryOnly]
|
|
|
|
forRequest:request];
|
|
|
|
// Process image data
|
|
|
|
processResponse(response, data, nil);
|
2015-11-17 15:18:55 +00:00
|
|
|
|
2015-11-20 13:15:49 +00:00
|
|
|
});
|
2015-10-19 16:04:54 +00:00
|
|
|
|
2015-11-20 13:15:49 +00:00
|
|
|
}];
|
|
|
|
task.downloadProgressBlock = progressHandler;
|
|
|
|
[task start];
|
|
|
|
|
|
|
|
cancelLoad = ^{
|
|
|
|
[task cancel];
|
|
|
|
};
|
|
|
|
|
|
|
|
});
|
2015-07-21 12:39:57 +00:00
|
|
|
|
2015-11-17 15:18:55 +00:00
|
|
|
return ^{
|
2015-11-20 13:15:49 +00:00
|
|
|
if (cancelLoad) {
|
|
|
|
cancelLoad();
|
2015-11-17 15:18:55 +00:00
|
|
|
}
|
|
|
|
OSAtomicOr32Barrier(1, &cancelled);
|
|
|
|
};
|
2015-09-02 15:25:10 +00:00
|
|
|
}
|
|
|
|
|
Added getImageSize method
Summary:
public
This diff adds a `getSize()` method to `Image` to retrieve the width and height of an image prior to displaying it. This is useful when working with images from uncontrolled sources, and has been a much-requested feature.
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 fully supported way to preload images will be provided in a future diff.
The API (separate success and failure callbacks) is far from ideal, but until we agree on a unified standard, this was the most conventional way I could think of to implement it. If it returned a promise or something similar, it would be unique among all such APIS in the framework.
Please note that this has been a long time coming, in part due to much bikeshedding about what the API should look like, so while it's not unlikely that the API may change in future, I think having *some* way to do this is better than waiting until we can define the "perfect" way.
Reviewed By: vjeux
Differential Revision: D2797365
fb-gh-sync-id: 11eb1b8547773b1f8be0bc55ddf6dfedebf7fc0a
2016-01-01 02:50:26 +00:00
|
|
|
- (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);
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2015-09-02 15:25:10 +00:00
|
|
|
- (RCTImageLoaderCancellationBlock)decodeImageData:(NSData *)data
|
|
|
|
size:(CGSize)size
|
|
|
|
scale:(CGFloat)scale
|
|
|
|
resizeMode:(UIViewContentMode)resizeMode
|
2015-10-20 12:00:50 +00:00
|
|
|
completionBlock:(RCTImageLoaderCompletionBlock)completionHandler
|
2015-09-02 15:25:10 +00:00
|
|
|
{
|
2015-11-17 15:18:55 +00:00
|
|
|
if (data.length == 0) {
|
|
|
|
completionHandler(RCTErrorWithMessage(@"No image data"), nil);
|
|
|
|
return ^{};
|
|
|
|
}
|
|
|
|
|
2015-10-19 16:04:54 +00:00
|
|
|
id<RCTImageDataDecoder> imageDecoder = [self imageDataDecoderForData:data];
|
2015-09-02 15:25:10 +00:00
|
|
|
if (imageDecoder) {
|
2015-10-20 12:00:50 +00:00
|
|
|
|
2015-10-19 16:04:54 +00:00
|
|
|
return [imageDecoder decodeImageData:data
|
|
|
|
size:size
|
|
|
|
scale:scale
|
|
|
|
resizeMode:resizeMode
|
2015-10-20 12:00:50 +00:00
|
|
|
completionHandler:completionHandler];
|
2015-09-02 15:25:10 +00:00
|
|
|
} else {
|
2015-10-20 12:00:50 +00:00
|
|
|
|
|
|
|
__block volatile uint32_t cancelled = 0;
|
2015-09-02 15:25:10 +00:00
|
|
|
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
2015-10-20 12:00:50 +00:00
|
|
|
if (cancelled) {
|
|
|
|
return;
|
|
|
|
}
|
2015-11-20 13:15:49 +00:00
|
|
|
UIImage *image = RCTDecodeImageWithData(data, size, scale, resizeMode);
|
2015-07-21 05:44:42 +00:00
|
|
|
if (image) {
|
2015-10-20 12:00:50 +00:00
|
|
|
completionHandler(nil, image);
|
2015-07-21 05:44:42 +00:00
|
|
|
} else {
|
2015-09-02 15:25:10 +00:00
|
|
|
NSString *errorMessage = [NSString stringWithFormat:@"Error decoding image data <NSData %p; %tu bytes>", data, data.length];
|
|
|
|
NSError *finalError = RCTErrorWithMessage(errorMessage);
|
2015-10-20 12:00:50 +00:00
|
|
|
completionHandler(finalError, nil);
|
2015-07-21 05:44:42 +00:00
|
|
|
}
|
2015-09-02 15:25:10 +00:00
|
|
|
});
|
2015-10-20 12:00:50 +00:00
|
|
|
|
|
|
|
return ^{
|
|
|
|
OSAtomicOr32Barrier(1, &cancelled);
|
|
|
|
};
|
2015-03-10 00:08:01 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
Added getImageSize method
Summary:
public
This diff adds a `getSize()` method to `Image` to retrieve the width and height of an image prior to displaying it. This is useful when working with images from uncontrolled sources, and has been a much-requested feature.
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 fully supported way to preload images will be provided in a future diff.
The API (separate success and failure callbacks) is far from ideal, but until we agree on a unified standard, this was the most conventional way I could think of to implement it. If it returned a promise or something similar, it would be unique among all such APIS in the framework.
Please note that this has been a long time coming, in part due to much bikeshedding about what the API should look like, so while it's not unlikely that the API may change in future, I think having *some* way to do this is better than waiting until we can define the "perfect" way.
Reviewed By: vjeux
Differential Revision: D2797365
fb-gh-sync-id: 11eb1b8547773b1f8be0bc55ddf6dfedebf7fc0a
2016-01-01 02:50:26 +00:00
|
|
|
- (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);
|
|
|
|
}];
|
|
|
|
}
|
|
|
|
|
2015-09-02 15:25:10 +00:00
|
|
|
#pragma mark - RCTURLRequestHandler
|
|
|
|
|
|
|
|
- (BOOL)canHandleRequest:(NSURLRequest *)request
|
2015-07-14 11:06:17 +00:00
|
|
|
{
|
2015-11-25 11:09:00 +00:00
|
|
|
return [self imageURLLoaderForURL:request.URL] != nil;
|
2015-07-15 20:17:13 +00:00
|
|
|
}
|
|
|
|
|
2015-09-02 15:25:10 +00:00
|
|
|
- (id)sendRequest:(NSURLRequest *)request withDelegate:(id<RCTURLRequestDelegate>)delegate
|
2015-07-15 20:17:13 +00:00
|
|
|
{
|
2015-09-02 15:25:10 +00:00
|
|
|
__block RCTImageLoaderCancellationBlock requestToken;
|
2015-10-19 16:04:54 +00:00
|
|
|
requestToken = [self loadImageWithTag:request.URL.absoluteString callback:^(NSError *error, UIImage *image) {
|
2015-09-02 15:25:10 +00:00
|
|
|
if (error) {
|
|
|
|
[delegate URLRequest:requestToken didCompleteWithError:error];
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
NSString *mimeType = nil;
|
|
|
|
NSData *imageData = nil;
|
|
|
|
if (RCTImageHasAlpha(image.CGImage)) {
|
|
|
|
mimeType = @"image/png";
|
|
|
|
imageData = UIImagePNGRepresentation(image);
|
|
|
|
} else {
|
|
|
|
mimeType = @"image/jpeg";
|
|
|
|
imageData = UIImageJPEGRepresentation(image, 1.0);
|
|
|
|
}
|
|
|
|
|
|
|
|
NSURLResponse *response = [[NSURLResponse alloc] initWithURL:request.URL
|
|
|
|
MIMEType:mimeType
|
|
|
|
expectedContentLength:imageData.length
|
|
|
|
textEncodingName:nil];
|
|
|
|
|
|
|
|
[delegate URLRequest:requestToken didReceiveResponse:response];
|
|
|
|
[delegate URLRequest:requestToken didReceiveData:imageData];
|
|
|
|
[delegate URLRequest:requestToken didCompleteWithError:nil];
|
|
|
|
}];
|
|
|
|
|
|
|
|
return requestToken;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)cancelRequest:(id)requestToken
|
|
|
|
{
|
|
|
|
if (requestToken) {
|
|
|
|
((RCTImageLoaderCancellationBlock)requestToken)();
|
|
|
|
}
|
2015-07-14 11:06:17 +00:00
|
|
|
}
|
|
|
|
|
2015-03-10 00:08:01 +00:00
|
|
|
@end
|
2015-07-27 15:48:31 +00:00
|
|
|
|
|
|
|
@implementation RCTBridge (RCTImageLoader)
|
|
|
|
|
|
|
|
- (RCTImageLoader *)imageLoader
|
|
|
|
{
|
2015-11-25 11:09:00 +00:00
|
|
|
return [self moduleForClass:[RCTImageLoader class]];
|
2015-07-27 15:48:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@end
|