Skip to content

Commit 36444a6

Browse files
author
Alex Akers
committed
Add pluggable image processing system
1 parent eb9a939 commit 36444a6

22 files changed

+982
-392
lines changed

Diff for: Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj

+10
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@
5454
3578590A1B28D2CF00341EDB /* libRCTLinking.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 357859011B28D2C500341EDB /* libRCTLinking.a */; };
5555
834C36EC1AF8DED70019C93C /* libRCTSettings.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 834C36D21AF8DA610019C93C /* libRCTSettings.a */; };
5656
83636F8F1B53F22C009F943E /* RCTUIManagerScenarioTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 83636F8E1B53F22C009F943E /* RCTUIManagerScenarioTests.m */; };
57+
8385CEF51B873B5C00C6273E /* RCTImageLoaderTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8385CEF41B873B5C00C6273E /* RCTImageLoaderTests.m */; };
58+
8385CF041B87479200C6273E /* RCTImageLoaderHelpers.m in Sources */ = {isa = PBXBuildFile; fileRef = 8385CF031B87479200C6273E /* RCTImageLoaderHelpers.m */; };
5759
83A936C81B7E0F08005B9C36 /* RCTConvert_UIColorTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 83A936C71B7E0F08005B9C36 /* RCTConvert_UIColorTests.m */; };
5860
D85B829E1AB6D5D7003F4FE2 /* libRCTVibration.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D85B829C1AB6D5CE003F4FE2 /* libRCTVibration.a */; };
5961
/* End PBXBuildFile section */
@@ -217,6 +219,9 @@
217219
357858F81B28D2C400341EDB /* RCTLinking.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTLinking.xcodeproj; path = ../../Libraries/LinkingIOS/RCTLinking.xcodeproj; sourceTree = "<group>"; };
218220
58005BE41ABA80530062E044 /* RCTTest.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTTest.xcodeproj; path = ../../Libraries/RCTTest/RCTTest.xcodeproj; sourceTree = "<group>"; };
219221
83636F8E1B53F22C009F943E /* RCTUIManagerScenarioTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTUIManagerScenarioTests.m; sourceTree = "<group>"; };
222+
8385CEF41B873B5C00C6273E /* RCTImageLoaderTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImageLoaderTests.m; sourceTree = "<group>"; };
223+
8385CF031B87479200C6273E /* RCTImageLoaderHelpers.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImageLoaderHelpers.m; sourceTree = "<group>"; };
224+
8385CF051B8747A000C6273E /* RCTImageLoaderHelpers.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCTImageLoaderHelpers.h; sourceTree = "<group>"; };
220225
83A936C71B7E0F08005B9C36 /* RCTConvert_UIColorTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTConvert_UIColorTests.m; sourceTree = "<group>"; };
221226
D85B82911AB6D5CE003F4FE2 /* RCTVibration.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTVibration.xcodeproj; path = ../../Libraries/Vibration/RCTVibration.xcodeproj; sourceTree = "<group>"; };
222227
/* End PBXFileReference section */
@@ -365,6 +370,9 @@
365370
1497CFA81B21F5E400C1F8F2 /* RCTConvert_UIFontTests.m */,
366371
1497CFA91B21F5E400C1F8F2 /* RCTEventDispatcherTests.m */,
367372
1300627E1B59179B0043FE5A /* RCTGzipTests.m */,
373+
8385CF051B8747A000C6273E /* RCTImageLoaderHelpers.h */,
374+
8385CF031B87479200C6273E /* RCTImageLoaderHelpers.m */,
375+
8385CEF41B873B5C00C6273E /* RCTImageLoaderTests.m */,
368376
144D21231B2204C5006DB32B /* RCTImageUtilTests.m */,
369377
13DB03471B5D2ED500C27245 /* RCTJSONTests.m */,
370378
13DF61B51B67A45000EDB188 /* RCTMethodArgumentTests.m */,
@@ -811,6 +819,8 @@
811819
83A936C81B7E0F08005B9C36 /* RCTConvert_UIColorTests.m in Sources */,
812820
13DF61B61B67A45000EDB188 /* RCTMethodArgumentTests.m in Sources */,
813821
138D6A181B53CD440074A87E /* RCTShadowViewTests.m in Sources */,
822+
8385CF041B87479200C6273E /* RCTImageLoaderHelpers.m in Sources */,
823+
8385CEF51B873B5C00C6273E /* RCTImageLoaderTests.m in Sources */,
814824
);
815825
runOnlyForDeploymentPostprocessing = 0;
816826
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/**
2+
* The examples provided by Facebook are for non-commercial testing and
3+
* evaluation purposes only.
4+
*
5+
* Facebook reserves all rights not expressly granted.
6+
*
7+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
8+
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
9+
* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
10+
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
11+
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
12+
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
13+
*/
14+
15+
#import "RCTImageLoader.h"
16+
17+
typedef BOOL (^RCTImageURLLoaderCanLoadImageURLHandler)(NSURL *requestURL);
18+
typedef RCTImageLoaderCancellationBlock (^RCTImageURLLoaderLoadImageURLHandler)(NSURL *imageURL, CGSize size, CGFloat scale, UIViewContentMode resizeMode, RCTImageLoaderProgressBlock progressHandler, RCTImageLoaderCompletionBlock completionHandler);
19+
20+
@interface RCTConcreteImageURLLoader : NSObject <RCTImageURLLoader>
21+
22+
- (instancetype)initWithPriority:(float)priority
23+
canLoadImageURLHandler:(RCTImageURLLoaderCanLoadImageURLHandler)canLoadImageURLHandler
24+
loadImageURLHandler:(RCTImageURLLoaderLoadImageURLHandler)loadImageURLHandler;
25+
26+
@end
27+
28+
typedef BOOL (^RCTImageDecoderCanDecodeImageDataHandler)(NSData *imageData);
29+
typedef RCTImageLoaderCancellationBlock (^RCTImageDecoderDecodeImageDataHandler)(NSData *imageData, CGSize size, CGFloat scale, UIViewContentMode resizeMode, RCTImageLoaderCompletionBlock completionHandler);
30+
31+
@interface RCTConcreteImageDecoder : NSObject <RCTImageDecoder>
32+
33+
- (instancetype)initWithPriority:(float)priority
34+
canDecodeImageDataHandler:(RCTImageDecoderCanDecodeImageDataHandler)canDecodeImageDataHandler
35+
decodeImageDataHandler:(RCTImageDecoderDecodeImageDataHandler)decodeImageDataHandler;
36+
37+
@end
38+
39+
#define _RCTDefineImageHandler(SUPERCLASS, CLASS_NAME) \
40+
@interface CLASS_NAME : SUPERCLASS @end \
41+
@implementation CLASS_NAME RCT_EXPORT_MODULE() @end
42+
43+
#define RCTDefineImageURLLoader(CLASS_NAME) \
44+
_RCTDefineImageHandler(RCTConcreteImageURLLoader, CLASS_NAME)
45+
46+
#define RCTDefineImageDecoder(CLASS_NAME) \
47+
_RCTDefineImageHandler(RCTConcreteImageDecoder, CLASS_NAME)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
/**
2+
* The examples provided by Facebook are for non-commercial testing and
3+
* evaluation purposes only.
4+
*
5+
* Facebook reserves all rights not expressly granted.
6+
*
7+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
8+
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
9+
* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
10+
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
11+
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
12+
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
13+
*/
14+
15+
#import "RCTImageLoaderHelpers.h"
16+
17+
@implementation RCTConcreteImageURLLoader
18+
{
19+
RCTImageURLLoaderCanLoadImageURLHandler _canLoadImageURLHandler;
20+
RCTImageURLLoaderLoadImageURLHandler _loadImageURLHandler;
21+
float _priority;
22+
}
23+
24+
+ (NSString *)moduleName
25+
{
26+
return nil;
27+
}
28+
29+
- (instancetype)init
30+
{
31+
return nil;
32+
}
33+
34+
- (instancetype)initWithPriority:(float)priority canLoadImageURLHandler:(RCTImageURLLoaderCanLoadImageURLHandler)canLoadImageURLHandler loadImageURLHandler:(RCTImageURLLoaderLoadImageURLHandler)loadImageURLHandler
35+
{
36+
if ((self = [super init])) {
37+
_canLoadImageURLHandler = [canLoadImageURLHandler copy];
38+
_loadImageURLHandler = [loadImageURLHandler copy];
39+
_priority = priority;
40+
}
41+
42+
return self;
43+
}
44+
45+
- (BOOL)canLoadImageURL:(NSURL *)requestURL
46+
{
47+
return _canLoadImageURLHandler(requestURL);
48+
}
49+
50+
- (RCTImageLoaderCancellationBlock)loadImageForURL:(NSURL *)imageURL size:(CGSize)size scale:(CGFloat)scale resizeMode:(UIViewContentMode)resizeMode progressHandler:(RCTImageLoaderProgressBlock)progressHandler completionHandler:(RCTImageLoaderCompletionBlock)completionHandler
51+
{
52+
return _loadImageURLHandler(imageURL, size, scale, resizeMode, progressHandler, completionHandler);
53+
}
54+
55+
- (float)imageLoaderPriority
56+
{
57+
return _priority;
58+
}
59+
60+
@end
61+
62+
@implementation RCTConcreteImageDecoder
63+
{
64+
RCTImageDecoderCanDecodeImageDataHandler _canDecodeImageDataHandler;
65+
RCTImageDecoderDecodeImageDataHandler _decodeImageDataHandler;
66+
float _priority;
67+
}
68+
69+
+ (NSString *)moduleName
70+
{
71+
return nil;
72+
}
73+
74+
- (instancetype)init
75+
{
76+
return nil;
77+
}
78+
79+
- (instancetype)initWithPriority:(float)priority canDecodeImageDataHandler:(RCTImageDecoderCanDecodeImageDataHandler)canDecodeImageDataHandler decodeImageDataHandler:(RCTImageDecoderDecodeImageDataHandler)decodeImageDataHandler
80+
{
81+
if ((self = [super init])) {
82+
_canDecodeImageDataHandler = [canDecodeImageDataHandler copy];
83+
_decodeImageDataHandler = [decodeImageDataHandler copy];
84+
_priority = priority;
85+
}
86+
87+
return self;
88+
}
89+
90+
- (BOOL)canDecodeImageData:(NSData *)imageData
91+
{
92+
return _canDecodeImageDataHandler(imageData);
93+
}
94+
95+
- (RCTImageLoaderCancellationBlock)decodeImageData:(NSData *)imageData size:(CGSize)size scale:(CGFloat)scale resizeMode:(UIViewContentMode)resizeMode completionHandler:(RCTImageLoaderCompletionBlock)completionHandler
96+
{
97+
return _decodeImageDataHandler(imageData, size, scale, resizeMode, completionHandler);
98+
}
99+
100+
- (float)imageDecoderPriority
101+
{
102+
return _priority;
103+
}
104+
105+
@end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
/**
2+
* The examples provided by Facebook are for non-commercial testing and
3+
* evaluation purposes only.
4+
*
5+
* Facebook reserves all rights not expressly granted.
6+
*
7+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
8+
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
9+
* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
10+
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
11+
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
12+
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
13+
*/
14+
15+
#import <XCTest/XCTest.h>
16+
17+
#import "RCTBridge.h"
18+
#import "RCTImageLoader.h"
19+
#import "RCTImageLoaderHelpers.h"
20+
21+
unsigned char blackGIF[] = {
22+
0x47, 0x49, 0x46, 0x38, 0x39, 0x61, 0x01, 0x00, 0x01, 0x00, 0x80, 0x00,
23+
0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x2c, 0x00, 0x00, 0x00, 0x00,
24+
0x01, 0x00, 0x01, 0x00, 0x00, 0x02, 0x02, 0x44, 0x01, 0x00, 0x3b
25+
};
26+
27+
RCTDefineImageURLLoader(RCTImageLoaderTestsURLLoader1)
28+
RCTDefineImageURLLoader(RCTImageLoaderTestsURLLoader2)
29+
RCTDefineImageDecoder(RCTImageLoaderTestsDecoder1)
30+
RCTDefineImageDecoder(RCTImageLoaderTestsDecoder2)
31+
32+
@interface RCTImageLoaderTests : XCTestCase
33+
34+
@end
35+
36+
@implementation RCTImageLoaderTests
37+
38+
- (void)testImageLoading
39+
{
40+
UIImage *image = [UIImage new];
41+
42+
id<RCTImageURLLoader> loader = [[RCTImageLoaderTestsURLLoader1 alloc] initWithPriority:1.0 canLoadImageURLHandler:^BOOL(__unused NSURL *requestURL) {
43+
return YES;
44+
} loadImageURLHandler:^RCTImageLoaderCancellationBlock(__unused NSURL *imageURL, __unused CGSize size, __unused CGFloat scale, __unused UIViewContentMode resizeMode, RCTImageLoaderProgressBlock progressHandler, RCTImageLoaderCompletionBlock completionHandler) {
45+
progressHandler(1, 1);
46+
completionHandler(nil, image);
47+
return nil;
48+
}];
49+
50+
RCTImageLoader *imageLoader = [RCTImageLoader new];
51+
NS_VALID_UNTIL_END_OF_SCOPE RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil moduleProvider:^{ return @[loader, imageLoader]; } launchOptions:nil];
52+
53+
RCTImageLoaderCancellationBlock cancelBlock = [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) {
54+
XCTAssertEqual(progress, 1);
55+
XCTAssertEqual(total, 1);
56+
} completionBlock:^(NSError *loadError, id loadedImage) {
57+
XCTAssertEqualObjects(loadedImage, image);
58+
XCTAssertNil(loadError);
59+
}];
60+
XCTAssertNil(cancelBlock);
61+
}
62+
63+
- (void)testImageLoaderUsesImageURLLoaderWithHighestPriority
64+
{
65+
UIImage *image = [UIImage new];
66+
67+
id<RCTImageURLLoader> loader1 = [[RCTImageLoaderTestsURLLoader1 alloc] initWithPriority:1.0 canLoadImageURLHandler:^BOOL(__unused NSURL *requestURL) {
68+
return YES;
69+
} loadImageURLHandler:^RCTImageLoaderCancellationBlock(__unused NSURL *imageURL, __unused CGSize size, __unused CGFloat scale, __unused UIViewContentMode resizeMode, RCTImageLoaderProgressBlock progressHandler, RCTImageLoaderCompletionBlock completionHandler) {
70+
progressHandler(1, 1);
71+
completionHandler(nil, image);
72+
return nil;
73+
}];
74+
75+
id<RCTImageURLLoader> loader2 = [[RCTImageLoaderTestsURLLoader2 alloc] initWithPriority:0.5 canLoadImageURLHandler:^BOOL(__unused NSURL *requestURL) {
76+
return YES;
77+
} loadImageURLHandler:^RCTImageLoaderCancellationBlock(__unused NSURL *imageURL, __unused CGSize size, __unused CGFloat scale, __unused UIViewContentMode resizeMode, __unused RCTImageLoaderProgressBlock progressHandler, __unused RCTImageLoaderCompletionBlock completionHandler) {
78+
XCTFail(@"Should not have used loader2");
79+
return nil;
80+
}];
81+
82+
RCTImageLoader *imageLoader = [RCTImageLoader new];
83+
NS_VALID_UNTIL_END_OF_SCOPE RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil moduleProvider:^{ return @[loader1, loader2, imageLoader]; } launchOptions:nil];
84+
85+
RCTImageLoaderCancellationBlock cancelBlock = [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) {
86+
XCTAssertEqual(progress, 1);
87+
XCTAssertEqual(total, 1);
88+
} completionBlock:^(NSError *loadError, id loadedImage) {
89+
XCTAssertEqualObjects(loadedImage, image);
90+
XCTAssertNil(loadError);
91+
}];
92+
XCTAssertNil(cancelBlock);
93+
}
94+
95+
- (void)testImageDecoding
96+
{
97+
NSData *data = [NSData dataWithBytesNoCopy:blackGIF length:sizeof(blackGIF) freeWhenDone:NO];
98+
UIImage *image = [[UIImage alloc] initWithData:data];
99+
100+
id<RCTImageDecoder> decoder = [[RCTImageLoaderTestsDecoder1 alloc] initWithPriority:1.0 canDecodeImageDataHandler:^BOOL(__unused NSData *imageData) {
101+
return YES;
102+
} decodeImageDataHandler:^RCTImageLoaderCancellationBlock(NSData *imageData, __unused CGSize size, __unused CGFloat scale, __unused UIViewContentMode resizeMode, RCTImageLoaderCompletionBlock completionHandler) {
103+
XCTAssertEqualObjects(imageData, data);
104+
completionHandler(nil, image);
105+
return nil;
106+
}];
107+
108+
RCTImageLoader *imageLoader = [RCTImageLoader new];
109+
NS_VALID_UNTIL_END_OF_SCOPE RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil moduleProvider:^{ return @[decoder, imageLoader]; } launchOptions:nil];
110+
111+
RCTImageLoaderCancellationBlock cancelBlock = [imageLoader decodeImageData:data size:CGSizeMake(1, 1) scale:1.0 resizeMode:UIViewContentModeScaleToFill completionBlock:^(NSError *decodeError, id decodedImage) {
112+
XCTAssertEqualObjects(decodedImage, image);
113+
XCTAssertNil(decodeError);
114+
}];
115+
XCTAssertNil(cancelBlock);
116+
}
117+
118+
- (void)testImageLoaderUsesImageDecoderWithHighestPriority
119+
{
120+
NSData *data = [NSData dataWithBytesNoCopy:blackGIF length:sizeof(blackGIF) freeWhenDone:NO];
121+
UIImage *image = [[UIImage alloc] initWithData:data];
122+
123+
id<RCTImageDecoder> decoder1 = [[RCTImageLoaderTestsDecoder1 alloc] initWithPriority:1.0 canDecodeImageDataHandler:^BOOL(__unused NSData *imageData) {
124+
return YES;
125+
} decodeImageDataHandler:^RCTImageLoaderCancellationBlock(NSData *imageData, __unused CGSize size, __unused CGFloat scale, __unused UIViewContentMode resizeMode, RCTImageLoaderCompletionBlock completionHandler) {
126+
XCTAssertEqualObjects(imageData, data);
127+
completionHandler(nil, image);
128+
return nil;
129+
}];
130+
131+
id<RCTImageDecoder> decoder2 = [[RCTImageLoaderTestsDecoder2 alloc] initWithPriority:0.5 canDecodeImageDataHandler:^BOOL(__unused NSData *imageData) {
132+
return YES;
133+
} decodeImageDataHandler:^RCTImageLoaderCancellationBlock(__unused NSData *imageData, __unused CGSize size, __unused CGFloat scale, __unused UIViewContentMode resizeMode, __unused RCTImageLoaderCompletionBlock completionHandler) {
134+
XCTFail(@"Should not have used decoder2");
135+
return nil;
136+
}];
137+
138+
RCTImageLoader *imageLoader = [RCTImageLoader new];
139+
NS_VALID_UNTIL_END_OF_SCOPE RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil moduleProvider:^{ return @[decoder1, decoder2, imageLoader]; } launchOptions:nil];
140+
141+
RCTImageLoaderCancellationBlock cancelBlock = [imageLoader decodeImageData:data size:CGSizeMake(1, 1) scale:1.0 resizeMode:UIViewContentModeScaleToFill completionBlock:^(NSError *decodeError, id decodedImage) {
142+
XCTAssertEqualObjects(decodedImage, image);
143+
XCTAssertNil(decodeError);
144+
}];
145+
XCTAssertNil(cancelBlock);
146+
}
147+
148+
@end

Diff for: Libraries/Image/RCTImageRequestHandler.h renamed to Libraries/Image/RCTAssetBundleImageLoader.h

+2-2
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77
* of patent rights can be found in the PATENTS file in the same directory.
88
*/
99

10-
#import "RCTURLRequestHandler.h"
10+
#import "RCTImageLoader.h"
1111

12-
@interface RCTImageRequestHandler : NSObject <RCTURLRequestHandler>
12+
@interface RCTAssetBundleImageLoader : NSObject <RCTImageURLLoader>
1313

1414
@end

0 commit comments

Comments
 (0)