Generalized image decoding and resizing logic

Summary:
public

Standardises the image decoding logic for all image sources, meaning we get the benefits of efficient downscaling of images from all sources, not just ALAssets.

Reviewed By: javache

Differential Revision: D2647083

fb-gh-sync-id: e41456f838e4c6ab709b1c1523f651a86ff6e623
This commit is contained in:
Nick Lockwood
2016-01-20 11:03:22 -08:00
committed by facebook-github-bot-5
parent 9b87e6c860
commit 21fcbbc32c
30 changed files with 510 additions and 379 deletions

View File

@@ -46,6 +46,7 @@ var ImageCapInsetsExample = React.createClass({
<Image
source={require('image!story-background')}
style={styles.storyBackground}
resizeMode={Image.resizeMode.stretch}
capInsets={{left: 15, right: 15, bottom: 15, top: 15}}
/>
</View>

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0700"
version = "1.8">
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
@@ -51,10 +51,10 @@
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
buildConfiguration = "Debug">
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
skipped = "NO">
@@ -90,11 +90,11 @@
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
buildConfiguration = "Debug"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
@@ -113,10 +113,10 @@
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
buildConfiguration = "Release"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">

View File

@@ -15,7 +15,7 @@
#import "RCTImageLoader.h"
typedef BOOL (^RCTImageURLLoaderCanLoadImageURLHandler)(NSURL *requestURL);
typedef RCTImageLoaderCancellationBlock (^RCTImageURLLoaderLoadImageURLHandler)(NSURL *imageURL, CGSize size, CGFloat scale, UIViewContentMode resizeMode, RCTImageLoaderProgressBlock progressHandler, RCTImageLoaderCompletionBlock completionHandler);
typedef RCTImageLoaderCancellationBlock (^RCTImageURLLoaderLoadImageURLHandler)(NSURL *imageURL, CGSize size, CGFloat scale, RCTResizeMode resizeMode, RCTImageLoaderProgressBlock progressHandler, RCTImageLoaderCompletionBlock completionHandler);
@interface RCTConcreteImageURLLoader : NSObject <RCTImageURLLoader>
@@ -26,7 +26,7 @@ typedef RCTImageLoaderCancellationBlock (^RCTImageURLLoaderLoadImageURLHandler)(
@end
typedef BOOL (^RCTImageDataDecoderCanDecodeImageDataHandler)(NSData *imageData);
typedef RCTImageLoaderCancellationBlock (^RCTImageDataDecoderDecodeImageDataHandler)(NSData *imageData, CGSize size, CGFloat scale, UIViewContentMode resizeMode, RCTImageLoaderCompletionBlock completionHandler);
typedef RCTImageLoaderCancellationBlock (^RCTImageDataDecoderDecodeImageDataHandler)(NSData *imageData, CGSize size, CGFloat scale, RCTResizeMode resizeMode, RCTImageLoaderCompletionBlock completionHandler);
@interface RCTConcreteImageDecoder : NSObject <RCTImageDataDecoder>

View File

@@ -47,7 +47,7 @@
return _canLoadImageURLHandler(requestURL);
}
- (RCTImageLoaderCancellationBlock)loadImageForURL:(NSURL *)imageURL size:(CGSize)size scale:(CGFloat)scale resizeMode:(UIViewContentMode)resizeMode progressHandler:(RCTImageLoaderProgressBlock)progressHandler completionHandler:(RCTImageLoaderCompletionBlock)completionHandler
- (RCTImageLoaderCancellationBlock)loadImageForURL:(NSURL *)imageURL size:(CGSize)size scale:(CGFloat)scale resizeMode:(RCTResizeMode)resizeMode progressHandler:(RCTImageLoaderProgressBlock)progressHandler completionHandler:(RCTImageLoaderCompletionBlock)completionHandler
{
return _loadImageURLHandler(imageURL, size, scale, resizeMode, progressHandler, completionHandler);
}
@@ -92,7 +92,7 @@
return _canDecodeImageDataHandler(imageData);
}
- (RCTImageLoaderCancellationBlock)decodeImageData:(NSData *)imageData size:(CGSize)size scale:(CGFloat)scale resizeMode:(UIViewContentMode)resizeMode completionHandler:(RCTImageLoaderCompletionBlock)completionHandler
- (RCTImageLoaderCancellationBlock)decodeImageData:(NSData *)imageData size:(CGSize)size scale:(CGFloat)scale resizeMode:(RCTResizeMode)resizeMode completionHandler:(RCTImageLoaderCompletionBlock)completionHandler
{
return _decodeImageDataHandler(imageData, size, scale, resizeMode, completionHandler);
}

View File

@@ -41,7 +41,7 @@ RCTDefineImageDecoder(RCTImageLoaderTestsDecoder2)
id<RCTImageURLLoader> loader = [[RCTImageLoaderTestsURLLoader1 alloc] initWithPriority:1.0 canLoadImageURLHandler:^BOOL(__unused NSURL *requestURL) {
return YES;
} loadImageURLHandler:^RCTImageLoaderCancellationBlock(__unused NSURL *imageURL, __unused CGSize size, __unused CGFloat scale, __unused UIViewContentMode resizeMode, RCTImageLoaderProgressBlock progressHandler, RCTImageLoaderCompletionBlock completionHandler) {
} loadImageURLHandler:^RCTImageLoaderCancellationBlock(__unused NSURL *imageURL, __unused CGSize size, __unused CGFloat scale, __unused RCTResizeMode resizeMode, RCTImageLoaderProgressBlock progressHandler, RCTImageLoaderCompletionBlock completionHandler) {
progressHandler(1, 1);
completionHandler(nil, image);
return nil;
@@ -50,7 +50,7 @@ RCTDefineImageDecoder(RCTImageLoaderTestsDecoder2)
RCTImageLoader *imageLoader = [RCTImageLoader new];
NS_VALID_UNTIL_END_OF_SCOPE RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil moduleProvider:^{ return @[loader, imageLoader]; } launchOptions:nil];
[imageLoader loadImageWithTag:@"http://facebook.github.io/react/img/logo_og.png" size:CGSizeMake(100, 100) scale:1.0 resizeMode:UIViewContentModeScaleAspectFit progressBlock:^(int64_t progress, int64_t total) {
[imageLoader loadImageWithTag:@"http://facebook.github.io/react/img/logo_og.png" size:CGSizeMake(100, 100) scale:1.0 resizeMode:RCTResizeModeContain progressBlock:^(int64_t progress, int64_t total) {
XCTAssertEqual(progress, 1);
XCTAssertEqual(total, 1);
} completionBlock:^(NSError *loadError, id loadedImage) {
@@ -65,7 +65,7 @@ RCTDefineImageDecoder(RCTImageLoaderTestsDecoder2)
id<RCTImageURLLoader> loader1 = [[RCTImageLoaderTestsURLLoader1 alloc] initWithPriority:1.0 canLoadImageURLHandler:^BOOL(__unused NSURL *requestURL) {
return YES;
} loadImageURLHandler:^RCTImageLoaderCancellationBlock(__unused NSURL *imageURL, __unused CGSize size, __unused CGFloat scale, __unused UIViewContentMode resizeMode, RCTImageLoaderProgressBlock progressHandler, RCTImageLoaderCompletionBlock completionHandler) {
} loadImageURLHandler:^RCTImageLoaderCancellationBlock(__unused NSURL *imageURL, __unused CGSize size, __unused CGFloat scale, __unused RCTResizeMode resizeMode, RCTImageLoaderProgressBlock progressHandler, RCTImageLoaderCompletionBlock completionHandler) {
progressHandler(1, 1);
completionHandler(nil, image);
return nil;
@@ -73,7 +73,7 @@ RCTDefineImageDecoder(RCTImageLoaderTestsDecoder2)
id<RCTImageURLLoader> loader2 = [[RCTImageLoaderTestsURLLoader2 alloc] initWithPriority:0.5 canLoadImageURLHandler:^BOOL(__unused NSURL *requestURL) {
return YES;
} loadImageURLHandler:^RCTImageLoaderCancellationBlock(__unused NSURL *imageURL, __unused CGSize size, __unused CGFloat scale, __unused UIViewContentMode resizeMode, __unused RCTImageLoaderProgressBlock progressHandler, __unused RCTImageLoaderCompletionBlock completionHandler) {
} loadImageURLHandler:^RCTImageLoaderCancellationBlock(__unused NSURL *imageURL, __unused CGSize size, __unused CGFloat scale, __unused RCTResizeMode resizeMode, __unused RCTImageLoaderProgressBlock progressHandler, __unused RCTImageLoaderCompletionBlock completionHandler) {
XCTFail(@"Should not have used loader2");
return nil;
}];
@@ -81,7 +81,7 @@ RCTDefineImageDecoder(RCTImageLoaderTestsDecoder2)
RCTImageLoader *imageLoader = [RCTImageLoader new];
NS_VALID_UNTIL_END_OF_SCOPE RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil moduleProvider:^{ return @[loader1, loader2, imageLoader]; } launchOptions:nil];
[imageLoader loadImageWithTag:@"http://facebook.github.io/react/img/logo_og.png" size:CGSizeMake(100, 100) scale:1.0 resizeMode:UIViewContentModeScaleAspectFit progressBlock:^(int64_t progress, int64_t total) {
[imageLoader loadImageWithTag:@"http://facebook.github.io/react/img/logo_og.png" size:CGSizeMake(100, 100) scale:1.0 resizeMode:RCTResizeModeContain progressBlock:^(int64_t progress, int64_t total) {
XCTAssertEqual(progress, 1);
XCTAssertEqual(total, 1);
} completionBlock:^(NSError *loadError, id loadedImage) {
@@ -97,7 +97,7 @@ RCTDefineImageDecoder(RCTImageLoaderTestsDecoder2)
id<RCTImageDataDecoder> decoder = [[RCTImageLoaderTestsDecoder1 alloc] initWithPriority:1.0 canDecodeImageDataHandler:^BOOL(__unused NSData *imageData) {
return YES;
} decodeImageDataHandler:^RCTImageLoaderCancellationBlock(NSData *imageData, __unused CGSize size, __unused CGFloat scale, __unused UIViewContentMode resizeMode, RCTImageLoaderCompletionBlock completionHandler) {
} decodeImageDataHandler:^RCTImageLoaderCancellationBlock(NSData *imageData, __unused CGSize size, __unused CGFloat scale, __unused RCTResizeMode resizeMode, RCTImageLoaderCompletionBlock completionHandler) {
XCTAssertEqualObjects(imageData, data);
completionHandler(nil, image);
return nil;
@@ -106,7 +106,7 @@ RCTDefineImageDecoder(RCTImageLoaderTestsDecoder2)
RCTImageLoader *imageLoader = [RCTImageLoader new];
NS_VALID_UNTIL_END_OF_SCOPE RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil moduleProvider:^{ return @[decoder, imageLoader]; } launchOptions:nil];
RCTImageLoaderCancellationBlock cancelBlock = [imageLoader decodeImageData:data size:CGSizeMake(1, 1) scale:1.0 resizeMode:UIViewContentModeScaleToFill completionBlock:^(NSError *decodeError, id decodedImage) {
RCTImageLoaderCancellationBlock cancelBlock = [imageLoader decodeImageData:data size:CGSizeMake(1, 1) scale:1.0 resizeMode:RCTResizeModeStretch completionBlock:^(NSError *decodeError, id decodedImage) {
XCTAssertEqualObjects(decodedImage, image);
XCTAssertNil(decodeError);
}];
@@ -120,7 +120,7 @@ RCTDefineImageDecoder(RCTImageLoaderTestsDecoder2)
id<RCTImageDataDecoder> decoder1 = [[RCTImageLoaderTestsDecoder1 alloc] initWithPriority:1.0 canDecodeImageDataHandler:^BOOL(__unused NSData *imageData) {
return YES;
} decodeImageDataHandler:^RCTImageLoaderCancellationBlock(NSData *imageData, __unused CGSize size, __unused CGFloat scale, __unused UIViewContentMode resizeMode, RCTImageLoaderCompletionBlock completionHandler) {
} decodeImageDataHandler:^RCTImageLoaderCancellationBlock(NSData *imageData, __unused CGSize size, __unused CGFloat scale, __unused RCTResizeMode resizeMode, RCTImageLoaderCompletionBlock completionHandler) {
XCTAssertEqualObjects(imageData, data);
completionHandler(nil, image);
return nil;
@@ -128,7 +128,7 @@ RCTDefineImageDecoder(RCTImageLoaderTestsDecoder2)
id<RCTImageDataDecoder> decoder2 = [[RCTImageLoaderTestsDecoder2 alloc] initWithPriority:0.5 canDecodeImageDataHandler:^BOOL(__unused NSData *imageData) {
return YES;
} decodeImageDataHandler:^RCTImageLoaderCancellationBlock(__unused NSData *imageData, __unused CGSize size, __unused CGFloat scale, __unused UIViewContentMode resizeMode, __unused RCTImageLoaderCompletionBlock completionHandler) {
} decodeImageDataHandler:^RCTImageLoaderCancellationBlock(__unused NSData *imageData, __unused CGSize size, __unused CGFloat scale, __unused RCTResizeMode resizeMode, __unused RCTImageLoaderCompletionBlock completionHandler) {
XCTFail(@"Should not have used decoder2");
return nil;
}];
@@ -136,7 +136,7 @@ RCTDefineImageDecoder(RCTImageLoaderTestsDecoder2)
RCTImageLoader *imageLoader = [RCTImageLoader new];
NS_VALID_UNTIL_END_OF_SCOPE RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil moduleProvider:^{ return @[decoder1, decoder2, imageLoader]; } launchOptions:nil];
RCTImageLoaderCancellationBlock cancelBlock = [imageLoader decodeImageData:data size:CGSizeMake(1, 1) scale:1.0 resizeMode:UIViewContentModeScaleToFill completionBlock:^(NSError *decodeError, id decodedImage) {
RCTImageLoaderCancellationBlock cancelBlock = [imageLoader decodeImageData:data size:CGSizeMake(1, 1) scale:1.0 resizeMode:RCTResizeModeStretch completionBlock:^(NSError *decodeError, id decodedImage) {
XCTAssertEqualObjects(decodedImage, image);
XCTAssertNil(decodeError);
}];

View File

@@ -46,19 +46,19 @@ RCTAssertEqualSizes(a.size, b.size); \
{
CGRect expected = {CGPointZero, {100, 20}};
CGRect result = RCTTargetRect(content, target, 1, UIViewContentModeScaleToFill);
CGRect result = RCTTargetRect(content, target, 1, RCTResizeModeStretch);
RCTAssertEqualRects(expected, result);
}
{
CGRect expected = {CGPointZero, {100, 10}};
CGRect result = RCTTargetRect(content, target, 1, UIViewContentModeScaleAspectFit);
CGRect expected = {{0, 5}, {100, 10}};
CGRect result = RCTTargetRect(content, target, 1, RCTResizeModeContain);
RCTAssertEqualRects(expected, result);
}
{
CGRect expected = {{-50, 0}, {200, 20}};
CGRect result = RCTTargetRect(content, target, 1, UIViewContentModeScaleAspectFill);
CGRect result = RCTTargetRect(content, target, 1, RCTResizeModeCover);
RCTAssertEqualRects(expected, result);
}
}
@@ -70,19 +70,19 @@ RCTAssertEqualSizes(a.size, b.size); \
{
CGRect expected = {CGPointZero, {100, 20}};
CGRect result = RCTTargetRect(content, target, 1, UIViewContentModeScaleToFill);
CGRect result = RCTTargetRect(content, target, 1, RCTResizeModeStretch);
RCTAssertEqualRects(expected, result);
}
{
CGRect expected = {CGPointZero, {2, 20}};
CGRect result = RCTTargetRect(content, target, 1, UIViewContentModeScaleAspectFit);
CGRect expected = {{49, 0}, {2, 20}};
CGRect result = RCTTargetRect(content, target, 1, RCTResizeModeContain);
RCTAssertEqualRects(expected, result);
}
{
CGRect expected = {{0, -490}, {100, 1000}};
CGRect result = RCTTargetRect(content, target, 1, UIViewContentModeScaleAspectFill);
CGRect result = RCTTargetRect(content, target, 1, RCTResizeModeCover);
RCTAssertEqualRects(expected, result);
}
}
@@ -94,19 +94,19 @@ RCTAssertEqualSizes(a.size, b.size); \
{
CGRect expected = {CGPointZero, {20, 50}};
CGRect result = RCTTargetRect(content, target, 1, UIViewContentModeScaleToFill);
CGRect result = RCTTargetRect(content, target, 1, RCTResizeModeStretch);
RCTAssertEqualRects(expected, result);
}
{
CGRect expected = {CGPointZero, {5, 50}};
CGRect result = RCTTargetRect(content, target, 1, UIViewContentModeScaleAspectFit);
CGRect expected = {{7,0}, {5, 50}};
CGRect result = RCTTargetRect(content, target, 1, RCTResizeModeContain);
RCTAssertEqualRects(expected, result);
}
{
CGRect expected = {{0, -75}, {20, 200}};
CGRect result = RCTTargetRect(content, target, 2, UIViewContentModeScaleAspectFill);
CGRect result = RCTTargetRect(content, target, 2, RCTResizeModeCover);
RCTAssertEqualRects(expected, result);
}
}
@@ -118,7 +118,7 @@ RCTAssertEqualSizes(a.size, b.size); \
{
CGRect expected = {{0, -75}, {20, 200}};
CGRect result = RCTTargetRect(content, target, 1, UIViewContentModeScaleAspectFill);
CGRect result = RCTTargetRect(content, target, 1, RCTResizeModeCover);
RCTAssertEqualRects(expected, result);
}
}
@@ -129,7 +129,7 @@ RCTAssertEqualSizes(a.size, b.size); \
CGSize target = {3, 3};
CGRect expected = {CGPointZero, {3, 3}};
CGRect result = RCTTargetRect(content, target, 1, UIViewContentModeScaleToFill);
CGRect result = RCTTargetRect(content, target, 1, RCTResizeModeStretch);
RCTAssertEqualRects(expected, result);
}

View File

@@ -1,168 +0,0 @@
/**
* 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.
*/
#import "RCTAssetsLibraryImageLoader.h"
#import <AssetsLibrary/AssetsLibrary.h>
#import <ImageIO/ImageIO.h>
#import <libkern/OSAtomic.h>
#import <UIKit/UIKit.h>
#import "RCTBridge.h"
#import "RCTConvert.h"
#import "RCTImageLoader.h"
#import "RCTImageUtils.h"
#import "RCTUtils.h"
static dispatch_queue_t RCTAssetsLibraryImageLoaderQueue(void);
@implementation RCTAssetsLibraryImageLoader
{
ALAssetsLibrary *_assetsLibrary;
}
RCT_EXPORT_MODULE()
@synthesize bridge = _bridge;
- (ALAssetsLibrary *)assetsLibrary
{
return _assetsLibrary ?: (_assetsLibrary = [ALAssetsLibrary new]);
}
#pragma mark - RCTImageLoader
- (BOOL)canLoadImageURL:(NSURL *)requestURL
{
return [requestURL.scheme caseInsensitiveCompare:@"assets-library"] == NSOrderedSame;
}
- (RCTImageLoaderCancellationBlock)loadImageForURL:(NSURL *)imageURL
size:(CGSize)size
scale:(CGFloat)scale
resizeMode:(UIViewContentMode)resizeMode
progressHandler:(RCTImageLoaderProgressBlock)progressHandler
completionHandler:(RCTImageLoaderCompletionBlock)completionHandler
{
__block volatile uint32_t cancelled = 0;
[[self assetsLibrary] assetForURL:imageURL resultBlock:^(ALAsset *asset) {
if (cancelled) {
return;
}
if (asset) {
// ALAssetLibrary API is async and will be multi-threaded. Loading a few full
// resolution images at once will spike the memory up to store the image data,
// and might trigger memory warnings and/or OOM crashes.
// To improve this, process the loaded asset in a serial queue.
dispatch_async(RCTAssetsLibraryImageLoaderQueue(), ^{
if (cancelled) {
return;
}
// Also make sure the image is released immediately after it's used so it
// doesn't spike the memory up during the process.
@autoreleasepool {
BOOL useMaximumSize = CGSizeEqualToSize(size, CGSizeZero);
ALAssetRepresentation *representation = [asset defaultRepresentation];
#if RCT_DEV
CGSize sizeBeingLoaded = size;
if (useMaximumSize) {
CGSize pointSize = representation.dimensions;
sizeBeingLoaded = CGSizeMake(pointSize.width * representation.scale, pointSize.height * representation.scale);
}
CGSize screenSize;
if ([[[UIDevice currentDevice] systemVersion] compare:@"8.0" options:NSNumericSearch] == NSOrderedDescending) {
screenSize = [UIScreen mainScreen].nativeBounds.size;
} else {
CGSize mainScreenSize = [UIScreen mainScreen].bounds.size;
CGFloat mainScreenScale = [[UIScreen mainScreen] scale];
screenSize = CGSizeMake(mainScreenSize.width * mainScreenScale, mainScreenSize.height * mainScreenScale);
}
CGFloat maximumPixelDimension = fmax(screenSize.width, screenSize.height);
if (sizeBeingLoaded.width > maximumPixelDimension || sizeBeingLoaded.height > maximumPixelDimension) {
RCTLogInfo(@"[PERF ASSETS] Loading %@ at size %@, which is larger than screen size %@",
representation.filename, NSStringFromCGSize(sizeBeingLoaded), NSStringFromCGSize(screenSize));
}
#endif
UIImage *image = nil;
NSError *error = nil;
if (useMaximumSize) {
image = [UIImage imageWithCGImage:representation.fullResolutionImage
scale:scale
orientation:(UIImageOrientation)representation.orientation];
} else {
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);
}
});
} else {
NSString *errorText = [NSString stringWithFormat:@"Failed to load asset at URL %@ with no error message.", imageURL];
completionHandler(RCTErrorWithMessage(errorText), nil);
}
} failureBlock:^(NSError *loadError) {
if (cancelled) {
return;
}
NSString *errorText = [NSString stringWithFormat:@"Failed to load asset at URL %@.\niOS Error: %@", imageURL, loadError];
completionHandler(RCTErrorWithMessage(errorText), nil);
}];
return ^{
OSAtomicOr32Barrier(1, &cancelled);
};
}
@end
@implementation RCTBridge (RCTAssetsLibraryImageLoader)
- (ALAssetsLibrary *)assetsLibrary
{
return [[self moduleForClass:[RCTAssetsLibraryImageLoader class]] assetsLibrary];
}
@end
static dispatch_queue_t RCTAssetsLibraryImageLoaderQueue(void)
{
static dispatch_queue_t queue;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
queue = dispatch_queue_create("com.facebook.RCTAssetsLibraryImageLoader", DISPATCH_QUEUE_SERIAL);
});
return queue;
}

View File

@@ -7,9 +7,12 @@
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "RCTImageLoader.h"
#import "RCTBridge.h"
#import "RCTURLRequestHandler.h"
@interface RCTAssetsLibraryImageLoader : NSObject <RCTImageURLLoader>
@class ALAssetsLibrary;
@interface RCTAssetsLibraryRequestHandler : NSObject <RCTURLRequestHandler>
@end

View File

@@ -0,0 +1,114 @@
/**
* 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.
*/
#import "RCTAssetsLibraryRequestHandler.h"
#import <AssetsLibrary/AssetsLibrary.h>
#import <libkern/OSAtomic.h>
#import "RCTBridge.h"
#import "RCTUtils.h"
@implementation RCTAssetsLibraryRequestHandler
{
ALAssetsLibrary *_assetsLibrary;
}
RCT_EXPORT_MODULE()
@synthesize bridge = _bridge;
- (ALAssetsLibrary *)assetsLibrary
{
return _assetsLibrary ?: (_assetsLibrary = [ALAssetsLibrary new]);
}
#pragma mark - RCTURLRequestHandler
- (BOOL)canHandleRequest:(NSURLRequest *)request
{
return [request.URL.scheme caseInsensitiveCompare:@"assets-library"] == NSOrderedSame;
}
- (id)sendRequest:(NSURLRequest *)request
withDelegate:(id<RCTURLRequestDelegate>)delegate
{
__block volatile uint32_t cancelled = 0;
void (^cancellationBlock)(void) = ^{
OSAtomicOr32Barrier(1, &cancelled);
};
[[self assetsLibrary] assetForURL:request.URL resultBlock:^(ALAsset *asset) {
if (cancelled) {
return;
}
if (asset) {
ALAssetRepresentation *representation = [asset defaultRepresentation];
NSInteger length = (NSInteger)representation.size;
NSURLResponse *response =
[[NSURLResponse alloc] initWithURL:request.URL
MIMEType:representation.UTI
expectedContentLength:length
textEncodingName:nil];
[delegate URLRequest:cancellationBlock didReceiveResponse:response];
NSError *error = nil;
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];
[delegate URLRequest:cancellationBlock didReceiveData:data];
[delegate URLRequest:cancellationBlock didCompleteWithError:nil];
} else {
free(buffer);
[delegate URLRequest:cancellationBlock didCompleteWithError:error];
}
} else {
NSString *errorMessage = [NSString stringWithFormat:@"Failed to load asset"
" at URL %@ with no error message.", request.URL];
NSError *error = RCTErrorWithMessage(errorMessage);
[delegate URLRequest:cancellationBlock didCompleteWithError:error];
}
} failureBlock:^(NSError *loadError) {
if (cancelled) {
return;
}
[delegate URLRequest:cancellationBlock didCompleteWithError:loadError];
}];
return cancellationBlock;
}
- (void)cancelRequest:(id)requestToken
{
((void (^)(void))requestToken)();
}
@end
@implementation RCTBridge (RCTAssetsLibraryImageLoader)
- (ALAssetsLibrary *)assetsLibrary
{
return [[self moduleForClass:[RCTAssetsLibraryRequestHandler class]] assetsLibrary];
}
@end

View File

@@ -9,7 +9,7 @@
/* Begin PBXBuildFile section */
137620351B31C53500677FF0 /* RCTImagePickerManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 137620341B31C53500677FF0 /* RCTImagePickerManager.m */; };
143879351AAD238D00F088A5 /* RCTCameraRollManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 143879341AAD238D00F088A5 /* RCTCameraRollManager.m */; };
8312EAEE1B85EB7C001867A2 /* RCTAssetsLibraryImageLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 8312EAED1B85EB7C001867A2 /* RCTAssetsLibraryImageLoader.m */; };
8312EAEE1B85EB7C001867A2 /* RCTAssetsLibraryRequestHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 8312EAED1B85EB7C001867A2 /* RCTAssetsLibraryRequestHandler.m */; };
8312EAF11B85F071001867A2 /* RCTPhotoLibraryImageLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 8312EAF01B85F071001867A2 /* RCTPhotoLibraryImageLoader.m */; };
/* End PBXBuildFile section */
@@ -31,8 +31,8 @@
143879331AAD238D00F088A5 /* RCTCameraRollManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTCameraRollManager.h; sourceTree = "<group>"; };
143879341AAD238D00F088A5 /* RCTCameraRollManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTCameraRollManager.m; sourceTree = "<group>"; };
58B5115D1A9E6B3D00147676 /* libRCTCameraRoll.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRCTCameraRoll.a; sourceTree = BUILT_PRODUCTS_DIR; };
8312EAEC1B85EB7C001867A2 /* RCTAssetsLibraryImageLoader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTAssetsLibraryImageLoader.h; sourceTree = "<group>"; };
8312EAED1B85EB7C001867A2 /* RCTAssetsLibraryImageLoader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTAssetsLibraryImageLoader.m; sourceTree = "<group>"; };
8312EAEC1B85EB7C001867A2 /* RCTAssetsLibraryRequestHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTAssetsLibraryRequestHandler.h; sourceTree = "<group>"; };
8312EAED1B85EB7C001867A2 /* RCTAssetsLibraryRequestHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTAssetsLibraryRequestHandler.m; sourceTree = "<group>"; };
8312EAEF1B85F071001867A2 /* RCTPhotoLibraryImageLoader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTPhotoLibraryImageLoader.h; sourceTree = "<group>"; };
8312EAF01B85F071001867A2 /* RCTPhotoLibraryImageLoader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTPhotoLibraryImageLoader.m; sourceTree = "<group>"; };
/* End PBXFileReference section */
@@ -51,8 +51,8 @@
58B511541A9E6B3D00147676 = {
isa = PBXGroup;
children = (
8312EAEC1B85EB7C001867A2 /* RCTAssetsLibraryImageLoader.h */,
8312EAED1B85EB7C001867A2 /* RCTAssetsLibraryImageLoader.m */,
8312EAEC1B85EB7C001867A2 /* RCTAssetsLibraryRequestHandler.h */,
8312EAED1B85EB7C001867A2 /* RCTAssetsLibraryRequestHandler.m */,
143879331AAD238D00F088A5 /* RCTCameraRollManager.h */,
143879341AAD238D00F088A5 /* RCTCameraRollManager.m */,
137620331B31C53500677FF0 /* RCTImagePickerManager.h */,
@@ -129,7 +129,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
8312EAEE1B85EB7C001867A2 /* RCTAssetsLibraryImageLoader.m in Sources */,
8312EAEE1B85EB7C001867A2 /* RCTAssetsLibraryRequestHandler.m in Sources */,
8312EAF11B85F071001867A2 /* RCTPhotoLibraryImageLoader.m in Sources */,
137620351B31C53500677FF0 /* RCTImagePickerManager.m in Sources */,
143879351AAD238D00F088A5 /* RCTCameraRollManager.m in Sources */,

View File

@@ -13,7 +13,7 @@
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import "RCTAssetsLibraryImageLoader.h"
#import "RCTAssetsLibraryRequestHandler.h"
#import "RCTBridge.h"
#import "RCTConvert.h"
#import "RCTImageLoader.h"

View File

@@ -30,7 +30,7 @@ RCT_EXPORT_MODULE()
- (RCTImageLoaderCancellationBlock)loadImageForURL:(NSURL *)imageURL
size:(CGSize)size
scale:(CGFloat)scale
resizeMode:(UIViewContentMode)resizeMode
resizeMode:(RCTResizeMode)resizeMode
progressHandler:(RCTImageLoaderProgressBlock)progressHandler
completionHandler:(RCTImageLoaderCompletionBlock)completionHandler
{
@@ -71,7 +71,7 @@ RCT_EXPORT_MODULE()
}
PHImageContentMode contentMode = PHImageContentModeAspectFill;
if (resizeMode == UIViewContentModeScaleAspectFit) {
if (resizeMode == RCTResizeModeContain) {
contentMode = PHImageContentModeAspectFit;
}

View File

@@ -30,7 +30,7 @@ RCT_EXPORT_MODULE()
- (RCTImageLoaderCancellationBlock)decodeImageData:(NSData *)imageData
size:(CGSize)size
scale:(CGFloat)scale
resizeMode:(UIViewContentMode)resizeMode
resizeMode:(RCTResizeMode)resizeMode
completionHandler:(RCTImageLoaderCompletionBlock)completionHandler
{
CGImageSourceRef imageSource = CGImageSourceCreateWithData((CFDataRef)imageData, NULL);

View File

@@ -11,9 +11,10 @@
1304D5AC1AA8C4A30002E2BE /* RCTImageViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 1304D5AA1AA8C4A30002E2BE /* RCTImageViewManager.m */; };
1304D5B21AA8C50D0002E2BE /* RCTGIFImageDecoder.m in Sources */ = {isa = PBXBuildFile; fileRef = 1304D5B11AA8C50D0002E2BE /* RCTGIFImageDecoder.m */; };
134B00A21B54232B00EC8DFB /* RCTImageUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 134B00A11B54232B00EC8DFB /* RCTImageUtils.m */; };
13EF7F0B1BC42D4E003F47DD /* RCTShadowVirtualImage.m in Sources */ = {isa = PBXBuildFile; fileRef = 13EF7F081BC42D4E003F47DD /* RCTShadowVirtualImage.m */; settings = {ASSET_TAGS = (); }; };
13EF7F0C1BC42D4E003F47DD /* RCTVirtualImageManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 13EF7F0A1BC42D4E003F47DD /* RCTVirtualImageManager.m */; settings = {ASSET_TAGS = (); }; };
13EF7F7F1BC825B1003F47DD /* RCTXCAssetImageLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 13EF7F7E1BC825B1003F47DD /* RCTXCAssetImageLoader.m */; settings = {ASSET_TAGS = (); }; };
139A38841C4D587C00862840 /* RCTResizeMode.m in Sources */ = {isa = PBXBuildFile; fileRef = 139A38831C4D587C00862840 /* RCTResizeMode.m */; };
13EF7F0B1BC42D4E003F47DD /* RCTShadowVirtualImage.m in Sources */ = {isa = PBXBuildFile; fileRef = 13EF7F081BC42D4E003F47DD /* RCTShadowVirtualImage.m */; };
13EF7F0C1BC42D4E003F47DD /* RCTVirtualImageManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 13EF7F0A1BC42D4E003F47DD /* RCTVirtualImageManager.m */; };
13EF7F7F1BC825B1003F47DD /* RCTXCAssetImageLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 13EF7F7E1BC825B1003F47DD /* RCTXCAssetImageLoader.m */; };
143879381AAD32A300F088A5 /* RCTImageLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 143879371AAD32A300F088A5 /* RCTImageLoader.m */; };
35123E6B1B59C99D00EBAD80 /* RCTImageStoreManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 35123E6A1B59C99D00EBAD80 /* RCTImageStoreManager.m */; };
354631681B69857700AA0B86 /* RCTImageEditingManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 354631671B69857700AA0B86 /* RCTImageEditingManager.m */; };
@@ -40,6 +41,8 @@
1304D5B11AA8C50D0002E2BE /* RCTGIFImageDecoder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTGIFImageDecoder.m; sourceTree = "<group>"; };
134B00A01B54232B00EC8DFB /* RCTImageUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTImageUtils.h; sourceTree = "<group>"; };
134B00A11B54232B00EC8DFB /* RCTImageUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImageUtils.m; sourceTree = "<group>"; };
139A38821C4D57AD00862840 /* RCTResizeMode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTResizeMode.h; sourceTree = "<group>"; };
139A38831C4D587C00862840 /* RCTResizeMode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTResizeMode.m; sourceTree = "<group>"; };
13EF7F071BC42D4E003F47DD /* RCTShadowVirtualImage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTShadowVirtualImage.h; sourceTree = "<group>"; };
13EF7F081BC42D4E003F47DD /* RCTShadowVirtualImage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTShadowVirtualImage.m; sourceTree = "<group>"; };
13EF7F091BC42D4E003F47DD /* RCTVirtualImageManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTVirtualImageManager.h; sourceTree = "<group>"; };
@@ -69,6 +72,8 @@
58B511541A9E6B3D00147676 = {
isa = PBXGroup;
children = (
139A38821C4D57AD00862840 /* RCTResizeMode.h */,
139A38831C4D587C00862840 /* RCTResizeMode.m */,
13EF7F7D1BC825B1003F47DD /* RCTXCAssetImageLoader.h */,
13EF7F7E1BC825B1003F47DD /* RCTXCAssetImageLoader.m */,
1304D5B01AA8C50D0002E2BE /* RCTGIFImageDecoder.h */,
@@ -165,6 +170,7 @@
1304D5B21AA8C50D0002E2BE /* RCTGIFImageDecoder.m in Sources */,
143879381AAD32A300F088A5 /* RCTImageLoader.m in Sources */,
354631681B69857700AA0B86 /* RCTImageEditingManager.m in Sources */,
139A38841C4D587C00862840 /* RCTResizeMode.m in Sources */,
1304D5AB1AA8C4A30002E2BE /* RCTImageView.m in Sources */,
13EF7F0B1BC42D4E003F47DD /* RCTShadowVirtualImage.m in Sources */,
13EF7F7F1BC825B1003F47DD /* RCTXCAssetImageLoader.m in Sources */,

View File

@@ -14,6 +14,7 @@
#import "RCTConvert.h"
#import "RCTLog.h"
#import "RCTUtils.h"
#import "RCTImageUtils.h"
#import "RCTImageStoreManager.h"
#import "RCTImageLoader.h"
@@ -44,8 +45,6 @@ RCT_EXPORT_METHOD(cropImage:(NSString *)imageTag
[RCTConvert CGSize:cropData[@"size"]]
};
NSString *resizeMode = cropData[@"resizeMode"] ?: @"contain";
[_bridge.imageLoader loadImageWithTag:imageTag callback:^(NSError *error, UIImage *image) {
if (error) {
errorCallback(error);
@@ -53,17 +52,21 @@ RCT_EXPORT_METHOD(cropImage:(NSString *)imageTag
}
// Crop image
CGRect rectToDrawIn = {{-rect.origin.x, -rect.origin.y}, image.size};
UIGraphicsBeginImageContextWithOptions(rect.size, !RCTImageHasAlpha(image.CGImage), image.scale);
[image drawInRect:rectToDrawIn];
UIImage *croppedImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
CGSize targetSize = rect.size;
CGRect targetRect = {{-rect.origin.x, -rect.origin.y}, image.size};
CGAffineTransform transform = RCTTransformFromTargetRect(image.size, targetRect);
UIImage *croppedImage = RCTTransformImage(image, targetSize, image.scale, transform);
// Scale image
if (cropData[@"displaySize"]) {
CGSize targetSize = [RCTConvert CGSize:cropData[@"displaySize"]];
croppedImage = [self scaleImage:croppedImage targetSize:targetSize resizeMode:resizeMode];
targetSize = [RCTConvert CGSize:cropData[@"displaySize"]]; // in pixels
RCTResizeMode resizeMode = [RCTConvert RCTResizeMode:cropData[@"resizeMode"] ?: @"contain"];
targetRect = RCTTargetRect(croppedImage.size, targetSize, 1, resizeMode);
transform = RCTTransformFromTargetRect(image.size, targetRect);
croppedImage = RCTTransformImage(image, targetSize, image.scale, transform);
}
// Store image
[_bridge.imageStoreManager storeImage:croppedImage withBlock:^(NSString *croppedImageTag) {
if (!croppedImageTag) {
NSString *errorMessage = @"Error storing cropped image in RCTImageStoreManager";
@@ -76,49 +79,4 @@ RCT_EXPORT_METHOD(cropImage:(NSString *)imageTag
}];
}
- (UIImage *)scaleImage:(UIImage *)image targetSize:(CGSize)targetSize resizeMode:(NSString *)resizeMode
{
if (CGSizeEqualToSize(image.size, targetSize)) {
return image;
}
CGFloat imageRatio = image.size.width / image.size.height;
CGFloat targetRatio = targetSize.width / targetSize.height;
CGFloat newWidth = targetSize.width;
CGFloat newHeight = targetSize.height;
// contain vs cover
// http://blog.vjeux.com/2013/image/css-container-and-cover.html
if ([resizeMode isEqualToString:@"contain"]) {
if (imageRatio <= targetRatio) {
newWidth = targetSize.height * imageRatio;
newHeight = targetSize.height;
} else {
newWidth = targetSize.width;
newHeight = targetSize.width / imageRatio;
}
} else if ([resizeMode isEqualToString:@"cover"]) {
if (imageRatio <= targetRatio) {
newWidth = targetSize.width;
newHeight = targetSize.width / imageRatio;
} else {
newWidth = targetSize.height * imageRatio;
newHeight = targetSize.height;
}
} // else assume we're stretching the image
// prevent upscaling
newWidth = MIN(newWidth, image.size.width);
newHeight = MIN(newHeight, image.size.height);
// perform the scaling @1x because targetSize is in actual pixel width/height
UIGraphicsBeginImageContextWithOptions(targetSize, NO, 1.0f);
[image drawInRect:CGRectMake(0.f, 0.f, newWidth, newHeight)];
UIImage *scaledImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return scaledImage;
}
@end

View File

@@ -11,6 +11,7 @@
#import "RCTBridge.h"
#import "RCTURLRequestHandler.h"
#import "RCTResizeMode.h"
@class ALAssetsLibrary;
@@ -40,10 +41,20 @@ typedef void (^RCTImageLoaderCancellationBlock)(void);
- (RCTImageLoaderCancellationBlock)loadImageWithTag:(NSString *)imageTag
size:(CGSize)size
scale:(CGFloat)scale
resizeMode:(UIViewContentMode)resizeMode
resizeMode:(RCTResizeMode)resizeMode
progressBlock:(RCTImageLoaderProgressBlock)progressBlock
completionBlock:(RCTImageLoaderCompletionBlock)completionBlock;
/**
* Loads an image without clipping the result to fit - used by RCTImageView.
*/
- (RCTImageLoaderCancellationBlock)loadImageWithoutClipping:(NSString *)imageTag
size:(CGSize)size
scale:(CGFloat)scale
resizeMode:(RCTResizeMode)resizeMode
progressBlock:(RCTImageLoaderProgressBlock)progressBlock
completionBlock:(RCTImageLoaderCompletionBlock)completionBlock;
/**
* Finds an appropriate image decoder and passes the target size, scale and
* resizeMode for optimal image decoding. Can be called from any thread,
@@ -52,7 +63,7 @@ typedef void (^RCTImageLoaderCancellationBlock)(void);
- (RCTImageLoaderCancellationBlock)decodeImageData:(NSData *)imageData
size:(CGSize)size
scale:(CGFloat)scale
resizeMode:(UIViewContentMode)resizeMode
resizeMode:(RCTResizeMode)resizeMode
completionBlock:(RCTImageLoaderCompletionBlock)completionBlock;
/**
@@ -96,7 +107,7 @@ typedef void (^RCTImageLoaderCancellationBlock)(void);
- (RCTImageLoaderCancellationBlock)loadImageForURL:(NSURL *)imageURL
size:(CGSize)size
scale:(CGFloat)scale
resizeMode:(UIViewContentMode)resizeMode
resizeMode:(RCTResizeMode)resizeMode
progressHandler:(RCTImageLoaderProgressBlock)progressHandler
completionHandler:(RCTImageLoaderCompletionBlock)completionHandler;
@@ -134,7 +145,7 @@ typedef void (^RCTImageLoaderCancellationBlock)(void);
- (RCTImageLoaderCancellationBlock)decodeImageData:(NSData *)imageData
size:(CGSize)size
scale:(CGFloat)scale
resizeMode:(UIViewContentMode)resizeMode
resizeMode:(RCTResizeMode)resizeMode
completionHandler:(RCTImageLoaderCompletionBlock)completionHandler;
@optional

View File

@@ -173,13 +173,31 @@ RCT_EXPORT_MODULE()
return nil;
}
static UIImage *RCTResizeImageIfNeeded(UIImage *image,
CGSize size,
CGFloat scale,
RCTResizeMode resizeMode)
{
if (CGSizeEqualToSize(size, CGSizeZero) ||
CGSizeEqualToSize(image.size, CGSizeZero) ||
CGSizeEqualToSize(image.size, size)) {
return image;
}
CAKeyframeAnimation *animation = image.reactKeyframeAnimation;
CGRect targetSize = RCTTargetRect(image.size, size, scale, resizeMode);
CGAffineTransform transform = RCTTransformFromTargetRect(image.size, targetSize);
image = RCTTransformImage(image, size, scale, transform);
image.reactKeyframeAnimation = animation;
return image;
}
- (RCTImageLoaderCancellationBlock)loadImageWithTag:(NSString *)imageTag
callback:(RCTImageLoaderCompletionBlock)callback
{
return [self loadImageWithTag:imageTag
size:CGSizeZero
scale:1
resizeMode:UIViewContentModeScaleToFill
resizeMode:RCTResizeModeStretch
progressBlock:nil
completionBlock:callback];
}
@@ -192,7 +210,7 @@ RCT_EXPORT_MODULE()
- (RCTImageLoaderCancellationBlock)loadImageOrDataWithTag:(NSString *)imageTag
size:(CGSize)size
scale:(CGFloat)scale
resizeMode:(UIViewContentMode)resizeMode
resizeMode:(RCTResizeMode)resizeMode
progressBlock:(RCTImageLoaderProgressBlock)progressHandler
completionBlock:(void (^)(NSError *error, id imageOrData))completionBlock
{
@@ -352,9 +370,26 @@ RCT_EXPORT_MODULE()
- (RCTImageLoaderCancellationBlock)loadImageWithTag:(NSString *)imageTag
size:(CGSize)size
scale:(CGFloat)scale
resizeMode:(UIViewContentMode)resizeMode
resizeMode:(RCTResizeMode)resizeMode
progressBlock:(RCTImageLoaderProgressBlock)progressHandler
completionBlock:(RCTImageLoaderCompletionBlock)completionBlock
{
return [self loadImageWithoutClipping:imageTag
size:size
scale:scale
resizeMode:resizeMode
progressBlock:progressHandler
completionBlock:^(NSError *error, UIImage *image) {
completionBlock(error, RCTResizeImageIfNeeded(image, size, scale, resizeMode));
}];
}
- (RCTImageLoaderCancellationBlock)loadImageWithoutClipping:(NSString *)imageTag
size:(CGSize)size
scale:(CGFloat)scale
resizeMode:(RCTResizeMode)resizeMode
progressBlock:(RCTImageLoaderProgressBlock)progressHandler
completionBlock:(RCTImageLoaderCompletionBlock)completionBlock
{
__block volatile uint32_t cancelled = 0;
__block void(^cancelLoad)(void) = nil;
@@ -365,11 +400,11 @@ RCT_EXPORT_MODULE()
if (!imageOrData || [imageOrData isKindOfClass:[UIImage class]]) {
completionBlock(error, imageOrData);
} else {
cancelLoad = [weakSelf decodeImageData:imageOrData
size:size
scale:scale
resizeMode:resizeMode
completionBlock:completionBlock] ?: ^{};
cancelLoad = [weakSelf decodeImageDataWithoutClipping:imageOrData
size:size
scale:scale
resizeMode:resizeMode
completionBlock:completionBlock] ?: ^{};
}
}
};
@@ -391,17 +426,47 @@ RCT_EXPORT_MODULE()
- (RCTImageLoaderCancellationBlock)decodeImageData:(NSData *)data
size:(CGSize)size
scale:(CGFloat)scale
resizeMode:(UIViewContentMode)resizeMode
completionBlock:(RCTImageLoaderCompletionBlock)completionHandler
resizeMode:(RCTResizeMode)resizeMode
completionBlock:(RCTImageLoaderCompletionBlock)completionBlock
{
return [self decodeImageDataWithoutClipping:data
size:size
scale:scale
resizeMode:resizeMode
completionBlock:^(NSError *error, UIImage *image) {
completionBlock(error, RCTResizeImageIfNeeded(image, size, scale, resizeMode));
}];
}
- (RCTImageLoaderCancellationBlock)decodeImageDataWithoutClipping:(NSData *)data
size:(CGSize)size
scale:(CGFloat)scale
resizeMode:(RCTResizeMode)resizeMode
completionBlock:(RCTImageLoaderCompletionBlock)completionBlock
{
if (data.length == 0) {
completionHandler(RCTErrorWithMessage(@"No image data"), nil);
completionBlock(RCTErrorWithMessage(@"No image data"), nil);
return ^{};
}
__block volatile uint32_t cancelled = 0;
void (^completionHandler)(NSError *, UIImage *) = ^(NSError *error, UIImage *image) {
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);
}
});
} else if (!cancelled) {
completionBlock(error, image);
}
};
id<RCTImageDataDecoder> imageDecoder = [self imageDataDecoderForData:data];
if (imageDecoder) {
return [imageDecoder decodeImageData:data
size:size
scale:scale
@@ -409,12 +474,26 @@ RCT_EXPORT_MODULE()
completionHandler:completionHandler];
} else {
__block volatile uint32_t cancelled = 0;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
if (cancelled) {
return;
}
UIImage *image = RCTDecodeImageWithData(data, size, scale, resizeMode);
#if RCT_DEV
CGSize imagePixelSize = RCTSizeInPixels(image.size, image.scale);
CGSize screenPixelSize = RCTSizeInPixels(RCTScreenSize(), RCTScreenScale());
if (imagePixelSize.width * imagePixelSize.height >
screenPixelSize.width * screenPixelSize.height) {
RCTLogInfo(@"[PERF ASSETS] Loading image at size %@, which is larger "
"than the screen size %@", NSStringFromCGSize(imagePixelSize),
NSStringFromCGSize(screenPixelSize));
}
#endif
if (image) {
completionHandler(nil, image);
} else {
@@ -436,7 +515,7 @@ RCT_EXPORT_MODULE()
return [self loadImageOrDataWithTag:imageTag
size:CGSizeZero
scale:1
resizeMode:UIViewContentModeScaleToFill
resizeMode:RCTResizeModeStretch
progressBlock:nil
completionBlock:^(NSError *error, id imageOrData) {
CGSize size;
@@ -461,7 +540,17 @@ RCT_EXPORT_MODULE()
- (BOOL)canHandleRequest:(NSURLRequest *)request
{
return [self imageURLLoaderForURL:request.URL] != nil;
NSURL *requestURL = request.URL;
for (id<RCTImageURLLoader> loader in _loaders) {
// Don't use RCTImageURLLoader protocol for modules that already conform to
// RCTURLRequestHandler as it's inefficient to decode an image and then
// convert it back into data
if (![loader conformsToProtocol:@protocol(RCTURLRequestHandler)] &&
[loader canLoadImageURL:requestURL]) {
return YES;
}
}
return NO;
}
- (id)sendRequest:(NSURLRequest *)request withDelegate:(id<RCTURLRequestDelegate>)delegate

View File

@@ -217,6 +217,7 @@ RCT_EXPORT_METHOD(addImageFromBase64:(NSString *)base64String
dispatch_async(_methodQueue, ^{
NSData *imageData = _store[imageTag];
dispatch_async(dispatch_get_main_queue(), ^{
// imageWithData: is not thread-safe, so we can't do this on methodQueue
block([UIImage imageWithData:imageData]);
});
});

View File

@@ -11,28 +11,36 @@
#import <UIKit/UIKit.h>
#import "RCTDefines.h"
#import "RCTResizeMode.h"
NS_ASSUME_NONNULL_BEGIN
/**
* This function takes an input content size (typically from an image), a target
* size and scale that it will be drawn at (typically in a CGContext) and then
* This function takes an source size (typically from an image), a target size
* and scale that it will be drawn at (typically in a CGContext) and then
* calculates the rectangle to draw the image into so that it will be sized and
* positioned correctly if drawn using the specified content mode.
* positioned correctly according to the specified resizeMode.
*/
RCT_EXTERN CGRect RCTTargetRect(CGSize sourceSize, CGSize destSize,
CGFloat destScale, UIViewContentMode resizeMode);
CGFloat destScale, RCTResizeMode resizeMode);
/**
* This function takes a source size (typically from an image), a target rect
* that it will be drawn into (typically relative to a CGContext), and works out
* the transform needed to draw the image at the correct scale and position.
*/
RCT_EXTERN CGAffineTransform RCTTransformFromTargetRect(CGSize sourceSize,
CGRect targetRect);
/**
* This function takes an input content size & scale (typically from an image),
* a target size & scale at which it will be displayed (typically in a
* UIImageView) and then calculates the optimal size at which to redraw the
* image so that it will be displayed correctly with the specified content mode.
* image so that it will be displayed correctly with the specified resizeMode.
*/
RCT_EXTERN CGSize RCTTargetSize(CGSize sourceSize, CGFloat sourceScale,
CGSize destSize, CGFloat destScale,
UIViewContentMode resizeMode,
BOOL allowUpscaling);
RCTResizeMode resizeMode, BOOL allowUpscaling);
/**
* This function takes an input content size & scale (typically from an image),
@@ -41,12 +49,7 @@ 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);
RCTResizeMode resizeMode);
/**
* This function takes the source data for an image and decodes it at the
@@ -58,7 +61,7 @@ RCT_EXTERN CGSize RCTSizeInPixels(CGSize pointSize, CGFloat scale);
RCT_EXTERN UIImage *__nullable RCTDecodeImageWithData(NSData *data,
CGSize destSize,
CGFloat destScale,
UIViewContentMode resizeMode);
RCTResizeMode resizeMode);
/**
* This function takes the source data for an image and decodes just the
@@ -75,4 +78,19 @@ RCT_EXTERN NSDictionary<NSString *, id> *__nullable RCTGetImageMetadata(NSData *
*/
RCT_EXTERN NSData *__nullable RCTGetImageData(CGImageRef image, float quality);
/**
* This function transforms an image. `destSize` is the size of the final image,
* and `destScale` is its scale. The `transform` argument controls how the
* source image will be mapped to the destination image.
*/
RCT_EXTERN UIImage *__nullable RCTTransformImage(UIImage *image,
CGSize destSize,
CGFloat destScale,
CGAffineTransform transform);
/*
* Return YES if image has an alpha component
*/
RCT_EXTERN BOOL RCTImageHasAlpha(CGImageRef image);
NS_ASSUME_NONNULL_END

View File

@@ -35,7 +35,7 @@ static CGSize RCTCeilSize(CGSize size, CGFloat scale)
}
CGRect RCTTargetRect(CGSize sourceSize, CGSize destSize,
CGFloat destScale, UIViewContentMode resizeMode)
CGFloat destScale, RCTResizeMode resizeMode)
{
if (CGSizeEqualToSize(destSize, CGSizeZero)) {
// Assume we require the largest size available
@@ -58,16 +58,16 @@ CGRect RCTTargetRect(CGSize sourceSize, CGSize destSize,
if (resizeMode != UIViewContentModeScaleToFill) {
targetAspect = destSize.width / destSize.height;
if (aspect == targetAspect) {
resizeMode = UIViewContentModeScaleToFill;
resizeMode = RCTResizeModeStretch;
}
}
switch (resizeMode) {
case UIViewContentModeScaleToFill: // stretch
case RCTResizeModeStretch:
return (CGRect){CGPointZero, RCTCeilSize(destSize, destScale)};
case UIViewContentModeScaleAspectFit: // contain
case RCTResizeModeContain:
if (targetAspect <= aspect) { // target is taller than content
@@ -79,9 +79,15 @@ CGRect RCTTargetRect(CGSize sourceSize, CGSize destSize,
sourceSize.height = destSize.height = destSize.height;
sourceSize.width = sourceSize.height * aspect;
}
return (CGRect){CGPointZero, RCTCeilSize(sourceSize, destScale)};
return (CGRect){
{
RCTFloorValue((destSize.width - sourceSize.width) / 2, destScale),
RCTFloorValue((destSize.height - sourceSize.height) / 2, destScale),
},
RCTCeilSize(sourceSize, destScale)
};
case UIViewContentModeScaleAspectFill: // cover
case RCTResizeModeCover:
if (targetAspect <= aspect) { // target is taller than content
@@ -103,21 +109,28 @@ CGRect RCTTargetRect(CGSize sourceSize, CGSize destSize,
RCTCeilSize(sourceSize, destScale)
};
}
default:
RCTLogError(@"A resizeMode value of %zd is not supported", resizeMode);
return (CGRect){CGPointZero, RCTCeilSize(destSize, destScale)};
}
}
CGAffineTransform RCTTransformFromTargetRect(CGSize sourceSize, CGRect targetRect)
{
CGAffineTransform transform = CGAffineTransformIdentity;
transform = CGAffineTransformTranslate(transform,
targetRect.origin.x,
targetRect.origin.y);
transform = CGAffineTransformScale(transform,
targetRect.size.width / sourceSize.width,
targetRect.size.height / sourceSize.height);
return transform;
}
CGSize RCTTargetSize(CGSize sourceSize, CGFloat sourceScale,
CGSize destSize, CGFloat destScale,
UIViewContentMode resizeMode,
RCTResizeMode resizeMode,
BOOL allowUpscaling)
{
switch (resizeMode) {
case UIViewContentModeScaleToFill: // stretch
case RCTResizeModeStretch:
if (!allowUpscaling) {
CGFloat scale = sourceScale / destScale;
@@ -143,7 +156,7 @@ CGSize RCTTargetSize(CGSize sourceSize, CGFloat sourceScale,
BOOL RCTUpscalingRequired(CGSize sourceSize, CGFloat sourceScale,
CGSize destSize, CGFloat destScale,
UIViewContentMode resizeMode)
RCTResizeMode resizeMode)
{
if (CGSizeEqualToSize(destSize, CGSizeZero)) {
// Assume we require the largest size available
@@ -161,16 +174,16 @@ BOOL RCTUpscalingRequired(CGSize sourceSize, CGFloat sourceScale,
aspect = sourceSize.width / sourceSize.height;
targetAspect = destSize.width / destSize.height;
if (aspect == targetAspect) {
resizeMode = UIViewContentModeScaleToFill;
resizeMode = RCTResizeModeStretch;
}
}
switch (resizeMode) {
case UIViewContentModeScaleToFill: // stretch
case RCTResizeModeStretch:
return destSize.width > sourceSize.width || destSize.height > sourceSize.height;
case UIViewContentModeScaleAspectFit: // contain
case RCTResizeModeContain:
if (targetAspect <= aspect) { // target is taller than content
@@ -181,7 +194,7 @@ BOOL RCTUpscalingRequired(CGSize sourceSize, CGFloat sourceScale,
return destSize.height > sourceSize.height;
}
case UIViewContentModeScaleAspectFill: // cover
case RCTResizeModeCover:
if (targetAspect <= aspect) { // target is taller than content
@@ -191,33 +204,20 @@ BOOL RCTUpscalingRequired(CGSize sourceSize, CGFloat sourceScale,
return destSize.width > sourceSize.width;
}
default:
RCTLogError(@"A resizeMode value of %zd is not supported", resizeMode);
return NO;
}
}
CGSize RCTSizeInPixels(CGSize pointSize, CGFloat scale)
{
return (CGSize){
ceil(pointSize.width * scale),
ceil(pointSize.height * scale),
};
}
UIImage *__nullable RCTDecodeImageWithData(NSData *data,
CGSize destSize,
CGFloat destScale,
UIViewContentMode resizeMode)
RCTResizeMode resizeMode)
{
CGImageSourceRef sourceRef = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL);
if (!sourceRef) {
return nil;
}
// get original image size
// Get original image size
CFDictionaryRef imageProperties = CGImageSourceCopyPropertiesAtIndex(sourceRef, 0, NULL);
if (!imageProperties) {
CFRelease(sourceRef);
@@ -237,8 +237,14 @@ UIImage *__nullable RCTDecodeImageWithData(NSData *data,
destScale = RCTScreenScale();
}
// calculate target size
CGSize targetSize = RCTTargetSize(sourceSize, 1, destSize, destScale, resizeMode, YES);
if (resizeMode == UIViewContentModeScaleToFill) {
// Decoder cannot change aspect ratio, so RCTResizeModeStretch is equivalent
// to RCTResizeModeCover for our purposes
resizeMode = RCTResizeModeCover;
}
// Calculate target size
CGSize targetSize = RCTTargetSize(sourceSize, 1, destSize, destScale, resizeMode, NO);
CGSize targetPixelSize = RCTSizeInPixels(targetSize, destScale);
CGFloat maxPixelSize = fmax(fmin(sourceSize.width, targetPixelSize.width),
fmin(sourceSize.height, targetPixelSize.height));
@@ -250,14 +256,14 @@ UIImage *__nullable RCTDecodeImageWithData(NSData *data,
(id)kCGImageSourceThumbnailMaxPixelSize: @(maxPixelSize),
};
// get thumbnail
// Get thumbnail
CGImageRef imageRef = CGImageSourceCreateThumbnailAtIndex(sourceRef, 0, (__bridge CFDictionaryRef)options);
CFRelease(sourceRef);
if (!imageRef) {
return nil;
}
// return image
// Return image
UIImage *image = [UIImage imageWithCGImage:imageRef
scale:destScale
orientation:UIImageOrientationUp];
@@ -298,3 +304,33 @@ NSData *__nullable RCTGetImageData(CGImageRef image, float quality)
CFRelease(destination);
return (__bridge_transfer NSData *)imageData;
}
UIImage *__nullable RCTTransformImage(UIImage *image,
CGSize destSize,
CGFloat destScale,
CGAffineTransform transform)
{
if (destSize.width <= 0 | destSize.height <= 0 || destScale <= 0) {
return nil;
}
UIGraphicsBeginImageContextWithOptions(destSize, NO, destScale);
CGContextRef currentContext = UIGraphicsGetCurrentContext();
CGContextConcatCTM(currentContext, transform);
[image drawAtPoint:CGPointZero];
UIImage *result = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsGetCurrentContext();
return result;
}
BOOL RCTImageHasAlpha(CGImageRef image)
{
switch (CGImageGetAlphaInfo(image)) {
case kCGImageAlphaNone:
case kCGImageAlphaNoneSkipLast:
case kCGImageAlphaNoneSkipFirst:
return NO;
default:
return YES;
}
}

View File

@@ -9,6 +9,7 @@
#import <UIKit/UIKit.h>
#import "RCTImageComponent.h"
#import "RCTResizeMode.h"
@class RCTBridge;
@class RCTImageSource;

View File

@@ -103,8 +103,15 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
- (void)setCapInsets:(UIEdgeInsets)capInsets
{
if (!UIEdgeInsetsEqualToEdgeInsets(_capInsets, capInsets)) {
_capInsets = capInsets;
[self updateImage];
if (UIEdgeInsetsEqualToEdgeInsets(_capInsets, UIEdgeInsetsZero) ||
UIEdgeInsetsEqualToEdgeInsets(capInsets, UIEdgeInsetsZero)) {
_capInsets = capInsets;
// Need to reload image when enabling or disabling capInsets
[self reloadImage];
} else {
_capInsets = capInsets;
[self updateImage];
}
}
}
@@ -126,12 +133,8 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
- (BOOL)sourceNeedsReload
{
NSString *scheme = _source.imageURL.scheme;
return
[scheme isEqualToString:@"http"] ||
[scheme isEqualToString:@"https"] ||
[scheme isEqualToString:@"assets-library"] ||
[scheme isEqualToString:@"ph"];
// If capInsets are set, image doesn't need reloading when resized
return UIEdgeInsetsEqualToEdgeInsets(_capInsets, UIEdgeInsetsZero);
}
- (void)setContentMode:(UIViewContentMode)contentMode
@@ -179,14 +182,22 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
};
}
CGSize imageSize = self.bounds.size;
CGFloat imageScale = RCTScreenScale();
if (!UIEdgeInsetsEqualToEdgeInsets(_capInsets, UIEdgeInsetsZero)) {
// Don't resize images that use capInsets
imageSize = CGSizeZero;
imageScale = _source.scale;
}
RCTImageSource *source = _source;
__weak RCTImageView *weakSelf = self;
_reloadImageCancellationBlock = [_bridge.imageLoader loadImageWithTag:_source.imageURL.absoluteString
size:self.bounds.size
scale:RCTScreenScale()
resizeMode:self.contentMode
progressBlock:progressHandler
completionBlock:^(NSError *error, UIImage *image) {
_reloadImageCancellationBlock = [_bridge.imageLoader loadImageWithoutClipping:_source.imageURL.absoluteString
size:imageSize
scale:imageScale
resizeMode:(RCTResizeMode)self.contentMode
progressBlock:progressHandler
completionBlock:^(NSError *error, UIImage *image) {
dispatch_async(dispatch_get_main_queue(), ^{
RCTImageView *strongSelf = weakSelf;
if (![source isEqual:strongSelf.source]) {
@@ -227,14 +238,15 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
[self reloadImage];
} else if ([self sourceNeedsReload]) {
CGSize imageSize = self.image.size;
CGSize idealSize = RCTTargetSize(imageSize, self.image.scale, frame.size, RCTScreenScale(), self.contentMode, YES);
CGSize idealSize = RCTTargetSize(imageSize, self.image.scale, frame.size,
RCTScreenScale(), (RCTResizeMode)self.contentMode, YES);
if (RCTShouldReloadImageForSizeChange(imageSize, idealSize)) {
if (RCTShouldReloadImageForSizeChange(_targetSize, idealSize)) {
RCTLogInfo(@"[PERF IMAGEVIEW] Reloading image %@ as size %@", _source.imageURL, NSStringFromCGSize(idealSize));
// If the existing image or an image being loaded are not the right size, reload the asset in case there is a
// better size available.
// If the existing image or an image being loaded are not the right
// size, reload the asset in case there is a better size available.
_targetSize = idealSize;
[self reloadImage];
}
@@ -251,8 +263,8 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
[super didMoveToWindow];
if (!self.window) {
// Don't keep self alive through the asynchronous dispatch, if the intention was to remove the view so it would
// deallocate.
// Don't keep self alive through the asynchronous dispatch, if the intention
// was to remove the view so it would deallocate.
__weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
@@ -261,7 +273,8 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
return;
}
// If we haven't been re-added to a window by this run loop iteration, clear out the image to save memory.
// If we haven't been re-added to a window by this run loop iteration,
// clear out the image to save memory.
if (!strongSelf.window) {
[strongSelf clearImage];
}

View File

@@ -32,7 +32,7 @@ RCT_EXPORT_VIEW_PROPERTY(onProgress, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onError, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onLoad, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onLoadEnd, RCTDirectEventBlock)
RCT_REMAP_VIEW_PROPERTY(resizeMode, contentMode, UIViewContentMode)
RCT_REMAP_VIEW_PROPERTY(resizeMode, contentMode, RCTResizeMode)
RCT_EXPORT_VIEW_PROPERTY(source, RCTImageSource)
RCT_CUSTOM_VIEW_PROPERTY(tintColor, UIColor, RCTImageView)
{

View File

@@ -0,0 +1,22 @@
/**
* 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.
*/
#import "RCTConvert.h"
typedef NS_ENUM(NSInteger, RCTResizeMode) {
RCTResizeModeCover = UIViewContentModeScaleAspectFill,
RCTResizeModeContain = UIViewContentModeScaleAspectFit,
RCTResizeModeStretch = UIViewContentModeScaleToFill,
};
@interface RCTConvert(RCTResizeMode)
+ (RCTResizeMode)RCTResizeMode:(id)json;
@end

View File

@@ -0,0 +1,20 @@
/**
* 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.
*/
#import "RCTResizeMode.h"
@implementation RCTConvert(RCTResizeMode)
RCT_ENUM_CONVERTER(RCTResizeMode, (@{
@"cover": @(RCTResizeModeCover),
@"contain": @(RCTResizeModeContain),
@"stretch": @(RCTResizeModeStretch),
}), RCTResizeModeStretch, integerValue)
@end

View File

@@ -46,7 +46,7 @@ RCT_NOT_IMPLEMENTED(-(instancetype)init)
_cancellationBlock = [_bridge.imageLoader loadImageWithTag:source.imageURL.absoluteString
size:source.size
scale:source.scale
resizeMode:UIViewContentModeScaleToFill
resizeMode:RCTResizeModeStretch
progressBlock:nil
completionBlock:^(NSError *error, UIImage *image) {

View File

@@ -25,7 +25,7 @@ RCT_EXPORT_MODULE()
- (RCTImageLoaderCancellationBlock)loadImageForURL:(NSURL *)imageURL
size:(CGSize)size
scale:(CGFloat)scale
resizeMode:(UIViewContentMode)resizeMode
resizeMode:(RCTResizeMode)resizeMode
progressHandler:(RCTImageLoaderProgressBlock)progressHandler
completionHandler:(RCTImageLoaderCompletionBlock)completionHandler
{

View File

@@ -40,6 +40,9 @@ RCT_EXTERN CGFloat RCTRoundPixelValue(CGFloat value);
RCT_EXTERN CGFloat RCTCeilPixelValue(CGFloat value);
RCT_EXTERN CGFloat RCTFloorPixelValue(CGFloat value);
// Convert a size in points to pixels, rounded up to the nearest integral size
RCT_EXTERN CGSize RCTSizeInPixels(CGSize pointSize, CGFloat scale);
// Method swizzling
RCT_EXTERN void RCTSwapClassMethods(Class cls, SEL original, SEL replacement);
RCT_EXTERN void RCTSwapInstanceMethods(Class cls, SEL original, SEL replacement);
@@ -78,9 +81,6 @@ RCT_EXTERN UIAlertView *RCTAlertView(NSString *title,
NSString *cancelButtonTitle,
NSArray<NSString *> *otherButtonTitles);
// Return YES if image has an alpha component
RCT_EXTERN BOOL RCTImageHasAlpha(CGImageRef image);
// Create an NSError in the RCTErrorDomain
RCT_EXTERN NSError *RCTErrorWithMessage(NSString *message);

View File

@@ -219,7 +219,13 @@ CGSize RCTScreenSize()
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
RCTExecuteOnMainThread(^{
size = [UIScreen mainScreen].bounds.size;
if ([[[UIDevice currentDevice] systemVersion] compare:@"8.0" options:NSNumericSearch] == NSOrderedDescending) {
CGSize pixelSize = [UIScreen mainScreen].nativeBounds.size;
CGFloat scale = RCTScreenScale();
size = (CGSize){pixelSize.width / scale, pixelSize.height / scale};
} else {
size = [UIScreen mainScreen].bounds.size;
}
}, YES);
});
@@ -244,6 +250,14 @@ CGFloat RCTFloorPixelValue(CGFloat value)
return floor(value * scale) / scale;
}
CGSize RCTSizeInPixels(CGSize pointSize, CGFloat scale)
{
return (CGSize){
ceil(pointSize.width * scale),
ceil(pointSize.height * scale),
};
}
void RCTSwapClassMethods(Class cls, SEL original, SEL replacement)
{
Method originalMethod = class_getClassMethod(cls, original);
@@ -401,18 +415,6 @@ UIAlertView *RCTAlertView(NSString *title,
return alertView;
}
BOOL RCTImageHasAlpha(CGImageRef image)
{
switch (CGImageGetAlphaInfo(image)) {
case kCGImageAlphaNone:
case kCGImageAlphaNoneSkipLast:
case kCGImageAlphaNoneSkipFirst:
return NO;
default:
return YES;
}
}
NSError *RCTErrorWithMessage(NSString *message)
{
NSDictionary<NSString *, id> *errorInfo = @{NSLocalizedDescriptionKey: message};

View File

@@ -172,6 +172,10 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
// Ordinary property handlers
NSMethodSignature *typeSignature = [[RCTConvert class] methodSignatureForSelector:type];
if (!typeSignature) {
RCTLogError(@"No +[RCTConvert %@] function found.", NSStringFromSelector(type));
return ^(__unused id<RCTComponent> view, __unused id json){};
}
switch (typeSignature.methodReturnType[0]) {
#define RCT_CASE(_value, _type) \