RTCImageStoreManager uses NSData instead of UIImage
Summary: Hi, I'm currently building an app that changes metadata, does some resizes, maybe watermarking ...etc. I want to use RCTImageStoreManager to store the original image in memory and allow me to command different modifications from javascript as it gives me more flexibility. As RCTImageEditingManager does for example. But currently the RTCImageStoreManager uses UIImage to store the image, the problem is that UIImage losses metadata. So i suggest we change it to NSData. Additionally I added a method to remove an image from the store. A related PR can be found here https://github.com/lwansbrough/react-native-camera/pull/100. Closes https://github.com/facebook/react-native/pull/3290 Reviewed By: javache Differential Revision: D2647271 Pulled By: nicklockwood fb-gh-sync-id: e66353ae3005423beee72ec22189dcb117fc719f
This commit is contained in:
parent
83c9741dc9
commit
2b657003b7
|
@ -3,23 +3,34 @@
|
|||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import "RCTBridge.h"
|
||||
#import "RCTImageLoader.h"
|
||||
#import "RCTURLRequestHandler.h"
|
||||
|
||||
@interface RCTImageStoreManager : NSObject <RCTImageURLLoader>
|
||||
@interface RCTImageStoreManager : NSObject <RCTURLRequestHandler>
|
||||
|
||||
/**
|
||||
* Set and get cached images. These must be called from the main thread.
|
||||
* Set and get cached image data asynchronously. It is safe to call these from any
|
||||
* thread. The callbacks will be called on an unspecified thread.
|
||||
*/
|
||||
- (NSString *)storeImage:(UIImage *)image;
|
||||
- (UIImage *)imageForTag:(NSString *)imageTag;
|
||||
- (void)removeImageForTag:(NSString *)imageTag withBlock:(void (^)())block;
|
||||
- (void)storeImageData:(NSData *)imageData withBlock:(void (^)(NSString *imageTag))block;
|
||||
- (void)getImageDataForTag:(NSString *)imageTag withBlock:(void (^)(NSData *imageData))block;
|
||||
|
||||
/**
|
||||
* Set and get cached images asynchronously. It is safe to call these from any
|
||||
* thread. The callbacks will be called on the main thread.
|
||||
* Convenience method to store an image directly (image is converted to data
|
||||
* internally, so any metadata such as scale or orientation will be lost).
|
||||
*/
|
||||
- (void)storeImage:(UIImage *)image withBlock:(void (^)(NSString *imageTag))block;
|
||||
- (void)getImageForTag:(NSString *)imageTag withBlock:(void (^)(UIImage *image))block;
|
||||
|
||||
@end
|
||||
|
||||
@interface RCTImageStoreManager (Deprecated)
|
||||
|
||||
/**
|
||||
* These methods are deprecated - use the data-based alternatives instead.
|
||||
*/
|
||||
- (NSString *)storeImage:(UIImage *)image __deprecated;
|
||||
- (UIImage *)imageForTag:(NSString *)imageTag __deprecated;
|
||||
- (void)getImageForTag:(NSString *)imageTag withBlock:(void (^)(UIImage *image))block __deprecated;
|
||||
|
||||
@end
|
||||
|
||||
|
|
|
@ -9,12 +9,21 @@
|
|||
|
||||
#import "RCTImageStoreManager.h"
|
||||
|
||||
#import <ImageIO/ImageIO.h>
|
||||
#import <libkern/OSAtomic.h>
|
||||
#import <MobileCoreServices/UTType.h>
|
||||
|
||||
#import "RCTAssert.h"
|
||||
#import "RCTImageUtils.h"
|
||||
#import "RCTLog.h"
|
||||
#import "RCTUtils.h"
|
||||
|
||||
static NSString *const RCTImageStoreURLScheme = @"rct-image-store";
|
||||
|
||||
@implementation RCTImageStoreManager
|
||||
{
|
||||
NSMutableDictionary<NSString *, UIImage *> *_store;
|
||||
NSMutableDictionary<NSString *, NSData *> *_store;
|
||||
NSUInteger *_id;
|
||||
}
|
||||
|
||||
@synthesize methodQueue = _methodQueue;
|
||||
|
@ -24,61 +33,76 @@ RCT_EXPORT_MODULE()
|
|||
- (instancetype)init
|
||||
{
|
||||
if ((self = [super init])) {
|
||||
|
||||
// TODO: need a way to clear this store
|
||||
_store = [NSMutableDictionary new];
|
||||
_id = 0;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSString *)storeImage:(UIImage *)image
|
||||
- (void)removeImageForTag:(NSString *)imageTag withBlock:(void (^)())block
|
||||
{
|
||||
RCTAssertMainThread();
|
||||
NSString *tag = [NSString stringWithFormat:@"rct-image-store://%tu", _store.count];
|
||||
_store[tag] = image;
|
||||
return tag;
|
||||
dispatch_async(_methodQueue, ^{
|
||||
[self removeImageForTag:imageTag];
|
||||
if (block) {
|
||||
block();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
- (UIImage *)imageForTag:(NSString *)imageTag
|
||||
- (NSString *)_storeImageData:(NSData *)imageData
|
||||
{
|
||||
RCTAssertMainThread();
|
||||
return _store[imageTag];
|
||||
RCTAssertThread(_methodQueue, @"Must be called on RCTImageStoreManager thread");
|
||||
NSString *imageTag = [NSString stringWithFormat:@"%@://%tu", RCTImageStoreURLScheme, _id++];
|
||||
_store[imageTag] = imageData;
|
||||
return imageTag;
|
||||
}
|
||||
|
||||
- (void)storeImageData:(NSData *)imageData withBlock:(void (^)(NSString *imageTag))block
|
||||
{
|
||||
RCTAssertParam(block);
|
||||
dispatch_async(_methodQueue, ^{
|
||||
block([self _storeImageData:imageData]);
|
||||
});
|
||||
}
|
||||
|
||||
- (void)getImageDataForTag:(NSString *)imageTag withBlock:(void (^)(NSData *imageData))block
|
||||
{
|
||||
RCTAssertParam(block);
|
||||
dispatch_async(_methodQueue, ^{
|
||||
block(_store[imageTag]);
|
||||
});
|
||||
}
|
||||
|
||||
- (void)storeImage:(UIImage *)image withBlock:(void (^)(NSString *imageTag))block
|
||||
{
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
NSString *imageTag = [self storeImage:image];
|
||||
if (block) {
|
||||
RCTAssertParam(block);
|
||||
dispatch_async(_methodQueue, ^{
|
||||
NSString *imageTag = [self _storeImageData:RCTGetImageData(image.CGImage, 0.75)];
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
block(imageTag);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
- (void)getImageForTag:(NSString *)imageTag withBlock:(void (^)(UIImage *image))block
|
||||
RCT_EXPORT_METHOD(removeImageForTag:(NSString *)imageTag)
|
||||
{
|
||||
RCTAssert(block != nil, @"block must not be nil");
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
block([self imageForTag:imageTag]);
|
||||
});
|
||||
[_store removeObjectForKey:imageTag];
|
||||
}
|
||||
|
||||
// TODO (#5906496): Name could be more explicit - something like getBase64EncodedJPEGDataForTag:?
|
||||
// TODO (#5906496): Name could be more explicit - something like getBase64EncodedDataForTag:?
|
||||
RCT_EXPORT_METHOD(getBase64ForTag:(NSString *)imageTag
|
||||
successCallback:(RCTResponseSenderBlock)successCallback
|
||||
errorCallback:(RCTResponseErrorBlock)errorCallback)
|
||||
{
|
||||
[self getImageForTag:imageTag withBlock:^(UIImage *image) {
|
||||
if (!image) {
|
||||
errorCallback(RCTErrorWithMessage([NSString stringWithFormat:@"Invalid imageTag: %@", imageTag]));
|
||||
return;
|
||||
}
|
||||
dispatch_async(_methodQueue, ^{
|
||||
NSData *imageData = UIImageJPEGRepresentation(image, 1.0);
|
||||
NSString *base64 = [imageData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];
|
||||
successCallback(@[[base64 stringByReplacingOccurrencesOfString:@"\n" withString:@""]]);
|
||||
});
|
||||
}];
|
||||
NSData *imageData = _store[imageTag];
|
||||
if (!imageData) {
|
||||
errorCallback(RCTErrorWithMessage([NSString stringWithFormat:@"Invalid imageTag: %@", imageTag]));
|
||||
return;
|
||||
}
|
||||
// Dispatching to a background thread to perform base64 encoding
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
successCallback(@[[imageData base64EncodedStringWithOptions:0]]);
|
||||
});
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(addImageFromBase64:(NSString *)base64String
|
||||
|
@ -86,38 +110,114 @@ RCT_EXPORT_METHOD(addImageFromBase64:(NSString *)base64String
|
|||
errorCallback:(RCTResponseErrorBlock)errorCallback)
|
||||
|
||||
{
|
||||
NSData *imageData = [[NSData alloc] initWithBase64EncodedString:base64String options:0];
|
||||
if (imageData) {
|
||||
UIImage *image = [[UIImage alloc] initWithData:imageData];
|
||||
[self storeImage:image withBlock:^(NSString *imageTag) {
|
||||
successCallback(@[imageTag]);
|
||||
}];
|
||||
} else {
|
||||
errorCallback(RCTErrorWithMessage(@"Failed to add image from base64String"));
|
||||
// Dispatching to a background thread to perform base64 decoding
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
NSData *imageData = [[NSData alloc] initWithBase64EncodedString:base64String options:0];
|
||||
if (imageData) {
|
||||
dispatch_async(_methodQueue, ^{
|
||||
successCallback(@[[self _storeImageData:imageData]]);
|
||||
});
|
||||
} else {
|
||||
errorCallback(RCTErrorWithMessage(@"Failed to add image from base64String"));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#pragma mark - RCTURLRequestHandler
|
||||
|
||||
- (BOOL)canHandleRequest:(NSURLRequest *)request
|
||||
{
|
||||
return [request.URL.scheme caseInsensitiveCompare:RCTImageStoreURLScheme] == NSOrderedSame;
|
||||
}
|
||||
|
||||
- (id)sendRequest:(NSURLRequest *)request withDelegate:(id<RCTURLRequestDelegate>)delegate
|
||||
{
|
||||
__block volatile uint32_t cancelled = 0;
|
||||
void (^cancellationBlock)(void) = ^{
|
||||
OSAtomicOr32Barrier(1, &cancelled);
|
||||
};
|
||||
|
||||
// Dispatch async to give caller time to cancel the request
|
||||
dispatch_async(_methodQueue, ^{
|
||||
if (cancelled) {
|
||||
return;
|
||||
}
|
||||
|
||||
NSString *imageTag = request.URL.absoluteString;
|
||||
NSData *imageData = _store[imageTag];
|
||||
if (!imageData) {
|
||||
NSError *error = RCTErrorWithMessage([NSString stringWithFormat:@"Invalid imageTag: %@", imageTag]);
|
||||
[delegate URLRequest:cancellationBlock didCompleteWithError:error];
|
||||
return;
|
||||
}
|
||||
|
||||
CGImageSourceRef sourceRef = CGImageSourceCreateWithData((__bridge CFDataRef)imageData, NULL);
|
||||
if (!sourceRef) {
|
||||
NSError *error = RCTErrorWithMessage([NSString stringWithFormat:@"Unable to decode data for imageTag: %@", imageTag]);
|
||||
[delegate URLRequest:cancellationBlock didCompleteWithError:error];
|
||||
return;
|
||||
}
|
||||
CFStringRef UTI = CGImageSourceGetType(sourceRef);
|
||||
CFRelease(sourceRef);
|
||||
|
||||
NSString *MIMEType = (__bridge_transfer NSString *)UTTypeCopyPreferredTagWithClass(UTI, kUTTagClassMIMEType);
|
||||
NSURLResponse *response = [[NSURLResponse alloc] initWithURL:request.URL
|
||||
MIMEType:MIMEType
|
||||
expectedContentLength:imageData.length
|
||||
textEncodingName:nil];
|
||||
|
||||
[delegate URLRequest:cancellationBlock didReceiveResponse:response];
|
||||
[delegate URLRequest:cancellationBlock didReceiveData:imageData];
|
||||
[delegate URLRequest:cancellationBlock didCompleteWithError:nil];
|
||||
|
||||
});
|
||||
|
||||
return cancellationBlock;
|
||||
}
|
||||
|
||||
- (void)cancelRequest:(id)requestToken
|
||||
{
|
||||
if (requestToken) {
|
||||
((void (^)(void))requestToken)();
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - RCTImageLoader
|
||||
@end
|
||||
|
||||
- (BOOL)canLoadImageURL:(NSURL *)requestURL
|
||||
@implementation RCTImageStoreManager (Deprecated)
|
||||
|
||||
- (NSString *)storeImage:(UIImage *)image
|
||||
{
|
||||
return [requestURL.scheme.lowercaseString isEqualToString:@"rct-image-store"];
|
||||
RCTAssertMainThread();
|
||||
RCTLogWarn(@"RCTImageStoreManager.storeImage() is deprecated and has poor performance. Use an alternative method instead.");
|
||||
__block NSString *imageTag;
|
||||
dispatch_sync(_methodQueue, ^{
|
||||
imageTag = [self _storeImageData:RCTGetImageData(image.CGImage, 0.75)];
|
||||
});
|
||||
return imageTag;
|
||||
}
|
||||
|
||||
- (RCTImageLoaderCancellationBlock)loadImageForURL:(NSURL *)imageURL size:(CGSize)size scale:(CGFloat)scale resizeMode:(UIViewContentMode)resizeMode progressHandler:(RCTImageLoaderProgressBlock)progressHandler completionHandler:(RCTImageLoaderCompletionBlock)completionHandler
|
||||
- (UIImage *)imageForTag:(NSString *)imageTag
|
||||
{
|
||||
NSString *imageTag = imageURL.absoluteString;
|
||||
[self getImageForTag:imageTag withBlock:^(UIImage *image) {
|
||||
if (image) {
|
||||
completionHandler(nil, image);
|
||||
} else {
|
||||
NSString *errorMessage = [NSString stringWithFormat:@"Unable to load image from image store: %@", imageTag];
|
||||
NSError *error = RCTErrorWithMessage(errorMessage);
|
||||
completionHandler(error, nil);
|
||||
}
|
||||
}];
|
||||
RCTAssertMainThread();
|
||||
RCTLogWarn(@"RCTImageStoreManager.imageForTag() is deprecated and has poor performance. Use an alternative method instead.");
|
||||
__block NSData *imageData;
|
||||
dispatch_sync(_methodQueue, ^{
|
||||
imageData = _store[imageTag];
|
||||
});
|
||||
return [UIImage imageWithData:imageData];
|
||||
}
|
||||
|
||||
return nil;
|
||||
- (void)getImageForTag:(NSString *)imageTag withBlock:(void (^)(UIImage *image))block
|
||||
{
|
||||
RCTAssertParam(block);
|
||||
dispatch_async(_methodQueue, ^{
|
||||
NSData *imageData = _store[imageTag];
|
||||
UIImage *image = [UIImage imageWithData:imageData];
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
block(image);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -57,3 +57,12 @@ RCT_EXTERN UIImage *RCTDecodeImageWithData(NSData *data,
|
|||
CGSize destSize,
|
||||
CGFloat destScale,
|
||||
UIViewContentMode resizeMode);
|
||||
|
||||
/**
|
||||
* 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
|
||||
* JPEG. The `quality` argument controls the compression ratio of the JPEG
|
||||
* conversion, with 1.0 being maximum quality. It has no effect for images
|
||||
* using PNG compression.
|
||||
*/
|
||||
RCT_EXTERN NSData *RCTGetImageData(CGImageRef image, float quality);
|
||||
|
|
|
@ -10,9 +10,11 @@
|
|||
#import "RCTImageUtils.h"
|
||||
|
||||
#import <ImageIO/ImageIO.h>
|
||||
#import <MobileCoreServices/UTCoreTypes.h>
|
||||
#import <tgmath.h>
|
||||
|
||||
#import "RCTLog.h"
|
||||
#import "RCTUtils.h"
|
||||
|
||||
static CGFloat RCTCeilValue(CGFloat value, CGFloat scale)
|
||||
{
|
||||
|
@ -197,7 +199,7 @@ BOOL RCTUpscalingRequired(CGSize sourceSize, CGFloat sourceScale,
|
|||
}
|
||||
}
|
||||
|
||||
RCT_EXTERN CGSize RCTSizeInPixels(CGSize pointSize, CGFloat scale)
|
||||
CGSize RCTSizeInPixels(CGSize pointSize, CGFloat scale)
|
||||
{
|
||||
return (CGSize){
|
||||
ceil(pointSize.width * scale),
|
||||
|
@ -205,10 +207,10 @@ RCT_EXTERN CGSize RCTSizeInPixels(CGSize pointSize, CGFloat scale)
|
|||
};
|
||||
}
|
||||
|
||||
RCT_EXTERN UIImage *RCTDecodeImageWithData(NSData *data,
|
||||
CGSize destSize,
|
||||
CGFloat destScale,
|
||||
UIViewContentMode resizeMode)
|
||||
UIImage *RCTDecodeImageWithData(NSData *data,
|
||||
CGSize destSize,
|
||||
CGFloat destScale,
|
||||
UIViewContentMode resizeMode)
|
||||
{
|
||||
CGImageSourceRef sourceRef = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL);
|
||||
if (!sourceRef) {
|
||||
|
@ -251,10 +253,9 @@ RCT_EXTERN UIImage *RCTDecodeImageWithData(NSData *data,
|
|||
return nil;
|
||||
}
|
||||
|
||||
//adjust scale
|
||||
// adjust scale
|
||||
size_t actualWidth = CGImageGetWidth(imageRef);
|
||||
CGFloat scale = actualWidth / targetSize.width;
|
||||
|
||||
// return image
|
||||
UIImage *image = [UIImage imageWithCGImage:imageRef
|
||||
scale:scale
|
||||
|
@ -262,3 +263,26 @@ RCT_EXTERN UIImage *RCTDecodeImageWithData(NSData *data,
|
|||
CGImageRelease(imageRef);
|
||||
return image;
|
||||
}
|
||||
|
||||
NSData *RCTGetImageData(CGImageRef image, float quality)
|
||||
{
|
||||
NSDictionary *properties;
|
||||
CGImageDestinationRef destination;
|
||||
CFMutableDataRef imageData = CFDataCreateMutable(NULL, 0);
|
||||
if (RCTImageHasAlpha(image)) {
|
||||
// get png data
|
||||
destination = CGImageDestinationCreateWithData(imageData, kUTTypePNG, 1, NULL);
|
||||
} else {
|
||||
// get jpeg data
|
||||
destination = CGImageDestinationCreateWithData(imageData, kUTTypeJPEG, 1, NULL);
|
||||
properties = @{(NSString *)kCGImageDestinationLossyCompressionQuality: @(quality)};
|
||||
}
|
||||
CGImageDestinationAddImage(destination, image, (__bridge CFDictionaryRef)properties);
|
||||
if (!CGImageDestinationFinalize(destination))
|
||||
{
|
||||
CFRelease(imageData);
|
||||
imageData = NULL;
|
||||
}
|
||||
CFRelease(destination);
|
||||
return (__bridge_transfer NSData *)imageData;
|
||||
}
|
||||
|
|
|
@ -61,8 +61,9 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
|
|||
|
||||
- (void)cancel
|
||||
{
|
||||
if ([_handler respondsToSelector:@selector(cancelRequest:)]) {
|
||||
[_handler cancelRequest:_requestToken];
|
||||
__strong id strongToken = _requestToken;
|
||||
if (strongToken && [_handler respondsToSelector:@selector(cancelRequest:)]) {
|
||||
[_handler cancelRequest:strongToken];
|
||||
}
|
||||
[self invalidate];
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue