2015-09-02 15:25:10 +00:00
/ * *
* Copyright ( c ) 2015 - present , Facebook , Inc .
* All rights reserved .
*
* This source code is licensed under the BSD - style license found in the
* LICENSE file in the root directory of this source tree . An additional grant
* of patent rights can be found in the PATENTS file in the same directory .
* /
# import "RCTAssetsLibraryImageLoader.h"
# import < AssetsLibrary / AssetsLibrary . h >
# import < ImageIO / ImageIO . h >
2015-09-23 19:00:05 +00:00
# import < libkern / OSAtomic . h >
2015-09-02 15:25:10 +00:00
# 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 ) ;
static UIImage * RCTScaledImageForAsset ( ALAssetRepresentation * representation , CGSize size , CGFloat scale , UIViewContentMode resizeMode , NSError * * error ) ;
@ 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 . lowercaseString isEqualToString : @ "assets-library" ] ;
}
- ( RCTImageLoaderCancellationBlock ) loadImageForURL : ( NSURL * ) imageURL size : ( CGSize ) size scale : ( CGFloat ) scale resizeMode : ( UIViewContentMode ) resizeMode progressHandler : ( RCTImageLoaderProgressBlock ) progressHandler completionHandler : ( RCTImageLoaderCompletionBlock ) completionHandler
{
2015-09-23 19:00:05 +00:00
__block volatile uint32_t cancelled = 0 ;
2015-09-02 15:25:10 +00:00
[ [ self assetsLibrary ] assetForURL : imageURL resultBlock : ^ ( ALAsset * asset ) {
2015-09-23 19:00:05 +00:00
if ( cancelled ) {
return ;
}
2015-09-02 15:25:10 +00:00
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 ( ) , ^ {
2015-09-23 19:00:05 +00:00
if ( cancelled ) {
return ;
}
2015-09-02 15:25:10 +00:00
// 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 ] ;
2015-09-25 09:26:25 +00:00
# if RCT_DEV
CGSize sizeBeingLoaded = size ;
if ( useMaximumSize ) {
CGSize pointSize = representation . dimensions ;
sizeBeingLoaded = CGSizeMake ( pointSize . width * representation . scale , pointSize . height * representation . scale ) ;
}
2015-09-28 23:30:58 +00:00
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 ) ;
}
2015-09-25 09:26:25 +00:00
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
2015-09-02 15:25:10 +00:00
UIImage * image ;
NSError * error = nil ;
if ( useMaximumSize ) {
image = [ UIImage imageWithCGImage : representation . fullResolutionImage
scale : scale
orientation : ( UIImageOrientation ) representation . orientation ] ;
} else {
image = RCTScaledImageForAsset ( representation , size , scale , resizeMode , & error ) ;
}
completionHandler ( error , image ) ;
}
} ) ;
} else {
NSString * errorText = [ NSString stringWithFormat : @ "Failed to load asset at URL %@ with no error message." , imageURL ] ;
NSError * error = RCTErrorWithMessage ( errorText ) ;
completionHandler ( error , nil ) ;
}
} failureBlock : ^ ( NSError * loadError ) {
2015-09-23 19:00:05 +00:00
if ( cancelled ) {
return ;
}
2015-09-02 15:25:10 +00:00
NSString * errorText = [ NSString stringWithFormat : @ "Failed to load asset at URL %@.\niOS Error: %@" , imageURL , loadError ] ;
NSError * error = RCTErrorWithMessage ( errorText ) ;
completionHandler ( error , nil ) ;
} ] ;
2015-09-23 19:00:05 +00:00
return ^ {
OSAtomicOr32Barrier ( 1 , & cancelled ) ;
} ;
2015-09-02 15:25:10 +00:00
}
@ end
@ implementation RCTBridge ( RCTAssetsLibraryImageLoader )
- ( RCTAssetsLibraryImageLoader * ) assetsLibraryImageLoader
{
return self . modules [ RCTBridgeModuleNameForClass ( [ RCTAssetsLibraryImageLoader class ] ) ] ;
}
- ( ALAssetsLibrary * ) assetsLibrary
{
return [ self . assetsLibraryImageLoader 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 ;
}
// Why use a custom scaling method ? Greater efficiency , reduced memory overhead :
// http : // www . mindsea . com / 2012 / 12 / downscaling - huge - alassets - without - fear - of - sigkill
static UIImage * RCTScaledImageForAsset ( ALAssetRepresentation * representation , CGSize size , CGFloat scale , UIViewContentMode resizeMode , NSError * * error )
{
NSUInteger length = ( NSUInteger ) representation . size ;
NSMutableData * data = [ NSMutableData dataWithLength : length ] ;
if ( ! [ representation getBytes : data . mutableBytes
fromOffset : 0
length : length
error : error ] ) {
return nil ;
}
CGSize sourceSize = representation . dimensions ;
CGSize targetSize = RCTTargetSize ( sourceSize , representation . scale ,
size , scale , resizeMode , NO ) ;
NSDictionary * options = @ {
( id ) kCGImageSourceShouldAllowFloat : @ YES ,
( id ) kCGImageSourceCreateThumbnailWithTransform : @ YES ,
( id ) kCGImageSourceCreateThumbnailFromImageAlways : @ YES ,
( id ) kCGImageSourceThumbnailMaxPixelSize : @ ( MAX ( targetSize . width , targetSize . height ) * scale )
} ;
CGImageSourceRef sourceRef = CGImageSourceCreateWithData ( ( __bridge CFDataRef ) data , nil ) ;
CGImageRef imageRef = CGImageSourceCreateThumbnailAtIndex ( sourceRef , 0 , ( __bridge CFDictionaryRef ) options ) ;
if ( sourceRef ) {
CFRelease ( sourceRef ) ;
}
if ( imageRef ) {
UIImage * image = [ UIImage imageWithCGImage : imageRef scale : scale
orientation : UIImageOrientationUp ] ;
CGImageRelease ( imageRef ) ;
return image ;
}
return nil ;
}