Feature: refactor decoding code and provide decoding plugin
- open the decoding/encoding procedures to the users - switch from hardcoded decoding/encoding to pluginable decoders/encoders (builtin + user created) - `SDWebImageCodersManager` is a singleton holding an array of `SDImageCoder` (protocol). Even though a singleton is not necesarily a good pattern, in this case it eliminates dealing with passing this array around - uses a priority queue behind scenes, which means the latest added coders have priority. - the priority is crucial when encoding/decoding something, we go through the list and ask each coder if they can handle the current data (see `canDecodeFromData:`, `canEncodeToFormat:`, `canIncrementallyDecodeFromData:`) - each coder must conform to this protocol `SDImageCoder` describing all the required behavior for a coder - we provide 3 built-in coders: `SDWebImageImageIOCoder` (for JPEG, PNG, TIFF), `SDWebImageGIFCoder` (for GIF), `SDWebImageWebPCoder` (for WebP and animated WebP) - the user of SDWebImage can create custom coders by conforming to `SDImageCoder` and adding the coders to `SDWebImageCodersManager`. See `addCoder:` or `removeCoder:` or `coders` getter to get the array - in order to preserve backwards compatibility, the UIImage categories were preserved, calling the new coders APIs described above
This commit is contained in:
parent
c1df782869
commit
a0879fc049
|
@ -28,7 +28,7 @@ Pod::Spec.new do |s|
|
|||
|
||||
s.subspec 'Core' do |core|
|
||||
core.source_files = 'SDWebImage/{NS,SD,UI}*.{h,m}'
|
||||
core.exclude_files = 'SDWebImage/UIImage+WebP.{h,m}'
|
||||
core.exclude_files = 'SDWebImage/UIImage+WebP.{h,m}', 'SDWebImage/SDWebImageWebPCoder.{h,m}'
|
||||
core.tvos.exclude_files = 'SDWebImage/MKAnnotationView+WebCache.*'
|
||||
end
|
||||
|
||||
|
@ -52,7 +52,7 @@ Pod::Spec.new do |s|
|
|||
end
|
||||
|
||||
s.subspec 'WebP' do |webp|
|
||||
webp.source_files = 'SDWebImage/UIImage+WebP.{h,m}'
|
||||
webp.source_files = 'SDWebImage/UIImage+WebP.{h,m}', 'SDWebImage/SDWebImageWebPCoder.{h,m}'
|
||||
webp.xcconfig = {
|
||||
'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) SD_WEBP=1',
|
||||
'USER_HEADER_SEARCH_PATHS' => '$(inherited) $(SRCROOT)/libwebp/src'
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -30,4 +30,12 @@ typedef NS_ENUM(NSInteger, SDImageFormat) {
|
|||
*/
|
||||
+ (SDImageFormat)sd_imageFormatForImageData:(nullable NSData *)data;
|
||||
|
||||
/**
|
||||
Convert SDImageFormat to UTType
|
||||
|
||||
@param format Format as SDImageFormat
|
||||
@return The UTType as CFStringRef
|
||||
*/
|
||||
+ (nonnull CFStringRef)sd_UTTypeFromSDImageFormat:(SDImageFormat)format;
|
||||
|
||||
@end
|
||||
|
|
|
@ -8,7 +8,11 @@
|
|||
*/
|
||||
|
||||
#import "NSData+ImageContentType.h"
|
||||
|
||||
#if SD_MAC
|
||||
#import <CoreServices/CoreServices.h>
|
||||
#else
|
||||
#import <MobileCoreServices/MobileCoreServices.h>
|
||||
#endif
|
||||
|
||||
@implementation NSData (ImageContentType)
|
||||
|
||||
|
@ -43,4 +47,27 @@
|
|||
return SDImageFormatUndefined;
|
||||
}
|
||||
|
||||
+ (nonnull CFStringRef)sd_UTTypeFromSDImageFormat:(SDImageFormat)format {
|
||||
CFStringRef UTType;
|
||||
switch (format) {
|
||||
case SDImageFormatJPEG:
|
||||
UTType = kUTTypeJPEG;
|
||||
break;
|
||||
case SDImageFormatPNG:
|
||||
UTType = kUTTypePNG;
|
||||
break;
|
||||
case SDImageFormatGIF:
|
||||
UTType = kUTTypeGIF;
|
||||
break;
|
||||
case SDImageFormatTIFF:
|
||||
UTType = kUTTypeTIFF;
|
||||
break;
|
||||
default:
|
||||
// default is kUTTypePNG
|
||||
UTType = kUTTypePNG;
|
||||
break;
|
||||
}
|
||||
return UTType;
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -7,12 +7,9 @@
|
|||
*/
|
||||
|
||||
#import "SDImageCache.h"
|
||||
#import "SDWebImageDecoder.h"
|
||||
#import "UIImage+MultiFormat.h"
|
||||
#import <CommonCrypto/CommonDigest.h>
|
||||
#import "UIImage+GIF.h"
|
||||
#import "NSData+ImageContentType.h"
|
||||
#import "NSImage+WebCache.h"
|
||||
#import "SDWebImageCodersManager.h"
|
||||
|
||||
// See https://github.com/rs/SDWebImage/pull/1141 for discussion
|
||||
@interface AutoPurgeCache : NSCache
|
||||
|
@ -220,9 +217,9 @@ FOUNDATION_STATIC_INLINE NSUInteger SDCacheCostForImage(UIImage *image) {
|
|||
@autoreleasepool {
|
||||
NSData *data = imageData;
|
||||
if (!data && image) {
|
||||
SDImageFormat imageFormatFromData = [NSData sd_imageFormatForImageData:data];
|
||||
data = [image sd_imageDataAsFormat:imageFormatFromData];
|
||||
}
|
||||
// If we do not have any data to detect image format, use PNG format
|
||||
data = [[SDWebImageCodersManager sharedInstance] encodedDataWithImage:image format:SDImageFormatPNG];
|
||||
}
|
||||
[self storeImageDataToDisk:data forKey:key];
|
||||
}
|
||||
|
||||
|
@ -345,16 +342,10 @@ FOUNDATION_STATIC_INLINE NSUInteger SDCacheCostForImage(UIImage *image) {
|
|||
- (nullable UIImage *)diskImageForKey:(nullable NSString *)key {
|
||||
NSData *data = [self diskImageDataBySearchingAllPathsForKey:key];
|
||||
if (data) {
|
||||
UIImage *image = [UIImage sd_imageWithData:data];
|
||||
UIImage *image = [[SDWebImageCodersManager sharedInstance] decodedImageWithData:data];
|
||||
image = [self scaledImageForKey:key image:image];
|
||||
#ifdef SD_WEBP
|
||||
SDImageFormat imageFormat = [NSData sd_imageFormatForImageData:data];
|
||||
if (imageFormat == SDImageFormatWebP) {
|
||||
return image;
|
||||
}
|
||||
#endif
|
||||
if (self.config.shouldDecompressImages) {
|
||||
image = [UIImage decodedImageWithImage:image];
|
||||
image = [[SDWebImageCodersManager sharedInstance] decompressedImageWithImage:image data:&data options:@{SDWebImageCoderScaleDownLargeImagesKey: @(NO)}];
|
||||
}
|
||||
return image;
|
||||
} else {
|
||||
|
@ -378,7 +369,7 @@ FOUNDATION_STATIC_INLINE NSUInteger SDCacheCostForImage(UIImage *image) {
|
|||
UIImage *image = [self imageFromMemoryCacheForKey:key];
|
||||
if (image) {
|
||||
NSData *diskData = nil;
|
||||
if ([image isGIF]) {
|
||||
if (image.images) {
|
||||
diskData = [self diskImageDataBySearchingAllPathsForKey:key];
|
||||
}
|
||||
if (doneBlock) {
|
||||
|
|
|
@ -0,0 +1,115 @@
|
|||
/*
|
||||
* This file is part of the SDWebImage package.
|
||||
* (c) Olivier Poitrey <rs@dailymotion.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "SDWebImageCompat.h"
|
||||
#import "NSData+ImageContentType.h"
|
||||
|
||||
/**
|
||||
A Boolean value indicating whether to scale down large images during decompressing. (NSNumber)
|
||||
*/
|
||||
FOUNDATION_EXPORT NSString * _Nonnull const SDWebImageCoderScaleDownLargeImagesKey;
|
||||
|
||||
/**
|
||||
Return the shared device-dependent RGB color space created with CGColorSpaceCreateDeviceRGB.
|
||||
|
||||
@return The device-dependent RGB color space
|
||||
*/
|
||||
CG_EXTERN CGColorSpaceRef _Nonnull SDCGColorSpaceGetDeviceRGB(void);
|
||||
|
||||
/**
|
||||
Check whether CGImageRef contains alpha channel.
|
||||
|
||||
@param imageRef The CGImageRef
|
||||
@return Return YES if CGImageRef contains alpha channel, otherwise return NO
|
||||
*/
|
||||
CG_EXTERN BOOL SDCGImageRefContainsAlpha(_Nullable CGImageRef imageRef);
|
||||
|
||||
|
||||
/**
|
||||
This is the image coder protocol to provide custom image decoding/encoding.
|
||||
@note Pay attention that these methods are not called from main queue.
|
||||
*/
|
||||
@protocol SDWebImageCoder <NSObject>
|
||||
|
||||
#pragma mark - Decoding
|
||||
/**
|
||||
Returns YES if this coder can decode some data. Otherwise, the data should be passed to another coder.
|
||||
|
||||
@param data The image data so we can look at it
|
||||
@return YES if this coder can decode the data, NO otherwise
|
||||
*/
|
||||
- (BOOL)canDecodeFromData:(nullable NSData *)data;
|
||||
|
||||
/**
|
||||
Decode the image data to image.
|
||||
|
||||
@param data The image data to be decoded
|
||||
@return The decoded image from data
|
||||
*/
|
||||
- (nullable UIImage *)decodedImageWithData:(nullable NSData *)data;
|
||||
|
||||
/**
|
||||
Decompress the image with original image and image data.
|
||||
|
||||
@param image The original image to be decompressed
|
||||
@param data The pointer to original image data. The pointer itself is nonnull but image data can be null. This data will set to cache if needed. If you do not need to modify data at the sametime, ignore this param.
|
||||
@param optionsDict A dictionary containing any decompressing options. Pass {SDWebImageCoderScaleDownLargeImagesKey: @(YES)} to scale down large images
|
||||
@return The decompressed image
|
||||
*/
|
||||
- (nullable UIImage *)decompressedImageWithImage:(nullable UIImage *)image
|
||||
data:(NSData * _Nullable * _Nonnull)data
|
||||
options:(nullable NSDictionary<NSString*, NSObject*>*)optionsDict;
|
||||
|
||||
#pragma mark - Encoding
|
||||
|
||||
/**
|
||||
Returns YES if this coder can encode some image. Otherwise, it should be passed to another coder.
|
||||
|
||||
@param format The image format
|
||||
@return YES if this coder can encode the image, NO otherwise
|
||||
*/
|
||||
- (BOOL)canEncodeToFormat:(SDImageFormat)format;
|
||||
|
||||
/**
|
||||
Encode the image to image data.
|
||||
|
||||
@param image The image to be encoded
|
||||
@param format The image format to encode, you should note `SDImageFormatUndefined` format is also possible
|
||||
@return The encoded image data
|
||||
*/
|
||||
- (nullable NSData *)encodedDataWithImage:(nullable UIImage *)image format:(SDImageFormat)format;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
/**
|
||||
This is the image coder protocol to provide custom progressive image decoding.
|
||||
@note Pay attention that these methods are not called from main queue.
|
||||
*/
|
||||
@protocol SDWebImageProgressiveCoder <SDWebImageCoder>
|
||||
|
||||
/**
|
||||
Returns YES if this coder can incremental decode some data. Otherwise, it should be passed to another coder.
|
||||
|
||||
@param data The image data so we can look at it
|
||||
@return YES if this coder can decode the data, NO otherwise
|
||||
*/
|
||||
- (BOOL)canIncrementallyDecodeFromData:(nullable NSData *)data;
|
||||
|
||||
/**
|
||||
Incremental decode the image data to image.
|
||||
|
||||
@param data The image data has been downloaded so far
|
||||
@param finished Whether the download has finished
|
||||
@warning because incremental decoding need to keep the decoded context, we will alloc a new instance with the same class for each download operation to avoid conflicts
|
||||
@return The decoded image from data
|
||||
*/
|
||||
- (nullable UIImage *)incrementallyDecodedImageWithData:(nullable NSData *)data finished:(BOOL)finished;
|
||||
|
||||
@end
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* This file is part of the SDWebImage package.
|
||||
* (c) Olivier Poitrey <rs@dailymotion.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
#import "SDWebImageCoder.h"
|
||||
|
||||
NSString * const SDWebImageCoderScaleDownLargeImagesKey = @"scaleDownLargeImages";
|
||||
|
||||
CGColorSpaceRef SDCGColorSpaceGetDeviceRGB(void) {
|
||||
static CGColorSpaceRef colorSpace;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
colorSpace = CGColorSpaceCreateDeviceRGB();
|
||||
});
|
||||
return colorSpace;
|
||||
}
|
||||
|
||||
BOOL SDCGImageRefContainsAlpha(CGImageRef imageRef) {
|
||||
if (!imageRef) {
|
||||
return NO;
|
||||
}
|
||||
CGImageAlphaInfo alphaInfo = CGImageGetAlphaInfo(imageRef);
|
||||
BOOL hasAlpha = !(alphaInfo == kCGImageAlphaNone ||
|
||||
alphaInfo == kCGImageAlphaNoneSkipFirst ||
|
||||
alphaInfo == kCGImageAlphaNoneSkipLast);
|
||||
return hasAlpha;
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* This file is part of the SDWebImage package.
|
||||
* (c) Olivier Poitrey <rs@dailymotion.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "SDWebImageCoder.h"
|
||||
|
||||
/**
|
||||
Global object holding the array of coders, so that we avoid passing them from object to object.
|
||||
Uses a priority queue behind scenes, which means the latest added coders have priority.
|
||||
This is done so when encoding/decoding something, we go through the list and ask each coder if they can handle the current data.
|
||||
That way, users can add their custom coders while preserving our existing prebuilt ones
|
||||
|
||||
Note: the `coders` getter will return the coders in their reversed order
|
||||
Example:
|
||||
- by default we internally set coders = `IOCoder`, `GIFCoder`, `WebPCoder`
|
||||
- calling `coders` will return `@[WebPCoder, GIFCoder, IOCoder]`
|
||||
- call `[addCoder:[MyCrazyCoder new]]`
|
||||
- calling `coders` now returns `@[MyCrazyCoder, WebPCoder, GIFCoder, IOCoder]`
|
||||
|
||||
Coders
|
||||
------
|
||||
A coder must conform to the `SDWebImageCoder` protocol or even to `SDWebImageProgressiveCoder` if it supports progressive decoding
|
||||
Conformance is important because that way, they will implement `canDecodeFromData` or `canEncodeToFormat`
|
||||
Those methods are called on each coder in the array (using the priority order) until one of them returns YES.
|
||||
That means that coder can decode that data / encode to that format
|
||||
*/
|
||||
@interface SDWebImageCodersManager : NSObject<SDWebImageCoder>
|
||||
|
||||
/**
|
||||
Shared reusable instance
|
||||
*/
|
||||
+ (nonnull instancetype)sharedInstance;
|
||||
|
||||
/**
|
||||
All coders in coders manager. The coders array is a priority queue, which means the later added coder will have the highest priority
|
||||
*/
|
||||
@property (nonatomic, strong, readwrite, nullable) NSArray<SDWebImageCoder>* coders;
|
||||
|
||||
/**
|
||||
Add a new coder to the end of coders array. Which has the highest priority.
|
||||
|
||||
@param coder coder
|
||||
*/
|
||||
- (void)addCoder:(nonnull id<SDWebImageCoder>)coder;
|
||||
|
||||
/**
|
||||
Remove a coder in the coders array.
|
||||
|
||||
@param coder coder
|
||||
*/
|
||||
- (void)removeCoder:(nonnull id<SDWebImageCoder>)coder;
|
||||
|
||||
@end
|
|
@ -0,0 +1,137 @@
|
|||
/*
|
||||
* This file is part of the SDWebImage package.
|
||||
* (c) Olivier Poitrey <rs@dailymotion.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
#import "SDWebImageCodersManager.h"
|
||||
#import "SDWebImageImageIOCoder.h"
|
||||
#import "SDWebImageGIFCoder.h"
|
||||
#ifdef SD_WEBP
|
||||
#import "SDWebImageWebPCoder.h"
|
||||
#endif
|
||||
|
||||
@interface SDWebImageCodersManager ()
|
||||
|
||||
@property (nonatomic, strong, nonnull) NSMutableArray<SDWebImageCoder>* mutableCoders;
|
||||
@property (SDDispatchQueueSetterSementics, nonatomic, nullable) dispatch_queue_t mutableCodersAccessQueue;
|
||||
|
||||
@end
|
||||
|
||||
@implementation SDWebImageCodersManager
|
||||
|
||||
+ (nonnull instancetype)sharedInstance {
|
||||
static dispatch_once_t once;
|
||||
static id instance;
|
||||
dispatch_once(&once, ^{
|
||||
instance = [self new];
|
||||
});
|
||||
return instance;
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
if (self = [super init]) {
|
||||
// initialize with default coders
|
||||
_mutableCoders = [@[[SDWebImageImageIOCoder sharedCoder], [SDWebImageGIFCoder sharedCoder]] mutableCopy];
|
||||
#ifdef SD_WEBP
|
||||
[_mutableCoders addObject:[SDWebImageWebPCoder sharedCoder]];
|
||||
#endif
|
||||
_mutableCodersAccessQueue = dispatch_queue_create("com.hackemist.SDWebImageCodersManager", DISPATCH_QUEUE_CONCURRENT);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
SDDispatchQueueRelease(_mutableCodersAccessQueue);
|
||||
}
|
||||
|
||||
#pragma mark - Coder IO operations
|
||||
|
||||
- (void)addCoder:(nonnull id<SDWebImageCoder>)coder {
|
||||
if ([coder conformsToProtocol:@protocol(SDWebImageCoder)]) {
|
||||
dispatch_barrier_sync(self.mutableCodersAccessQueue, ^{
|
||||
[self.mutableCoders addObject:coder];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
- (void)removeCoder:(nonnull id<SDWebImageCoder>)coder {
|
||||
dispatch_barrier_sync(self.mutableCodersAccessQueue, ^{
|
||||
[self.mutableCoders removeObject:coder];
|
||||
});
|
||||
}
|
||||
|
||||
- (NSArray<SDWebImageCoder> *)coders {
|
||||
__block NSArray<SDWebImageCoder> *sortedCoders = nil;
|
||||
dispatch_sync(self.mutableCodersAccessQueue, ^{
|
||||
sortedCoders = (NSArray<SDWebImageCoder> *)[[[self.mutableCoders copy] reverseObjectEnumerator] allObjects];
|
||||
});
|
||||
return sortedCoders;
|
||||
}
|
||||
|
||||
- (void)setCoders:(NSArray<SDWebImageCoder> *)coders {
|
||||
dispatch_barrier_sync(self.mutableCodersAccessQueue, ^{
|
||||
self.mutableCoders = [coders mutableCopy];
|
||||
});
|
||||
}
|
||||
|
||||
#pragma mark - SDWebImageCoder
|
||||
- (BOOL)canDecodeFromData:(NSData *)data {
|
||||
for (id<SDWebImageCoder> coder in self.coders) {
|
||||
if ([coder canDecodeFromData:data]) {
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (BOOL)canEncodeToFormat:(SDImageFormat)format {
|
||||
for (id<SDWebImageCoder> coder in self.coders) {
|
||||
if ([coder canEncodeToFormat:format]) {
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (UIImage *)decodedImageWithData:(NSData *)data {
|
||||
if (!data) {
|
||||
return nil;
|
||||
}
|
||||
for (id<SDWebImageCoder> coder in self.coders) {
|
||||
if ([coder canDecodeFromData:data]) {
|
||||
return [coder decodedImageWithData:data];
|
||||
}
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (UIImage *)decompressedImageWithImage:(UIImage *)image
|
||||
data:(NSData *__autoreleasing _Nullable *)data
|
||||
options:(nullable NSDictionary<NSString*, NSObject*>*)optionsDict {
|
||||
if (!image) {
|
||||
return nil;
|
||||
}
|
||||
for (id<SDWebImageCoder> coder in self.coders) {
|
||||
if ([coder canDecodeFromData:*data]) {
|
||||
return [coder decompressedImageWithImage:image data:data options:optionsDict];
|
||||
}
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (NSData *)encodedDataWithImage:(UIImage *)image format:(SDImageFormat)format {
|
||||
if (!image) {
|
||||
return nil;
|
||||
}
|
||||
for (id<SDWebImageCoder> coder in self.coders) {
|
||||
if ([coder canEncodeToFormat:format]) {
|
||||
return [coder encodedDataWithImage:image format:format];
|
||||
}
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
@end
|
|
@ -7,8 +7,7 @@
|
|||
*/
|
||||
|
||||
#import "SDWebImageCompat.h"
|
||||
|
||||
#import "objc/runtime.h"
|
||||
#import "UIImage+MultiFormat.h"
|
||||
|
||||
#if !__has_feature(objc_arc)
|
||||
#error SDWebImage is ARC only. Either turn on ARC for the project or use -fobjc-arc flag
|
||||
|
@ -30,16 +29,9 @@ inline UIImage *SDScaledImageForKey(NSString * _Nullable key, UIImage * _Nullabl
|
|||
}
|
||||
|
||||
UIImage *animatedImage = [UIImage animatedImageWithImages:scaledImages duration:image.duration];
|
||||
#ifdef SD_WEBP
|
||||
if (animatedImage) {
|
||||
SEL sd_webpLoopCount = NSSelectorFromString(@"sd_webpLoopCount");
|
||||
NSNumber *value = objc_getAssociatedObject(image, sd_webpLoopCount);
|
||||
NSInteger loopCount = value.integerValue;
|
||||
if (loopCount) {
|
||||
objc_setAssociatedObject(animatedImage, sd_webpLoopCount, @(loopCount), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
||||
}
|
||||
animatedImage.sd_imageLoopCount = image.sd_imageLoopCount;
|
||||
}
|
||||
#endif
|
||||
return animatedImage;
|
||||
} else {
|
||||
#if SD_WATCH
|
||||
|
|
|
@ -1,272 +0,0 @@
|
|||
/*
|
||||
* This file is part of the SDWebImage package.
|
||||
* (c) Olivier Poitrey <rs@dailymotion.com>
|
||||
* (c) james <https://github.com/mystcolor>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
#import "SDWebImageDecoder.h"
|
||||
|
||||
@implementation UIImage (ForceDecode)
|
||||
|
||||
#if SD_UIKIT || SD_WATCH
|
||||
static const size_t kBytesPerPixel = 4;
|
||||
static const size_t kBitsPerComponent = 8;
|
||||
|
||||
+ (nullable UIImage *)decodedImageWithImage:(nullable UIImage *)image {
|
||||
if (![UIImage shouldDecodeImage:image]) {
|
||||
return image;
|
||||
}
|
||||
|
||||
// autorelease the bitmap context and all vars to help system to free memory when there are memory warning.
|
||||
// on iOS7, do not forget to call [[SDImageCache sharedImageCache] clearMemory];
|
||||
@autoreleasepool{
|
||||
|
||||
CGImageRef imageRef = image.CGImage;
|
||||
CGColorSpaceRef colorspaceRef = [UIImage colorSpaceForImageRef:imageRef];
|
||||
|
||||
size_t width = CGImageGetWidth(imageRef);
|
||||
size_t height = CGImageGetHeight(imageRef);
|
||||
size_t bytesPerRow = kBytesPerPixel * width;
|
||||
|
||||
// kCGImageAlphaNone is not supported in CGBitmapContextCreate.
|
||||
// Since the original image here has no alpha info, use kCGImageAlphaNoneSkipLast
|
||||
// to create bitmap graphics contexts without alpha info.
|
||||
CGContextRef context = CGBitmapContextCreate(NULL,
|
||||
width,
|
||||
height,
|
||||
kBitsPerComponent,
|
||||
bytesPerRow,
|
||||
colorspaceRef,
|
||||
kCGBitmapByteOrderDefault|kCGImageAlphaNoneSkipLast);
|
||||
if (context == NULL) {
|
||||
return image;
|
||||
}
|
||||
|
||||
// Draw the image into the context and retrieve the new bitmap image without alpha
|
||||
CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef);
|
||||
CGImageRef imageRefWithoutAlpha = CGBitmapContextCreateImage(context);
|
||||
UIImage *imageWithoutAlpha = [UIImage imageWithCGImage:imageRefWithoutAlpha
|
||||
scale:image.scale
|
||||
orientation:image.imageOrientation];
|
||||
|
||||
CGContextRelease(context);
|
||||
CGImageRelease(imageRefWithoutAlpha);
|
||||
|
||||
return imageWithoutAlpha;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Defines the maximum size in MB of the decoded image when the flag `SDWebImageScaleDownLargeImages` is set
|
||||
* Suggested value for iPad1 and iPhone 3GS: 60.
|
||||
* Suggested value for iPad2 and iPhone 4: 120.
|
||||
* Suggested value for iPhone 3G and iPod 2 and earlier devices: 30.
|
||||
*/
|
||||
static const CGFloat kDestImageSizeMB = 60.0f;
|
||||
|
||||
/*
|
||||
* Defines the maximum size in MB of a tile used to decode image when the flag `SDWebImageScaleDownLargeImages` is set
|
||||
* Suggested value for iPad1 and iPhone 3GS: 20.
|
||||
* Suggested value for iPad2 and iPhone 4: 40.
|
||||
* Suggested value for iPhone 3G and iPod 2 and earlier devices: 10.
|
||||
*/
|
||||
static const CGFloat kSourceImageTileSizeMB = 20.0f;
|
||||
|
||||
static const CGFloat kBytesPerMB = 1024.0f * 1024.0f;
|
||||
static const CGFloat kPixelsPerMB = kBytesPerMB / kBytesPerPixel;
|
||||
static const CGFloat kDestTotalPixels = kDestImageSizeMB * kPixelsPerMB;
|
||||
static const CGFloat kTileTotalPixels = kSourceImageTileSizeMB * kPixelsPerMB;
|
||||
|
||||
static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to overlap the seems where tiles meet.
|
||||
|
||||
+ (nullable UIImage *)decodedAndScaledDownImageWithImage:(nullable UIImage *)image {
|
||||
if (![UIImage shouldDecodeImage:image]) {
|
||||
return image;
|
||||
}
|
||||
|
||||
if (![UIImage shouldScaleDownImage:image]) {
|
||||
return [UIImage decodedImageWithImage:image];
|
||||
}
|
||||
|
||||
CGContextRef destContext;
|
||||
|
||||
// autorelease the bitmap context and all vars to help system to free memory when there are memory warning.
|
||||
// on iOS7, do not forget to call [[SDImageCache sharedImageCache] clearMemory];
|
||||
@autoreleasepool {
|
||||
CGImageRef sourceImageRef = image.CGImage;
|
||||
|
||||
CGSize sourceResolution = CGSizeZero;
|
||||
sourceResolution.width = CGImageGetWidth(sourceImageRef);
|
||||
sourceResolution.height = CGImageGetHeight(sourceImageRef);
|
||||
float sourceTotalPixels = sourceResolution.width * sourceResolution.height;
|
||||
// Determine the scale ratio to apply to the input image
|
||||
// that results in an output image of the defined size.
|
||||
// see kDestImageSizeMB, and how it relates to destTotalPixels.
|
||||
float imageScale = kDestTotalPixels / sourceTotalPixels;
|
||||
CGSize destResolution = CGSizeZero;
|
||||
destResolution.width = (int)(sourceResolution.width*imageScale);
|
||||
destResolution.height = (int)(sourceResolution.height*imageScale);
|
||||
|
||||
// current color space
|
||||
CGColorSpaceRef colorspaceRef = [UIImage colorSpaceForImageRef:sourceImageRef];
|
||||
|
||||
size_t bytesPerRow = kBytesPerPixel * destResolution.width;
|
||||
|
||||
// kCGImageAlphaNone is not supported in CGBitmapContextCreate.
|
||||
// Since the original image here has no alpha info, use kCGImageAlphaNoneSkipLast
|
||||
// to create bitmap graphics contexts without alpha info.
|
||||
destContext = CGBitmapContextCreate(NULL,
|
||||
destResolution.width,
|
||||
destResolution.height,
|
||||
kBitsPerComponent,
|
||||
bytesPerRow,
|
||||
colorspaceRef,
|
||||
kCGBitmapByteOrderDefault|kCGImageAlphaNoneSkipLast);
|
||||
|
||||
if (destContext == NULL) {
|
||||
return image;
|
||||
}
|
||||
CGContextSetInterpolationQuality(destContext, kCGInterpolationHigh);
|
||||
|
||||
// Now define the size of the rectangle to be used for the
|
||||
// incremental blits from the input image to the output image.
|
||||
// we use a source tile width equal to the width of the source
|
||||
// image due to the way that iOS retrieves image data from disk.
|
||||
// iOS must decode an image from disk in full width 'bands', even
|
||||
// if current graphics context is clipped to a subrect within that
|
||||
// band. Therefore we fully utilize all of the pixel data that results
|
||||
// from a decoding opertion by achnoring our tile size to the full
|
||||
// width of the input image.
|
||||
CGRect sourceTile = CGRectZero;
|
||||
sourceTile.size.width = sourceResolution.width;
|
||||
// The source tile height is dynamic. Since we specified the size
|
||||
// of the source tile in MB, see how many rows of pixels high it
|
||||
// can be given the input image width.
|
||||
sourceTile.size.height = (int)(kTileTotalPixels / sourceTile.size.width );
|
||||
sourceTile.origin.x = 0.0f;
|
||||
// The output tile is the same proportions as the input tile, but
|
||||
// scaled to image scale.
|
||||
CGRect destTile;
|
||||
destTile.size.width = destResolution.width;
|
||||
destTile.size.height = sourceTile.size.height * imageScale;
|
||||
destTile.origin.x = 0.0f;
|
||||
// The source seem overlap is proportionate to the destination seem overlap.
|
||||
// this is the amount of pixels to overlap each tile as we assemble the ouput image.
|
||||
float sourceSeemOverlap = (int)((kDestSeemOverlap/destResolution.height)*sourceResolution.height);
|
||||
CGImageRef sourceTileImageRef;
|
||||
// calculate the number of read/write operations required to assemble the
|
||||
// output image.
|
||||
int iterations = (int)( sourceResolution.height / sourceTile.size.height );
|
||||
// If tile height doesn't divide the image height evenly, add another iteration
|
||||
// to account for the remaining pixels.
|
||||
int remainder = (int)sourceResolution.height % (int)sourceTile.size.height;
|
||||
if(remainder) {
|
||||
iterations++;
|
||||
}
|
||||
// Add seem overlaps to the tiles, but save the original tile height for y coordinate calculations.
|
||||
float sourceTileHeightMinusOverlap = sourceTile.size.height;
|
||||
sourceTile.size.height += sourceSeemOverlap;
|
||||
destTile.size.height += kDestSeemOverlap;
|
||||
for( int y = 0; y < iterations; ++y ) {
|
||||
@autoreleasepool {
|
||||
sourceTile.origin.y = y * sourceTileHeightMinusOverlap + sourceSeemOverlap;
|
||||
destTile.origin.y = destResolution.height - (( y + 1 ) * sourceTileHeightMinusOverlap * imageScale + kDestSeemOverlap);
|
||||
sourceTileImageRef = CGImageCreateWithImageInRect( sourceImageRef, sourceTile );
|
||||
if( y == iterations - 1 && remainder ) {
|
||||
float dify = destTile.size.height;
|
||||
destTile.size.height = CGImageGetHeight( sourceTileImageRef ) * imageScale;
|
||||
dify -= destTile.size.height;
|
||||
destTile.origin.y += dify;
|
||||
}
|
||||
CGContextDrawImage( destContext, destTile, sourceTileImageRef );
|
||||
CGImageRelease( sourceTileImageRef );
|
||||
}
|
||||
}
|
||||
|
||||
CGImageRef destImageRef = CGBitmapContextCreateImage(destContext);
|
||||
CGContextRelease(destContext);
|
||||
if (destImageRef == NULL) {
|
||||
return image;
|
||||
}
|
||||
UIImage *destImage = [UIImage imageWithCGImage:destImageRef scale:image.scale orientation:image.imageOrientation];
|
||||
CGImageRelease(destImageRef);
|
||||
if (destImage == nil) {
|
||||
return image;
|
||||
}
|
||||
return destImage;
|
||||
}
|
||||
}
|
||||
|
||||
+ (BOOL)shouldDecodeImage:(nullable UIImage *)image {
|
||||
// Prevent "CGBitmapContextCreateImage: invalid context 0x0" error
|
||||
if (image == nil) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
// do not decode animated images
|
||||
if (image.images != nil) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
CGImageRef imageRef = image.CGImage;
|
||||
|
||||
CGImageAlphaInfo alpha = CGImageGetAlphaInfo(imageRef);
|
||||
BOOL anyAlpha = (alpha == kCGImageAlphaFirst ||
|
||||
alpha == kCGImageAlphaLast ||
|
||||
alpha == kCGImageAlphaPremultipliedFirst ||
|
||||
alpha == kCGImageAlphaPremultipliedLast);
|
||||
// do not decode images with alpha
|
||||
if (anyAlpha) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
+ (BOOL)shouldScaleDownImage:(nonnull UIImage *)image {
|
||||
BOOL shouldScaleDown = YES;
|
||||
|
||||
CGImageRef sourceImageRef = image.CGImage;
|
||||
CGSize sourceResolution = CGSizeZero;
|
||||
sourceResolution.width = CGImageGetWidth(sourceImageRef);
|
||||
sourceResolution.height = CGImageGetHeight(sourceImageRef);
|
||||
float sourceTotalPixels = sourceResolution.width * sourceResolution.height;
|
||||
float imageScale = kDestTotalPixels / sourceTotalPixels;
|
||||
if (imageScale < 1) {
|
||||
shouldScaleDown = YES;
|
||||
} else {
|
||||
shouldScaleDown = NO;
|
||||
}
|
||||
|
||||
return shouldScaleDown;
|
||||
}
|
||||
|
||||
+ (CGColorSpaceRef)colorSpaceForImageRef:(CGImageRef)imageRef {
|
||||
// current
|
||||
CGColorSpaceModel imageColorSpaceModel = CGColorSpaceGetModel(CGImageGetColorSpace(imageRef));
|
||||
CGColorSpaceRef colorspaceRef = CGImageGetColorSpace(imageRef);
|
||||
|
||||
BOOL unsupportedColorSpace = (imageColorSpaceModel == kCGColorSpaceModelUnknown ||
|
||||
imageColorSpaceModel == kCGColorSpaceModelMonochrome ||
|
||||
imageColorSpaceModel == kCGColorSpaceModelCMYK ||
|
||||
imageColorSpaceModel == kCGColorSpaceModelIndexed);
|
||||
if (unsupportedColorSpace) {
|
||||
colorspaceRef = CGColorSpaceCreateDeviceRGB();
|
||||
CFAutorelease(colorspaceRef);
|
||||
}
|
||||
return colorspaceRef;
|
||||
}
|
||||
#elif SD_MAC
|
||||
+ (nullable UIImage *)decodedImageWithImage:(nullable UIImage *)image {
|
||||
return image;
|
||||
}
|
||||
|
||||
+ (nullable UIImage *)decodedAndScaledDownImageWithImage:(nullable UIImage *)image {
|
||||
return image;
|
||||
}
|
||||
#endif
|
||||
|
||||
@end
|
|
@ -111,13 +111,11 @@ typedef SDHTTPHeadersDictionary * _Nullable (^SDWebImageDownloaderHeadersFilterB
|
|||
*/
|
||||
@property (readonly, nonatomic) NSUInteger currentDownloadCount;
|
||||
|
||||
|
||||
/**
|
||||
* The timeout value (in seconds) for the download operation. Default: 15.0.
|
||||
*/
|
||||
@property (assign, nonatomic) NSTimeInterval downloadTimeout;
|
||||
|
||||
|
||||
/**
|
||||
* The configuration in use by the internal NSURLSession.
|
||||
* Mutating this object directly has no effect.
|
||||
|
|
|
@ -7,11 +7,9 @@
|
|||
*/
|
||||
|
||||
#import "SDWebImageDownloaderOperation.h"
|
||||
#import "SDWebImageDecoder.h"
|
||||
#import "UIImage+MultiFormat.h"
|
||||
#import <ImageIO/ImageIO.h>
|
||||
#import "SDWebImageManager.h"
|
||||
#import "NSImage+WebCache.h"
|
||||
#import "SDWebImageCodersManager.h"
|
||||
|
||||
NSString *const SDWebImageDownloadStartNotification = @"SDWebImageDownloadStartNotification";
|
||||
NSString *const SDWebImageDownloadReceiveResponseNotification = @"SDWebImageDownloadReceiveResponseNotification";
|
||||
|
@ -46,15 +44,11 @@ typedef NSMutableDictionary<NSString *, id> SDCallbacksDictionary;
|
|||
@property (assign, nonatomic) UIBackgroundTaskIdentifier backgroundTaskId;
|
||||
#endif
|
||||
|
||||
@property (strong, nonatomic, nullable) id<SDWebImageProgressiveCoder> progressiveCoder;
|
||||
|
||||
@end
|
||||
|
||||
@implementation SDWebImageDownloaderOperation {
|
||||
size_t _width, _height;
|
||||
#if SD_UIKIT || SD_WATCH
|
||||
UIImageOrientation _orientation;
|
||||
#endif
|
||||
CGImageSourceRef _imageSource;
|
||||
}
|
||||
@implementation SDWebImageDownloaderOperation
|
||||
|
||||
@synthesize executing = _executing;
|
||||
@synthesize finished = _finished;
|
||||
|
@ -82,10 +76,6 @@ typedef NSMutableDictionary<NSString *, id> SDCallbacksDictionary;
|
|||
|
||||
- (void)dealloc {
|
||||
SDDispatchQueueRelease(_barrierQueue);
|
||||
if (_imageSource) {
|
||||
CFRelease(_imageSource);
|
||||
_imageSource = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
- (nullable id)addHandlersForProgress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
|
||||
|
@ -329,9 +319,6 @@ didReceiveResponse:(NSURLResponse *)response
|
|||
[self.imageData appendData:data];
|
||||
|
||||
if ((self.options & SDWebImageDownloaderProgressiveDownload) && self.expectedSize > 0) {
|
||||
// The following code is from http://www.cocoaintheshell.com/2011/05/progressive-images-download-imageio/
|
||||
// Thanks to the author @Nyx0uf
|
||||
|
||||
// Get the image data
|
||||
NSData *imageData = [self.imageData copy];
|
||||
// Get the total bytes downloaded
|
||||
|
@ -339,83 +326,25 @@ didReceiveResponse:(NSURLResponse *)response
|
|||
// Get the finish status
|
||||
BOOL finished = (totalSize >= self.expectedSize);
|
||||
|
||||
if (!_imageSource) {
|
||||
_imageSource = CGImageSourceCreateIncremental(NULL);
|
||||
}
|
||||
// Update the data source, we must pass ALL the data, not just the new bytes
|
||||
CGImageSourceUpdateData(_imageSource, (__bridge CFDataRef)imageData, finished);
|
||||
|
||||
if (_width + _height == 0) {
|
||||
CFDictionaryRef properties = CGImageSourceCopyPropertiesAtIndex(_imageSource, 0, NULL);
|
||||
if (properties) {
|
||||
NSInteger orientationValue = -1;
|
||||
CFTypeRef val = CFDictionaryGetValue(properties, kCGImagePropertyPixelHeight);
|
||||
if (val) CFNumberGetValue(val, kCFNumberLongType, &_height);
|
||||
val = CFDictionaryGetValue(properties, kCGImagePropertyPixelWidth);
|
||||
if (val) CFNumberGetValue(val, kCFNumberLongType, &_width);
|
||||
val = CFDictionaryGetValue(properties, kCGImagePropertyOrientation);
|
||||
if (val) CFNumberGetValue(val, kCFNumberNSIntegerType, &orientationValue);
|
||||
CFRelease(properties);
|
||||
|
||||
// When we draw to Core Graphics, we lose orientation information,
|
||||
// which means the image below born of initWithCGIImage will be
|
||||
// oriented incorrectly sometimes. (Unlike the image born of initWithData
|
||||
// in didCompleteWithError.) So save it here and pass it on later.
|
||||
#if SD_UIKIT || SD_WATCH
|
||||
_orientation = [[self class] orientationFromPropertyValue:(orientationValue == -1 ? 1 : orientationValue)];
|
||||
#endif
|
||||
if (!self.progressiveCoder) {
|
||||
// We need to create a new instance for progressive decoding to avoid conflicts
|
||||
for (id<SDWebImageCoder>coder in [SDWebImageCodersManager sharedInstance].coders) {
|
||||
if ([coder conformsToProtocol:@protocol(SDWebImageProgressiveCoder)] &&
|
||||
[((id<SDWebImageProgressiveCoder>)coder) canIncrementallyDecodeFromData:imageData]) {
|
||||
self.progressiveCoder = [[[coder class] alloc] init];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_width + _height > 0 && !finished) {
|
||||
// Create the image
|
||||
CGImageRef partialImageRef = CGImageSourceCreateImageAtIndex(_imageSource, 0, NULL);
|
||||
UIImage *image = [self.progressiveCoder incrementallyDecodedImageWithData:imageData finished:finished];
|
||||
if (image) {
|
||||
NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:self.request.URL];
|
||||
image = [self scaledImageForKey:key image:image];
|
||||
if (self.shouldDecompressImages) {
|
||||
image = [[SDWebImageCodersManager sharedInstance] decompressedImageWithImage:image data:&data options:@{SDWebImageCoderScaleDownLargeImagesKey: @(NO)}];
|
||||
}
|
||||
|
||||
#if SD_UIKIT || SD_WATCH
|
||||
// Workaround for iOS anamorphic image
|
||||
if (partialImageRef) {
|
||||
const size_t partialHeight = CGImageGetHeight(partialImageRef);
|
||||
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
|
||||
CGContextRef bmContext = CGBitmapContextCreate(NULL, _width, _height, 8, _width * 4, colorSpace, kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedFirst);
|
||||
CGColorSpaceRelease(colorSpace);
|
||||
if (bmContext) {
|
||||
CGContextDrawImage(bmContext, (CGRect){.origin.x = 0.0f, .origin.y = 0.0f, .size.width = _width, .size.height = partialHeight}, partialImageRef);
|
||||
CGImageRelease(partialImageRef);
|
||||
partialImageRef = CGBitmapContextCreateImage(bmContext);
|
||||
CGContextRelease(bmContext);
|
||||
}
|
||||
else {
|
||||
CGImageRelease(partialImageRef);
|
||||
partialImageRef = nil;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (partialImageRef) {
|
||||
#if SD_UIKIT || SD_WATCH
|
||||
UIImage *image = [UIImage imageWithCGImage:partialImageRef scale:1 orientation:_orientation];
|
||||
#elif SD_MAC
|
||||
UIImage *image = [[UIImage alloc] initWithCGImage:partialImageRef size:NSZeroSize];
|
||||
#endif
|
||||
CGImageRelease(partialImageRef);
|
||||
NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:self.request.URL];
|
||||
UIImage *scaledImage = [self scaledImageForKey:key image:image];
|
||||
if (self.shouldDecompressImages) {
|
||||
image = [UIImage decodedImageWithImage:scaledImage];
|
||||
}
|
||||
else {
|
||||
image = scaledImage;
|
||||
}
|
||||
|
||||
[self callCompletionBlocksWithImage:image imageData:nil error:nil finished:NO];
|
||||
}
|
||||
}
|
||||
|
||||
if (finished) {
|
||||
if (_imageSource) {
|
||||
CFRelease(_imageSource);
|
||||
_imageSource = NULL;
|
||||
}
|
||||
[self callCompletionBlocksWithImage:image imageData:nil error:nil finished:NO];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -463,7 +392,6 @@ didReceiveResponse:(NSURLResponse *)response
|
|||
*/
|
||||
NSData *imageData = [self.imageData copy];
|
||||
if (imageData) {
|
||||
UIImage *image = [UIImage sd_imageWithData:imageData];
|
||||
/** if you specified to only use cached data via `SDWebImageDownloaderIgnoreCachedResponse`,
|
||||
* then we should check if the cached data is equal to image data
|
||||
*/
|
||||
|
@ -471,6 +399,7 @@ didReceiveResponse:(NSURLResponse *)response
|
|||
// call completion block with nil
|
||||
[self callCompletionBlocksWithImage:nil imageData:nil error:nil finished:YES];
|
||||
} else {
|
||||
UIImage *image = [[SDWebImageCodersManager sharedInstance] decodedImageWithData:imageData];
|
||||
NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:self.request.URL];
|
||||
image = [self scaledImageForKey:key image:image];
|
||||
|
||||
|
@ -489,14 +418,8 @@ didReceiveResponse:(NSURLResponse *)response
|
|||
|
||||
if (shouldDecode) {
|
||||
if (self.shouldDecompressImages) {
|
||||
if (self.options & SDWebImageDownloaderScaleDownLargeImages) {
|
||||
#if SD_UIKIT || SD_WATCH
|
||||
image = [UIImage decodedAndScaledDownImageWithImage:image];
|
||||
imageData = UIImagePNGRepresentation(image);
|
||||
#endif
|
||||
} else {
|
||||
image = [UIImage decodedImageWithImage:image];
|
||||
}
|
||||
BOOL shouldScaleDown = self.options & SDWebImageDownloaderScaleDownLargeImages;
|
||||
image = [[SDWebImageCodersManager sharedInstance] decompressedImageWithImage:image data:&imageData options:@{SDWebImageCoderScaleDownLargeImagesKey: @(shouldScaleDown)}];
|
||||
}
|
||||
}
|
||||
if (CGSizeEqualToSize(image.size, CGSizeZero)) {
|
||||
|
@ -544,32 +467,6 @@ didReceiveResponse:(NSURLResponse *)response
|
|||
}
|
||||
|
||||
#pragma mark Helper methods
|
||||
|
||||
#if SD_UIKIT || SD_WATCH
|
||||
+ (UIImageOrientation)orientationFromPropertyValue:(NSInteger)value {
|
||||
switch (value) {
|
||||
case 1:
|
||||
return UIImageOrientationUp;
|
||||
case 3:
|
||||
return UIImageOrientationDown;
|
||||
case 8:
|
||||
return UIImageOrientationLeft;
|
||||
case 6:
|
||||
return UIImageOrientationRight;
|
||||
case 2:
|
||||
return UIImageOrientationUpMirrored;
|
||||
case 4:
|
||||
return UIImageOrientationDownMirrored;
|
||||
case 5:
|
||||
return UIImageOrientationLeftMirrored;
|
||||
case 7:
|
||||
return UIImageOrientationRightMirrored;
|
||||
default:
|
||||
return UIImageOrientationUp;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
- (nullable UIImage *)scaledImageForKey:(nullable NSString *)key image:(nullable UIImage *)image {
|
||||
return SDScaledImageForKey(key, image);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* This file is part of the SDWebImage package.
|
||||
* (c) Olivier Poitrey <rs@dailymotion.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "SDWebImageCoder.h"
|
||||
|
||||
/**
|
||||
Built in coder that supports GIF
|
||||
*/
|
||||
@interface SDWebImageGIFCoder : NSObject <SDWebImageCoder>
|
||||
|
||||
+ (nonnull instancetype)sharedCoder;
|
||||
|
||||
@end
|
|
@ -0,0 +1,117 @@
|
|||
/*
|
||||
* This file is part of the SDWebImage package.
|
||||
* (c) Olivier Poitrey <rs@dailymotion.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
#import "SDWebImageGIFCoder.h"
|
||||
#import "NSImage+WebCache.h"
|
||||
#import <ImageIO/ImageIO.h>
|
||||
#import "NSData+ImageContentType.h"
|
||||
|
||||
@implementation SDWebImageGIFCoder
|
||||
|
||||
+ (instancetype)sharedCoder {
|
||||
static SDWebImageGIFCoder *coder;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
coder = [[SDWebImageGIFCoder alloc] init];
|
||||
});
|
||||
return coder;
|
||||
}
|
||||
|
||||
#pragma mark - Decode
|
||||
- (BOOL)canDecodeFromData:(nullable NSData *)data {
|
||||
return ([NSData sd_imageFormatForImageData:data] == SDImageFormatGIF);
|
||||
}
|
||||
|
||||
- (UIImage *)decodedImageWithData:(NSData *)data {
|
||||
if (!data) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
#if SD_MAC
|
||||
return [[UIImage alloc] initWithData:data];
|
||||
#else
|
||||
|
||||
CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL);
|
||||
|
||||
size_t count = CGImageSourceGetCount(source);
|
||||
|
||||
UIImage *staticImage;
|
||||
|
||||
if (count <= 1) {
|
||||
staticImage = [[UIImage alloc] initWithData:data];
|
||||
} else {
|
||||
// we will only retrieve the 1st frame. the full GIF support is available via the FLAnimatedImageView category.
|
||||
// this here is only code to allow drawing animated images as static ones
|
||||
#if SD_WATCH
|
||||
CGFloat scale = 1;
|
||||
scale = [WKInterfaceDevice currentDevice].screenScale;
|
||||
#elif SD_UIKIT
|
||||
CGFloat scale = 1;
|
||||
scale = [UIScreen mainScreen].scale;
|
||||
#endif
|
||||
|
||||
CGImageRef CGImage = CGImageSourceCreateImageAtIndex(source, 0, NULL);
|
||||
#if SD_UIKIT || SD_WATCH
|
||||
UIImage *frameImage = [UIImage imageWithCGImage:CGImage scale:scale orientation:UIImageOrientationUp];
|
||||
staticImage = [UIImage animatedImageWithImages:@[frameImage] duration:0.0f];
|
||||
#endif
|
||||
CGImageRelease(CGImage);
|
||||
}
|
||||
|
||||
CFRelease(source);
|
||||
|
||||
return staticImage;
|
||||
#endif
|
||||
}
|
||||
|
||||
- (UIImage *)decompressedImageWithImage:(UIImage *)image
|
||||
data:(NSData *__autoreleasing _Nullable *)data
|
||||
options:(nullable NSDictionary<NSString*, NSObject*>*)optionsDict {
|
||||
// GIF do not decompress
|
||||
return image;
|
||||
}
|
||||
|
||||
#pragma mark - Encode
|
||||
- (BOOL)canEncodeToFormat:(SDImageFormat)format {
|
||||
return (format == SDImageFormatGIF);
|
||||
}
|
||||
|
||||
- (NSData *)encodedDataWithImage:(UIImage *)image format:(SDImageFormat)format {
|
||||
if (!image) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
if (format != SDImageFormatGIF) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSMutableData *imageData = [NSMutableData data];
|
||||
CFStringRef imageUTType = [NSData sd_UTTypeFromSDImageFormat:format];
|
||||
|
||||
// Create an image destination.
|
||||
CGImageDestinationRef imageDestination = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)imageData, imageUTType, 1, NULL);
|
||||
if (!imageDestination) {
|
||||
// Handle failure.
|
||||
return nil;
|
||||
}
|
||||
|
||||
// Add your image to the destination.
|
||||
CGImageDestinationAddImage(imageDestination, image.CGImage, nil);
|
||||
|
||||
// Finalize the destination.
|
||||
if (CGImageDestinationFinalize(imageDestination) == NO) {
|
||||
// Handle failure.
|
||||
imageData = nil;
|
||||
}
|
||||
|
||||
CFRelease(imageDestination);
|
||||
|
||||
return [imageData copy];
|
||||
}
|
||||
|
||||
@end
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* This file is part of the SDWebImage package.
|
||||
* (c) Olivier Poitrey <rs@dailymotion.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "SDWebImageCoder.h"
|
||||
|
||||
/**
|
||||
Built in coder that supports PNG, JPEG, TIFF, includes support for progressive decoding
|
||||
*/
|
||||
@interface SDWebImageImageIOCoder : NSObject <SDWebImageProgressiveCoder>
|
||||
|
||||
+ (nonnull instancetype)sharedCoder;
|
||||
|
||||
@end
|
|
@ -0,0 +1,579 @@
|
|||
/*
|
||||
* This file is part of the SDWebImage package.
|
||||
* (c) Olivier Poitrey <rs@dailymotion.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
#import "SDWebImageImageIOCoder.h"
|
||||
#import "NSImage+WebCache.h"
|
||||
#import <ImageIO/ImageIO.h>
|
||||
#import "NSData+ImageContentType.h"
|
||||
|
||||
#if SD_UIKIT || SD_WATCH
|
||||
static const size_t kBytesPerPixel = 4;
|
||||
static const size_t kBitsPerComponent = 8;
|
||||
|
||||
/*
|
||||
* Defines the maximum size in MB of the decoded image when the flag `SDWebImageScaleDownLargeImages` is set
|
||||
* Suggested value for iPad1 and iPhone 3GS: 60.
|
||||
* Suggested value for iPad2 and iPhone 4: 120.
|
||||
* Suggested value for iPhone 3G and iPod 2 and earlier devices: 30.
|
||||
*/
|
||||
static const CGFloat kDestImageSizeMB = 60.0f;
|
||||
|
||||
/*
|
||||
* Defines the maximum size in MB of a tile used to decode image when the flag `SDWebImageScaleDownLargeImages` is set
|
||||
* Suggested value for iPad1 and iPhone 3GS: 20.
|
||||
* Suggested value for iPad2 and iPhone 4: 40.
|
||||
* Suggested value for iPhone 3G and iPod 2 and earlier devices: 10.
|
||||
*/
|
||||
static const CGFloat kSourceImageTileSizeMB = 20.0f;
|
||||
|
||||
static const CGFloat kBytesPerMB = 1024.0f * 1024.0f;
|
||||
static const CGFloat kPixelsPerMB = kBytesPerMB / kBytesPerPixel;
|
||||
static const CGFloat kDestTotalPixels = kDestImageSizeMB * kPixelsPerMB;
|
||||
static const CGFloat kTileTotalPixels = kSourceImageTileSizeMB * kPixelsPerMB;
|
||||
|
||||
static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to overlap the seems where tiles meet.
|
||||
#endif
|
||||
|
||||
@implementation SDWebImageImageIOCoder {
|
||||
size_t _width, _height;
|
||||
#if SD_UIKIT || SD_WATCH
|
||||
UIImageOrientation _orientation;
|
||||
#endif
|
||||
CGImageSourceRef _imageSource;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
if (_imageSource) {
|
||||
CFRelease(_imageSource);
|
||||
_imageSource = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
+ (instancetype)sharedCoder {
|
||||
static SDWebImageImageIOCoder *coder;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
coder = [[SDWebImageImageIOCoder alloc] init];
|
||||
});
|
||||
return coder;
|
||||
}
|
||||
|
||||
#pragma mark - Decode
|
||||
- (BOOL)canDecodeFromData:(nullable NSData *)data {
|
||||
switch ([NSData sd_imageFormatForImageData:data]) {
|
||||
// Do not support GIF and WebP decoding
|
||||
case SDImageFormatGIF:
|
||||
case SDImageFormatWebP:
|
||||
return NO;
|
||||
default:
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)canIncrementallyDecodeFromData:(NSData *)data {
|
||||
switch ([NSData sd_imageFormatForImageData:data]) {
|
||||
// Support static GIF progressive decoding
|
||||
case SDImageFormatWebP:
|
||||
return NO;
|
||||
default:
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
|
||||
- (UIImage *)decodedImageWithData:(NSData *)data {
|
||||
if (!data) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
UIImage *image = [[UIImage alloc] initWithData:data];
|
||||
#if SD_UIKIT || SD_WATCH
|
||||
UIImageOrientation orientation = [[self class] sd_imageOrientationFromImageData:data];
|
||||
if (orientation != UIImageOrientationUp) {
|
||||
image = [UIImage imageWithCGImage:image.CGImage
|
||||
scale:image.scale
|
||||
orientation:orientation];
|
||||
}
|
||||
#endif
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
- (UIImage *)incrementallyDecodedImageWithData:(NSData *)data finished:(BOOL)finished {
|
||||
if (!_imageSource) {
|
||||
_imageSource = CGImageSourceCreateIncremental(NULL);
|
||||
}
|
||||
UIImage *image;
|
||||
|
||||
// The following code is from http://www.cocoaintheshell.com/2011/05/progressive-images-download-imageio/
|
||||
// Thanks to the author @Nyx0uf
|
||||
|
||||
// Update the data source, we must pass ALL the data, not just the new bytes
|
||||
CGImageSourceUpdateData(_imageSource, (__bridge CFDataRef)data, finished);
|
||||
|
||||
if (_width + _height == 0) {
|
||||
CFDictionaryRef properties = CGImageSourceCopyPropertiesAtIndex(_imageSource, 0, NULL);
|
||||
if (properties) {
|
||||
NSInteger orientationValue = 1;
|
||||
CFTypeRef val = CFDictionaryGetValue(properties, kCGImagePropertyPixelHeight);
|
||||
if (val) CFNumberGetValue(val, kCFNumberLongType, &_height);
|
||||
val = CFDictionaryGetValue(properties, kCGImagePropertyPixelWidth);
|
||||
if (val) CFNumberGetValue(val, kCFNumberLongType, &_width);
|
||||
val = CFDictionaryGetValue(properties, kCGImagePropertyOrientation);
|
||||
if (val) CFNumberGetValue(val, kCFNumberNSIntegerType, &orientationValue);
|
||||
CFRelease(properties);
|
||||
|
||||
// When we draw to Core Graphics, we lose orientation information,
|
||||
// which means the image below born of initWithCGIImage will be
|
||||
// oriented incorrectly sometimes. (Unlike the image born of initWithData
|
||||
// in didCompleteWithError.) So save it here and pass it on later.
|
||||
#if SD_UIKIT || SD_WATCH
|
||||
_orientation = [[self class] sd_imageOrientationFromEXIFOrientation:orientationValue];
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
if (_width + _height > 0) {
|
||||
// Create the image
|
||||
CGImageRef partialImageRef = CGImageSourceCreateImageAtIndex(_imageSource, 0, NULL);
|
||||
|
||||
#if SD_UIKIT || SD_WATCH
|
||||
// Workaround for iOS anamorphic image
|
||||
if (partialImageRef) {
|
||||
const size_t partialHeight = CGImageGetHeight(partialImageRef);
|
||||
CGColorSpaceRef colorSpace = SDCGColorSpaceGetDeviceRGB();
|
||||
CGContextRef bmContext = CGBitmapContextCreate(NULL, _width, _height, 8, _width * 4, colorSpace, kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedFirst);
|
||||
if (bmContext) {
|
||||
CGContextDrawImage(bmContext, (CGRect){.origin.x = 0.0f, .origin.y = 0.0f, .size.width = _width, .size.height = partialHeight}, partialImageRef);
|
||||
CGImageRelease(partialImageRef);
|
||||
partialImageRef = CGBitmapContextCreateImage(bmContext);
|
||||
CGContextRelease(bmContext);
|
||||
}
|
||||
else {
|
||||
CGImageRelease(partialImageRef);
|
||||
partialImageRef = nil;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (partialImageRef) {
|
||||
#if SD_UIKIT || SD_WATCH
|
||||
image = [UIImage imageWithCGImage:partialImageRef scale:1 orientation:_orientation];
|
||||
#elif SD_MAC
|
||||
image = [[UIImage alloc] initWithCGImage:partialImageRef size:NSZeroSize];
|
||||
#endif
|
||||
CGImageRelease(partialImageRef);
|
||||
}
|
||||
}
|
||||
|
||||
if (finished) {
|
||||
if (_imageSource) {
|
||||
CFRelease(_imageSource);
|
||||
_imageSource = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
- (UIImage *)decompressedImageWithImage:(UIImage *)image
|
||||
data:(NSData *__autoreleasing _Nullable *)data
|
||||
options:(nullable NSDictionary<NSString*, NSObject*>*)optionsDict {
|
||||
#if SD_MAC
|
||||
return image;
|
||||
#endif
|
||||
#if SD_UIKIT || SD_WATCH
|
||||
BOOL shouldScaleDown = NO;
|
||||
if (optionsDict != nil) {
|
||||
NSNumber *scaleDownLargeImagesOption = nil;
|
||||
if ([optionsDict[SDWebImageCoderScaleDownLargeImagesKey] isKindOfClass:[NSNumber class]]) {
|
||||
scaleDownLargeImagesOption = (NSNumber *)optionsDict[SDWebImageCoderScaleDownLargeImagesKey];
|
||||
}
|
||||
if (scaleDownLargeImagesOption != nil) {
|
||||
shouldScaleDown = [scaleDownLargeImagesOption boolValue];
|
||||
}
|
||||
}
|
||||
if (!shouldScaleDown) {
|
||||
return [self sd_decompressedImageWithImage:image];
|
||||
} else {
|
||||
return [self sd_decompressedAndScaledDownImageWithImage:image];
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if SD_UIKIT || SD_WATCH
|
||||
- (nullable UIImage *)sd_decompressedImageWithImage:(nullable UIImage *)image {
|
||||
if (![[self class] shouldDecodeImage:image]) {
|
||||
return image;
|
||||
}
|
||||
|
||||
// autorelease the bitmap context and all vars to help system to free memory when there are memory warning.
|
||||
// on iOS7, do not forget to call [[SDImageCache sharedImageCache] clearMemory];
|
||||
@autoreleasepool{
|
||||
|
||||
CGImageRef imageRef = image.CGImage;
|
||||
CGColorSpaceRef colorspaceRef = [[self class] colorSpaceForImageRef:imageRef];
|
||||
|
||||
size_t width = CGImageGetWidth(imageRef);
|
||||
size_t height = CGImageGetHeight(imageRef);
|
||||
size_t bytesPerRow = kBytesPerPixel * width;
|
||||
|
||||
// kCGImageAlphaNone is not supported in CGBitmapContextCreate.
|
||||
// Since the original image here has no alpha info, use kCGImageAlphaNoneSkipLast
|
||||
// to create bitmap graphics contexts without alpha info.
|
||||
CGContextRef context = CGBitmapContextCreate(NULL,
|
||||
width,
|
||||
height,
|
||||
kBitsPerComponent,
|
||||
bytesPerRow,
|
||||
colorspaceRef,
|
||||
kCGBitmapByteOrderDefault|kCGImageAlphaNoneSkipLast);
|
||||
if (context == NULL) {
|
||||
return image;
|
||||
}
|
||||
|
||||
// Draw the image into the context and retrieve the new bitmap image without alpha
|
||||
CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef);
|
||||
CGImageRef imageRefWithoutAlpha = CGBitmapContextCreateImage(context);
|
||||
UIImage *imageWithoutAlpha = [UIImage imageWithCGImage:imageRefWithoutAlpha
|
||||
scale:image.scale
|
||||
orientation:image.imageOrientation];
|
||||
|
||||
CGContextRelease(context);
|
||||
CGImageRelease(imageRefWithoutAlpha);
|
||||
|
||||
return imageWithoutAlpha;
|
||||
}
|
||||
}
|
||||
|
||||
- (nullable UIImage *)sd_decompressedAndScaledDownImageWithImage:(nullable UIImage *)image {
|
||||
if (![[self class] shouldDecodeImage:image]) {
|
||||
return image;
|
||||
}
|
||||
|
||||
if (![[self class] shouldScaleDownImage:image]) {
|
||||
return [self sd_decompressedImageWithImage:image];
|
||||
}
|
||||
|
||||
CGContextRef destContext;
|
||||
|
||||
// autorelease the bitmap context and all vars to help system to free memory when there are memory warning.
|
||||
// on iOS7, do not forget to call [[SDImageCache sharedImageCache] clearMemory];
|
||||
@autoreleasepool {
|
||||
CGImageRef sourceImageRef = image.CGImage;
|
||||
|
||||
CGSize sourceResolution = CGSizeZero;
|
||||
sourceResolution.width = CGImageGetWidth(sourceImageRef);
|
||||
sourceResolution.height = CGImageGetHeight(sourceImageRef);
|
||||
float sourceTotalPixels = sourceResolution.width * sourceResolution.height;
|
||||
// Determine the scale ratio to apply to the input image
|
||||
// that results in an output image of the defined size.
|
||||
// see kDestImageSizeMB, and how it relates to destTotalPixels.
|
||||
float imageScale = kDestTotalPixels / sourceTotalPixels;
|
||||
CGSize destResolution = CGSizeZero;
|
||||
destResolution.width = (int)(sourceResolution.width*imageScale);
|
||||
destResolution.height = (int)(sourceResolution.height*imageScale);
|
||||
|
||||
// current color space
|
||||
CGColorSpaceRef colorspaceRef = [[self class] colorSpaceForImageRef:sourceImageRef];
|
||||
|
||||
size_t bytesPerRow = kBytesPerPixel * destResolution.width;
|
||||
|
||||
// kCGImageAlphaNone is not supported in CGBitmapContextCreate.
|
||||
// Since the original image here has no alpha info, use kCGImageAlphaNoneSkipLast
|
||||
// to create bitmap graphics contexts without alpha info.
|
||||
destContext = CGBitmapContextCreate(NULL,
|
||||
destResolution.width,
|
||||
destResolution.height,
|
||||
kBitsPerComponent,
|
||||
bytesPerRow,
|
||||
colorspaceRef,
|
||||
kCGBitmapByteOrderDefault|kCGImageAlphaNoneSkipLast);
|
||||
|
||||
if (destContext == NULL) {
|
||||
return image;
|
||||
}
|
||||
CGContextSetInterpolationQuality(destContext, kCGInterpolationHigh);
|
||||
|
||||
// Now define the size of the rectangle to be used for the
|
||||
// incremental blits from the input image to the output image.
|
||||
// we use a source tile width equal to the width of the source
|
||||
// image due to the way that iOS retrieves image data from disk.
|
||||
// iOS must decode an image from disk in full width 'bands', even
|
||||
// if current graphics context is clipped to a subrect within that
|
||||
// band. Therefore we fully utilize all of the pixel data that results
|
||||
// from a decoding opertion by achnoring our tile size to the full
|
||||
// width of the input image.
|
||||
CGRect sourceTile = CGRectZero;
|
||||
sourceTile.size.width = sourceResolution.width;
|
||||
// The source tile height is dynamic. Since we specified the size
|
||||
// of the source tile in MB, see how many rows of pixels high it
|
||||
// can be given the input image width.
|
||||
sourceTile.size.height = (int)(kTileTotalPixels / sourceTile.size.width );
|
||||
sourceTile.origin.x = 0.0f;
|
||||
// The output tile is the same proportions as the input tile, but
|
||||
// scaled to image scale.
|
||||
CGRect destTile;
|
||||
destTile.size.width = destResolution.width;
|
||||
destTile.size.height = sourceTile.size.height * imageScale;
|
||||
destTile.origin.x = 0.0f;
|
||||
// The source seem overlap is proportionate to the destination seem overlap.
|
||||
// this is the amount of pixels to overlap each tile as we assemble the ouput image.
|
||||
float sourceSeemOverlap = (int)((kDestSeemOverlap/destResolution.height)*sourceResolution.height);
|
||||
CGImageRef sourceTileImageRef;
|
||||
// calculate the number of read/write operations required to assemble the
|
||||
// output image.
|
||||
int iterations = (int)( sourceResolution.height / sourceTile.size.height );
|
||||
// If tile height doesn't divide the image height evenly, add another iteration
|
||||
// to account for the remaining pixels.
|
||||
int remainder = (int)sourceResolution.height % (int)sourceTile.size.height;
|
||||
if(remainder) {
|
||||
iterations++;
|
||||
}
|
||||
// Add seem overlaps to the tiles, but save the original tile height for y coordinate calculations.
|
||||
float sourceTileHeightMinusOverlap = sourceTile.size.height;
|
||||
sourceTile.size.height += sourceSeemOverlap;
|
||||
destTile.size.height += kDestSeemOverlap;
|
||||
for( int y = 0; y < iterations; ++y ) {
|
||||
@autoreleasepool {
|
||||
sourceTile.origin.y = y * sourceTileHeightMinusOverlap + sourceSeemOverlap;
|
||||
destTile.origin.y = destResolution.height - (( y + 1 ) * sourceTileHeightMinusOverlap * imageScale + kDestSeemOverlap);
|
||||
sourceTileImageRef = CGImageCreateWithImageInRect( sourceImageRef, sourceTile );
|
||||
if( y == iterations - 1 && remainder ) {
|
||||
float dify = destTile.size.height;
|
||||
destTile.size.height = CGImageGetHeight( sourceTileImageRef ) * imageScale;
|
||||
dify -= destTile.size.height;
|
||||
destTile.origin.y += dify;
|
||||
}
|
||||
CGContextDrawImage( destContext, destTile, sourceTileImageRef );
|
||||
CGImageRelease( sourceTileImageRef );
|
||||
}
|
||||
}
|
||||
|
||||
CGImageRef destImageRef = CGBitmapContextCreateImage(destContext);
|
||||
CGContextRelease(destContext);
|
||||
if (destImageRef == NULL) {
|
||||
return image;
|
||||
}
|
||||
UIImage *destImage = [UIImage imageWithCGImage:destImageRef scale:image.scale orientation:image.imageOrientation];
|
||||
CGImageRelease(destImageRef);
|
||||
if (destImage == nil) {
|
||||
return image;
|
||||
}
|
||||
return destImage;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#pragma mark - Encode
|
||||
- (BOOL)canEncodeToFormat:(SDImageFormat)format {
|
||||
switch (format) {
|
||||
// Do not support GIF and WebP encoding
|
||||
case SDImageFormatGIF:
|
||||
case SDImageFormatWebP:
|
||||
return NO;
|
||||
default:
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSData *)encodedDataWithImage:(UIImage *)image format:(SDImageFormat)format {
|
||||
if (!image) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
if (format == SDImageFormatUndefined) {
|
||||
BOOL hasAlpha = SDCGImageRefContainsAlpha(image.CGImage);
|
||||
if (hasAlpha) {
|
||||
format = SDImageFormatPNG;
|
||||
} else {
|
||||
format = SDImageFormatJPEG;
|
||||
}
|
||||
}
|
||||
|
||||
NSMutableData *imageData = [NSMutableData data];
|
||||
CFStringRef imageUTType = [NSData sd_UTTypeFromSDImageFormat:format];
|
||||
|
||||
// Create an image destination.
|
||||
CGImageDestinationRef imageDestination = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)imageData, imageUTType, 1, NULL);
|
||||
if (!imageDestination) {
|
||||
// Handle failure.
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSMutableDictionary *properties = [NSMutableDictionary dictionary];
|
||||
#if SD_UIKIT
|
||||
NSInteger exifOrientation = [[self class] sd_exifOrientationFromImageOrientation:image.imageOrientation];
|
||||
[properties setValue:@(exifOrientation) forKey:(__bridge_transfer NSString *)kCGImagePropertyOrientation];
|
||||
#endif
|
||||
|
||||
// Add your image to the destination.
|
||||
CGImageDestinationAddImage(imageDestination, image.CGImage, (__bridge CFDictionaryRef)properties);
|
||||
|
||||
// Finalize the destination.
|
||||
if (CGImageDestinationFinalize(imageDestination) == NO) {
|
||||
// Handle failure.
|
||||
imageData = nil;
|
||||
}
|
||||
|
||||
CFRelease(imageDestination);
|
||||
|
||||
return [imageData copy];
|
||||
}
|
||||
|
||||
#pragma mark - Helper
|
||||
+ (BOOL)shouldDecodeImage:(nullable UIImage *)image {
|
||||
// Prevent "CGBitmapContextCreateImage: invalid context 0x0" error
|
||||
if (image == nil) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
// do not decode animated images
|
||||
if (image.images != nil) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
CGImageRef imageRef = image.CGImage;
|
||||
|
||||
BOOL hasAlpha = SDCGImageRefContainsAlpha(imageRef);
|
||||
// do not decode images with alpha
|
||||
if (hasAlpha) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
#if SD_UIKIT || SD_WATCH
|
||||
#pragma mark EXIF orientation tag converter
|
||||
+ (UIImageOrientation)sd_imageOrientationFromImageData:(nonnull NSData *)imageData {
|
||||
UIImageOrientation result = UIImageOrientationUp;
|
||||
CGImageSourceRef imageSource = CGImageSourceCreateWithData((__bridge CFDataRef)imageData, NULL);
|
||||
if (imageSource) {
|
||||
CFDictionaryRef properties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, NULL);
|
||||
if (properties) {
|
||||
CFTypeRef val;
|
||||
NSInteger exifOrientation;
|
||||
val = CFDictionaryGetValue(properties, kCGImagePropertyOrientation);
|
||||
if (val) {
|
||||
CFNumberGetValue(val, kCFNumberNSIntegerType, &exifOrientation);
|
||||
result = [self sd_imageOrientationFromEXIFOrientation:exifOrientation];
|
||||
} // else - if it's not set it remains at up
|
||||
CFRelease((CFTypeRef) properties);
|
||||
} else {
|
||||
//NSLog(@"NO PROPERTIES, FAIL");
|
||||
}
|
||||
CFRelease(imageSource);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Convert an EXIF image orientation to an iOS one.
|
||||
+ (UIImageOrientation)sd_imageOrientationFromEXIFOrientation:(NSInteger)exifOrientation {
|
||||
UIImageOrientation imageOrientation = UIImageOrientationUp;
|
||||
switch (exifOrientation) {
|
||||
case kCGImagePropertyOrientationUp:
|
||||
imageOrientation = UIImageOrientationUp;
|
||||
break;
|
||||
case kCGImagePropertyOrientationDown:
|
||||
imageOrientation = UIImageOrientationDown;
|
||||
break;
|
||||
case kCGImagePropertyOrientationLeft:
|
||||
imageOrientation = UIImageOrientationLeft;
|
||||
break;
|
||||
case kCGImagePropertyOrientationRight:
|
||||
imageOrientation = UIImageOrientationRight;
|
||||
break;
|
||||
case kCGImagePropertyOrientationUpMirrored:
|
||||
imageOrientation = UIImageOrientationUpMirrored;
|
||||
break;
|
||||
case kCGImagePropertyOrientationDownMirrored:
|
||||
imageOrientation = UIImageOrientationDownMirrored;
|
||||
break;
|
||||
case kCGImagePropertyOrientationLeftMirrored:
|
||||
imageOrientation = UIImageOrientationLeftMirrored;
|
||||
break;
|
||||
case kCGImagePropertyOrientationRightMirrored:
|
||||
imageOrientation = UIImageOrientationRightMirrored;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return imageOrientation;
|
||||
}
|
||||
|
||||
// Convert an iOS orientation to an EXIF image orientation.
|
||||
+ (NSInteger)sd_exifOrientationFromImageOrientation:(UIImageOrientation)imageOrientation {
|
||||
NSInteger exifOrientation = kCGImagePropertyOrientationUp;
|
||||
switch (imageOrientation) {
|
||||
case UIImageOrientationUp:
|
||||
exifOrientation = kCGImagePropertyOrientationUp;
|
||||
break;
|
||||
case UIImageOrientationDown:
|
||||
exifOrientation = kCGImagePropertyOrientationDown;
|
||||
break;
|
||||
case UIImageOrientationLeft:
|
||||
exifOrientation = kCGImagePropertyOrientationLeft;
|
||||
break;
|
||||
case UIImageOrientationRight:
|
||||
exifOrientation = kCGImagePropertyOrientationRight;
|
||||
break;
|
||||
case UIImageOrientationUpMirrored:
|
||||
exifOrientation = kCGImagePropertyOrientationUpMirrored;
|
||||
break;
|
||||
case UIImageOrientationDownMirrored:
|
||||
exifOrientation = kCGImagePropertyOrientationDownMirrored;
|
||||
break;
|
||||
case UIImageOrientationLeftMirrored:
|
||||
exifOrientation = kCGImagePropertyOrientationLeftMirrored;
|
||||
break;
|
||||
case UIImageOrientationRightMirrored:
|
||||
exifOrientation = kCGImagePropertyOrientationRightMirrored;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return exifOrientation;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if SD_UIKIT || SD_WATCH
|
||||
+ (BOOL)shouldScaleDownImage:(nonnull UIImage *)image {
|
||||
BOOL shouldScaleDown = YES;
|
||||
|
||||
CGImageRef sourceImageRef = image.CGImage;
|
||||
CGSize sourceResolution = CGSizeZero;
|
||||
sourceResolution.width = CGImageGetWidth(sourceImageRef);
|
||||
sourceResolution.height = CGImageGetHeight(sourceImageRef);
|
||||
float sourceTotalPixels = sourceResolution.width * sourceResolution.height;
|
||||
float imageScale = kDestTotalPixels / sourceTotalPixels;
|
||||
if (imageScale < 1) {
|
||||
shouldScaleDown = YES;
|
||||
} else {
|
||||
shouldScaleDown = NO;
|
||||
}
|
||||
|
||||
return shouldScaleDown;
|
||||
}
|
||||
|
||||
+ (CGColorSpaceRef)colorSpaceForImageRef:(CGImageRef)imageRef {
|
||||
// current
|
||||
CGColorSpaceModel imageColorSpaceModel = CGColorSpaceGetModel(CGImageGetColorSpace(imageRef));
|
||||
CGColorSpaceRef colorspaceRef = CGImageGetColorSpace(imageRef);
|
||||
|
||||
BOOL unsupportedColorSpace = (imageColorSpaceModel == kCGColorSpaceModelUnknown ||
|
||||
imageColorSpaceModel == kCGColorSpaceModelMonochrome ||
|
||||
imageColorSpaceModel == kCGColorSpaceModelCMYK ||
|
||||
imageColorSpaceModel == kCGColorSpaceModelIndexed);
|
||||
if (unsupportedColorSpace) {
|
||||
colorspaceRef = SDCGColorSpaceGetDeviceRGB();
|
||||
}
|
||||
return colorspaceRef;
|
||||
}
|
||||
#endif
|
||||
|
||||
@end
|
|
@ -7,8 +7,8 @@
|
|||
*/
|
||||
|
||||
#import "SDWebImageManager.h"
|
||||
#import <objc/message.h>
|
||||
#import "NSImage+WebCache.h"
|
||||
#import <objc/message.h>
|
||||
|
||||
@interface SDWebImageCombinedOperation : NSObject <SDWebImageOperation>
|
||||
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* This file is part of the SDWebImage package.
|
||||
* (c) Olivier Poitrey <rs@dailymotion.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
#ifdef SD_WEBP
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "SDWebImageCoder.h"
|
||||
|
||||
/**
|
||||
Built in coder that supports WebP and animated WebP
|
||||
*/
|
||||
@interface SDWebImageWebPCoder : NSObject <SDWebImageProgressiveCoder>
|
||||
|
||||
+ (nonnull instancetype)sharedCoder;
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
|
@ -0,0 +1,561 @@
|
|||
/*
|
||||
* This file is part of the SDWebImage package.
|
||||
* (c) Olivier Poitrey <rs@dailymotion.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
#ifdef SD_WEBP
|
||||
|
||||
#import "SDWebImageWebPCoder.h"
|
||||
#import "NSImage+WebCache.h"
|
||||
#import "UIImage+MultiFormat.h"
|
||||
#import "NSData+ImageContentType.h"
|
||||
#import <ImageIO/ImageIO.h>
|
||||
#if __has_include(<webp/decode.h>) && __has_include(<webp/encode.h>) && __has_include(<webp/demux.h>) && __has_include(<webp/mux.h>)
|
||||
#import <webp/decode.h>
|
||||
#import <webp/encode.h>
|
||||
#import <webp/demux.h>
|
||||
#import <webp/mux.h>
|
||||
#else
|
||||
#import "webp/decode.h"
|
||||
#import "webp/encode.h"
|
||||
#import "webp/demux.h"
|
||||
#import "webp/mux.h"
|
||||
#endif
|
||||
|
||||
@implementation SDWebImageWebPCoder {
|
||||
WebPIDecoder *_idec;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
if (_idec) {
|
||||
WebPIDelete(_idec);
|
||||
_idec = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
+ (instancetype)sharedCoder {
|
||||
static SDWebImageWebPCoder *coder;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
coder = [[SDWebImageWebPCoder alloc] init];
|
||||
});
|
||||
return coder;
|
||||
}
|
||||
|
||||
#pragma mark - Decode
|
||||
- (BOOL)canDecodeFromData:(nullable NSData *)data {
|
||||
return ([NSData sd_imageFormatForImageData:data] == SDImageFormatWebP);
|
||||
}
|
||||
|
||||
- (BOOL)canIncrementallyDecodeFromData:(NSData *)data {
|
||||
return ([NSData sd_imageFormatForImageData:data] == SDImageFormatWebP);
|
||||
}
|
||||
|
||||
- (UIImage *)decodedImageWithData:(NSData *)data {
|
||||
if (!data) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
WebPData webpData;
|
||||
WebPDataInit(&webpData);
|
||||
webpData.bytes = data.bytes;
|
||||
webpData.size = data.length;
|
||||
WebPDemuxer *demuxer = WebPDemux(&webpData);
|
||||
if (!demuxer) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
uint32_t flags = WebPDemuxGetI(demuxer, WEBP_FF_FORMAT_FLAGS);
|
||||
#if SD_UIKIT || SD_WATCH
|
||||
int loopCount = WebPDemuxGetI(demuxer, WEBP_FF_LOOP_COUNT);
|
||||
int frameCount = WebPDemuxGetI(demuxer, WEBP_FF_FRAME_COUNT);
|
||||
#endif
|
||||
int canvasWidth = WebPDemuxGetI(demuxer, WEBP_FF_CANVAS_WIDTH);
|
||||
int canvasHeight = WebPDemuxGetI(demuxer, WEBP_FF_CANVAS_HEIGHT);
|
||||
CGBitmapInfo bitmapInfo;
|
||||
if (!(flags & ALPHA_FLAG)) {
|
||||
bitmapInfo = kCGBitmapByteOrder32Big | kCGImageAlphaNoneSkipLast;
|
||||
} else {
|
||||
bitmapInfo = kCGBitmapByteOrder32Big | kCGImageAlphaPremultipliedLast;
|
||||
}
|
||||
CGContextRef canvas = CGBitmapContextCreate(NULL, canvasWidth, canvasHeight, 8, 0, SDCGColorSpaceGetDeviceRGB(), bitmapInfo);
|
||||
if (!canvas) {
|
||||
WebPDemuxDelete(demuxer);
|
||||
return nil;
|
||||
}
|
||||
|
||||
if (!(flags & ANIMATION_FLAG)) {
|
||||
// for static single webp image
|
||||
UIImage *staticImage = [self sd_rawWebpImageWithData:webpData];
|
||||
if (staticImage) {
|
||||
// draw on CGBitmapContext can reduce memory usage
|
||||
CGImageRef imageRef = staticImage.CGImage;
|
||||
size_t width = CGImageGetWidth(imageRef);
|
||||
size_t height = CGImageGetHeight(imageRef);
|
||||
CGContextDrawImage(canvas, CGRectMake(0, 0, width, height), imageRef);
|
||||
CGImageRef newImageRef = CGBitmapContextCreateImage(canvas);
|
||||
#if SD_UIKIT || SD_WATCH
|
||||
staticImage = [[UIImage alloc] initWithCGImage:newImageRef];
|
||||
#else
|
||||
staticImage = [[UIImage alloc] initWithCGImage:newImageRef size:NSZeroSize];
|
||||
#endif
|
||||
CGImageRelease(newImageRef);
|
||||
}
|
||||
WebPDemuxDelete(demuxer);
|
||||
CGContextRelease(canvas);
|
||||
return staticImage;
|
||||
}
|
||||
|
||||
// for animated webp image
|
||||
WebPIterator iter;
|
||||
if (!WebPDemuxGetFrame(demuxer, 1, &iter)) {
|
||||
WebPDemuxReleaseIterator(&iter);
|
||||
WebPDemuxDelete(demuxer);
|
||||
CGContextRelease(canvas);
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSMutableArray<UIImage *> *images = [NSMutableArray array];
|
||||
#if SD_UIKIT || SD_WATCH
|
||||
NSTimeInterval totalDuration = 0;
|
||||
int durations[frameCount];
|
||||
#endif
|
||||
|
||||
do {
|
||||
@autoreleasepool {
|
||||
UIImage *image;
|
||||
if (iter.blend_method == WEBP_MUX_BLEND) {
|
||||
image = [self sd_blendWebpImageWithCanvas:canvas iterator:iter];
|
||||
} else {
|
||||
image = [self sd_nonblendWebpImageWithCanvas:canvas iterator:iter];
|
||||
}
|
||||
|
||||
if (!image) {
|
||||
continue;
|
||||
}
|
||||
|
||||
[images addObject:image];
|
||||
|
||||
#if SD_MAC
|
||||
break;
|
||||
#else
|
||||
|
||||
int duration = iter.duration;
|
||||
if (duration <= 10) {
|
||||
// WebP standard says 0 duration is used for canvas updating but not showing image, but actually Chrome and other implementations set it to 100ms if duration is lower or equal than 10ms
|
||||
// Some animated WebP images also created without duration, we should keep compatibility
|
||||
duration = 100;
|
||||
}
|
||||
totalDuration += duration;
|
||||
size_t count = images.count;
|
||||
durations[count - 1] = duration;
|
||||
#endif
|
||||
}
|
||||
|
||||
} while (WebPDemuxNextFrame(&iter));
|
||||
|
||||
WebPDemuxReleaseIterator(&iter);
|
||||
WebPDemuxDelete(demuxer);
|
||||
CGContextRelease(canvas);
|
||||
|
||||
UIImage *finalImage = nil;
|
||||
#if SD_UIKIT || SD_WATCH
|
||||
NSArray<UIImage *> *animatedImages = [self sd_animatedImagesWithImages:images durations:durations totalDuration:totalDuration];
|
||||
finalImage = [UIImage animatedImageWithImages:animatedImages duration:totalDuration / 1000.0];
|
||||
if (finalImage) {
|
||||
finalImage.sd_imageLoopCount = loopCount;
|
||||
}
|
||||
#elif SD_MAC
|
||||
finalImage = images.firstObject;
|
||||
#endif
|
||||
return finalImage;
|
||||
}
|
||||
|
||||
- (UIImage *)incrementallyDecodedImageWithData:(NSData *)data finished:(BOOL)finished {
|
||||
if (!_idec) {
|
||||
// Progressive images need transparent, so always use premultiplied RGBA
|
||||
_idec = WebPINewRGB(MODE_rgbA, NULL, 0, 0);
|
||||
if (!_idec) {
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
UIImage *image;
|
||||
|
||||
VP8StatusCode status = WebPIUpdate(_idec, data.bytes, data.length);
|
||||
if (status != VP8_STATUS_OK && status != VP8_STATUS_SUSPENDED) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
int width;
|
||||
int height;
|
||||
uint8_t *rgba = WebPIDecGetRGB(_idec, NULL, (int *)&width, (int *)&height, NULL);
|
||||
|
||||
if (width + height > 0) {
|
||||
// Construct a UIImage from the decoded RGBA value array
|
||||
CGDataProviderRef provider =
|
||||
CGDataProviderCreateWithData(NULL, rgba, 0, NULL);
|
||||
CGColorSpaceRef colorSpaceRef = SDCGColorSpaceGetDeviceRGB();
|
||||
|
||||
CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Big | kCGImageAlphaPremultipliedLast;
|
||||
size_t components = 4;
|
||||
CGColorRenderingIntent renderingIntent = kCGRenderingIntentDefault;
|
||||
CGImageRef imageRef = CGImageCreate(width, height, 8, components * 8, components * width, colorSpaceRef, bitmapInfo, provider, NULL, NO, renderingIntent);
|
||||
|
||||
CGDataProviderRelease(provider);
|
||||
|
||||
if (!imageRef) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
CGContextRef canvas = CGBitmapContextCreate(NULL, width, height, 8, 0, SDCGColorSpaceGetDeviceRGB(), bitmapInfo);
|
||||
if (!canvas) {
|
||||
CGImageRelease(imageRef);
|
||||
return nil;
|
||||
}
|
||||
|
||||
CGContextDrawImage(canvas, CGRectMake(0, 0, width, height), imageRef);
|
||||
CGImageRef newImageRef = CGBitmapContextCreateImage(canvas);
|
||||
CGImageRelease(imageRef);
|
||||
if (!newImageRef) {
|
||||
CGContextRelease(canvas);
|
||||
return nil;
|
||||
}
|
||||
|
||||
#if SD_UIKIT || SD_WATCH
|
||||
image = [[UIImage alloc] initWithCGImage:newImageRef];
|
||||
#else
|
||||
image = [[UIImage alloc] initWithCGImage:newImageRef size:NSZeroSize];
|
||||
#endif
|
||||
CGImageRelease(newImageRef);
|
||||
CGContextRelease(canvas);
|
||||
}
|
||||
|
||||
if (finished) {
|
||||
if (_idec) {
|
||||
WebPIDelete(_idec);
|
||||
_idec = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
- (UIImage *)decompressedImageWithImage:(UIImage *)image
|
||||
data:(NSData *__autoreleasing _Nullable *)data
|
||||
options:(nullable NSDictionary<NSString*, NSObject*>*)optionsDict {
|
||||
// WebP do not decompress
|
||||
return image;
|
||||
}
|
||||
|
||||
- (nullable UIImage *)sd_blendWebpImageWithCanvas:(CGContextRef)canvas iterator:(WebPIterator)iter {
|
||||
UIImage *image = [self sd_rawWebpImageWithData:iter.fragment];
|
||||
if (!image) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
size_t canvasWidth = CGBitmapContextGetWidth(canvas);
|
||||
size_t canvasHeight = CGBitmapContextGetHeight(canvas);
|
||||
CGSize size = CGSizeMake(canvasWidth, canvasHeight);
|
||||
CGFloat tmpX = iter.x_offset;
|
||||
CGFloat tmpY = size.height - iter.height - iter.y_offset;
|
||||
CGRect imageRect = CGRectMake(tmpX, tmpY, iter.width, iter.height);
|
||||
|
||||
CGContextDrawImage(canvas, imageRect, image.CGImage);
|
||||
CGImageRef newImageRef = CGBitmapContextCreateImage(canvas);
|
||||
|
||||
#if SD_UIKIT || SD_WATCH
|
||||
image = [UIImage imageWithCGImage:newImageRef];
|
||||
#elif SD_MAC
|
||||
image = [[UIImage alloc] initWithCGImage:newImageRef size:NSZeroSize];
|
||||
#endif
|
||||
|
||||
CGImageRelease(newImageRef);
|
||||
|
||||
if (iter.dispose_method == WEBP_MUX_DISPOSE_BACKGROUND) {
|
||||
CGContextClearRect(canvas, imageRect);
|
||||
}
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
- (nullable UIImage *)sd_nonblendWebpImageWithCanvas:(CGContextRef)canvas iterator:(WebPIterator)iter {
|
||||
UIImage *image = [self sd_rawWebpImageWithData:iter.fragment];
|
||||
if (!image) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
size_t canvasWidth = CGBitmapContextGetWidth(canvas);
|
||||
size_t canvasHeight = CGBitmapContextGetHeight(canvas);
|
||||
CGSize size = CGSizeMake(canvasWidth, canvasHeight);
|
||||
CGFloat tmpX = iter.x_offset;
|
||||
CGFloat tmpY = size.height - iter.height - iter.y_offset;
|
||||
CGRect imageRect = CGRectMake(tmpX, tmpY, iter.width, iter.height);
|
||||
|
||||
CGContextClearRect(canvas, imageRect);
|
||||
CGContextDrawImage(canvas, imageRect, image.CGImage);
|
||||
CGImageRef newImageRef = CGBitmapContextCreateImage(canvas);
|
||||
|
||||
#if SD_UIKIT || SD_WATCH
|
||||
image = [UIImage imageWithCGImage:newImageRef];
|
||||
#elif SD_MAC
|
||||
image = [[UIImage alloc] initWithCGImage:newImageRef size:NSZeroSize];
|
||||
#endif
|
||||
|
||||
CGImageRelease(newImageRef);
|
||||
|
||||
if (iter.dispose_method == WEBP_MUX_DISPOSE_BACKGROUND) {
|
||||
CGContextClearRect(canvas, imageRect);
|
||||
}
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
- (nullable UIImage *)sd_rawWebpImageWithData:(WebPData)webpData {
|
||||
WebPDecoderConfig config;
|
||||
if (!WebPInitDecoderConfig(&config)) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
if (WebPGetFeatures(webpData.bytes, webpData.size, &config.input) != VP8_STATUS_OK) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
config.output.colorspace = config.input.has_alpha ? MODE_rgbA : MODE_RGB;
|
||||
config.options.use_threads = 1;
|
||||
|
||||
// Decode the WebP image data into a RGBA value array
|
||||
if (WebPDecode(webpData.bytes, webpData.size, &config) != VP8_STATUS_OK) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
int width = config.input.width;
|
||||
int height = config.input.height;
|
||||
if (config.options.use_scaling) {
|
||||
width = config.options.scaled_width;
|
||||
height = config.options.scaled_height;
|
||||
}
|
||||
|
||||
// Construct a UIImage from the decoded RGBA value array
|
||||
CGDataProviderRef provider =
|
||||
CGDataProviderCreateWithData(NULL, config.output.u.RGBA.rgba, config.output.u.RGBA.size, FreeImageData);
|
||||
CGColorSpaceRef colorSpaceRef = SDCGColorSpaceGetDeviceRGB();
|
||||
CGBitmapInfo bitmapInfo = config.input.has_alpha ? kCGBitmapByteOrder32Big | kCGImageAlphaPremultipliedLast : kCGBitmapByteOrder32Big | kCGImageAlphaNoneSkipLast;
|
||||
size_t components = config.input.has_alpha ? 4 : 3;
|
||||
CGColorRenderingIntent renderingIntent = kCGRenderingIntentDefault;
|
||||
CGImageRef imageRef = CGImageCreate(width, height, 8, components * 8, components * width, colorSpaceRef, bitmapInfo, provider, NULL, NO, renderingIntent);
|
||||
|
||||
CGDataProviderRelease(provider);
|
||||
|
||||
#if SD_UIKIT || SD_WATCH
|
||||
UIImage *image = [[UIImage alloc] initWithCGImage:imageRef];
|
||||
#else
|
||||
UIImage *image = [[UIImage alloc] initWithCGImage:imageRef size:NSZeroSize];
|
||||
#endif
|
||||
CGImageRelease(imageRef);
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
#pragma mark - Encode
|
||||
- (BOOL)canEncodeToFormat:(SDImageFormat)format {
|
||||
return (format == SDImageFormatWebP);
|
||||
}
|
||||
|
||||
- (NSData *)encodedDataWithImage:(UIImage *)image format:(SDImageFormat)format {
|
||||
if (!image) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSData *data;
|
||||
#if SD_UIKIT || SD_WATCH
|
||||
if (!image.images) {
|
||||
#endif
|
||||
// for static single webp image
|
||||
data = [self sd_encodedWebpDataWithImage:image];
|
||||
#if SD_UIKIT || SD_WATCH
|
||||
} else {
|
||||
// for animated webp image
|
||||
int durations[image.images.count];
|
||||
NSArray<UIImage *> *images = [self sd_imagesFromAnimatedImages:image.images totalDuration:image.duration durations:durations];
|
||||
WebPMux *mux = WebPMuxNew();
|
||||
if (!mux) {
|
||||
return nil;
|
||||
}
|
||||
for (NSUInteger i = 0; i < images.count; i++) {
|
||||
NSData *webpData = [self sd_encodedWebpDataWithImage:images[i]];
|
||||
int duration = durations[i];
|
||||
WebPMuxFrameInfo frame = { .bitstream.bytes = webpData.bytes,
|
||||
.bitstream.size = webpData.length,
|
||||
.duration = duration,
|
||||
.id = WEBP_CHUNK_ANMF,
|
||||
.dispose_method = WEBP_MUX_DISPOSE_BACKGROUND, // each frame will clear canvas
|
||||
.blend_method = WEBP_MUX_NO_BLEND
|
||||
};
|
||||
if (WebPMuxPushFrame(mux, &frame, 0) != WEBP_MUX_OK) {
|
||||
WebPMuxDelete(mux);
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
int loopCount = (int)image.sd_imageLoopCount;
|
||||
WebPMuxAnimParams params = { .bgcolor = 0,
|
||||
.loop_count = loopCount
|
||||
};
|
||||
if (WebPMuxSetAnimationParams(mux, ¶ms) != WEBP_MUX_OK) {
|
||||
WebPMuxDelete(mux);
|
||||
return nil;
|
||||
}
|
||||
|
||||
WebPData outputData;
|
||||
WebPMuxError error = WebPMuxAssemble(mux, &outputData);
|
||||
WebPMuxDelete(mux);
|
||||
if (error != WEBP_MUX_OK) {
|
||||
return nil;
|
||||
}
|
||||
data = [NSData dataWithBytes:outputData.bytes length:outputData.size];
|
||||
WebPDataClear(&outputData);
|
||||
}
|
||||
#endif
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
- (nullable NSData *)sd_encodedWebpDataWithImage:(nullable UIImage *)image {
|
||||
if (!image) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSData *webpData;
|
||||
CGImageRef imageRef = image.CGImage;
|
||||
|
||||
size_t width = CGImageGetWidth(imageRef);
|
||||
size_t height = CGImageGetHeight(imageRef);
|
||||
if (width == 0 || width > WEBP_MAX_DIMENSION) {
|
||||
return nil;
|
||||
}
|
||||
if (height == 0 || height > WEBP_MAX_DIMENSION) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
size_t bytesPerRow = CGImageGetBytesPerRow(imageRef);
|
||||
CGDataProviderRef dataProvider = CGImageGetDataProvider(imageRef);
|
||||
CFDataRef dataRef = CGDataProviderCopyData(dataProvider);
|
||||
uint8_t *rgba = (uint8_t *)CFDataGetBytePtr(dataRef);
|
||||
|
||||
uint8_t *data = NULL;
|
||||
float quality = 100.0;
|
||||
size_t size = WebPEncodeRGBA(rgba, (int)width, (int)height, (int)bytesPerRow, quality, &data);
|
||||
CFRelease(dataRef);
|
||||
rgba = NULL;
|
||||
|
||||
if (size) {
|
||||
// success
|
||||
webpData = [NSData dataWithBytes:data length:size];
|
||||
}
|
||||
if (data) {
|
||||
WebPFree(data);
|
||||
}
|
||||
|
||||
return webpData;
|
||||
}
|
||||
|
||||
- (NSArray<UIImage *> *)sd_animatedImagesWithImages:(NSArray<UIImage *> *)images durations:(int const * const)durations totalDuration:(NSTimeInterval)totalDuration
|
||||
{
|
||||
// [UIImage animatedImageWithImages:duration:] only use the average duration for per frame
|
||||
// divide the total duration to implement per frame duration for animated WebP
|
||||
NSUInteger count = images.count;
|
||||
if (!count) {
|
||||
return nil;
|
||||
}
|
||||
if (count == 1) {
|
||||
return images;
|
||||
}
|
||||
|
||||
int const gcd = gcdArray(count, durations);
|
||||
NSMutableArray<UIImage *> *animatedImages = [NSMutableArray arrayWithCapacity:count];
|
||||
[images enumerateObjectsUsingBlock:^(UIImage * _Nonnull image, NSUInteger idx, BOOL * _Nonnull stop) {
|
||||
int duration = durations[idx];
|
||||
int repeatCount;
|
||||
if (gcd) {
|
||||
repeatCount = duration / gcd;
|
||||
} else {
|
||||
repeatCount = 1;
|
||||
}
|
||||
for (int i = 0; i < repeatCount; ++i) {
|
||||
[animatedImages addObject:image];
|
||||
}
|
||||
}];
|
||||
|
||||
return animatedImages;
|
||||
}
|
||||
|
||||
- (NSArray<UIImage *> *)sd_imagesFromAnimatedImages:(NSArray<UIImage *> *)animatedImages totalDuration:(NSTimeInterval)totalDuration durations:(int * const)durations {
|
||||
// This is the reversed procedure to sd_animatedImagesWithImages:durations:totalDuration
|
||||
// To avoid precision loss, convert from s to ms during this method
|
||||
NSUInteger count = animatedImages.count;
|
||||
if (!count) {
|
||||
return nil;
|
||||
}
|
||||
if (count == 1) {
|
||||
durations[0] = totalDuration * 1000; // s -> ms
|
||||
}
|
||||
|
||||
int const duration = totalDuration * 1000 / count;
|
||||
|
||||
__block NSUInteger index = 0;
|
||||
__block int repeatCount = 1;
|
||||
__block UIImage *previousImage = animatedImages.firstObject;
|
||||
NSMutableArray<UIImage *> *images = [NSMutableArray array];
|
||||
[animatedImages enumerateObjectsUsingBlock:^(UIImage * _Nonnull image, NSUInteger idx, BOOL * _Nonnull stop) {
|
||||
// ignore first
|
||||
if (idx == 0) {
|
||||
return;
|
||||
}
|
||||
if ([image isEqual:previousImage]) {
|
||||
repeatCount++;
|
||||
} else {
|
||||
[images addObject:previousImage];
|
||||
durations[index] = duration * repeatCount;
|
||||
repeatCount = 1;
|
||||
index++;
|
||||
}
|
||||
previousImage = image;
|
||||
// last one
|
||||
if (idx == count - 1) {
|
||||
[images addObject:previousImage];
|
||||
durations[index] = duration * repeatCount;
|
||||
}
|
||||
}];
|
||||
|
||||
return images;
|
||||
}
|
||||
|
||||
static void FreeImageData(void *info, const void *data, size_t size) {
|
||||
free((void *)data);
|
||||
}
|
||||
|
||||
static int gcdArray(size_t const count, int const * const values) {
|
||||
int result = values[0];
|
||||
for (size_t i = 1; i < count; ++i) {
|
||||
result = gcd(values[i], result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static int gcd(int a,int b) {
|
||||
int c;
|
||||
while (a != 0) {
|
||||
c = a;
|
||||
a = b % a;
|
||||
b = c;
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
|
@ -1,13 +1,11 @@
|
|||
/*
|
||||
* This file is part of the SDWebImage package.
|
||||
* (c) Olivier Poitrey <rs@dailymotion.com>
|
||||
* (c) james <https://github.com/mystcolor>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "SDWebImageCompat.h"
|
||||
|
||||
@interface UIImage (ForceDecode)
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* This file is part of the SDWebImage package.
|
||||
* (c) Olivier Poitrey <rs@dailymotion.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
#import "UIImage+ForceDecode.h"
|
||||
#import "SDWebImageCodersManager.h"
|
||||
|
||||
@implementation UIImage (ForceDecode)
|
||||
|
||||
+ (UIImage *)decodedImageWithImage:(UIImage *)image {
|
||||
if (!image) {
|
||||
return nil;
|
||||
}
|
||||
NSData *tempData;
|
||||
return [[SDWebImageCodersManager sharedInstance] decompressedImageWithImage:image data:&tempData options:@{SDWebImageCoderScaleDownLargeImagesKey: @(NO)}];
|
||||
}
|
||||
|
||||
+ (UIImage *)decodedAndScaledDownImageWithImage:(UIImage *)image {
|
||||
if (!image) {
|
||||
return nil;
|
||||
}
|
||||
NSData *tempData;
|
||||
return [[SDWebImageCodersManager sharedInstance] decompressedImageWithImage:image data:&tempData options:@{SDWebImageCoderScaleDownLargeImagesKey: @(YES)}];
|
||||
}
|
||||
|
||||
@end
|
|
@ -8,8 +8,7 @@
|
|||
*/
|
||||
|
||||
#import "UIImage+GIF.h"
|
||||
#import <ImageIO/ImageIO.h>
|
||||
#import "objc/runtime.h"
|
||||
#import "SDWebImageGIFCoder.h"
|
||||
#import "NSImage+WebCache.h"
|
||||
|
||||
@implementation UIImage (GIF)
|
||||
|
@ -18,42 +17,7 @@
|
|||
if (!data) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
#if SD_MAC
|
||||
return [[UIImage alloc] initWithData:data];
|
||||
#else
|
||||
|
||||
CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL);
|
||||
|
||||
size_t count = CGImageSourceGetCount(source);
|
||||
|
||||
UIImage *staticImage;
|
||||
|
||||
if (count <= 1) {
|
||||
staticImage = [[UIImage alloc] initWithData:data];
|
||||
} else {
|
||||
// we will only retrieve the 1st frame. the full GIF support is available via the FLAnimatedImageView category.
|
||||
// this here is only code to allow drawing animated images as static ones
|
||||
#if SD_WATCH
|
||||
CGFloat scale = 1;
|
||||
scale = [WKInterfaceDevice currentDevice].screenScale;
|
||||
#elif SD_UIKIT
|
||||
CGFloat scale = 1;
|
||||
scale = [UIScreen mainScreen].scale;
|
||||
#endif
|
||||
|
||||
CGImageRef CGImage = CGImageSourceCreateImageAtIndex(source, 0, NULL);
|
||||
#if SD_UIKIT || SD_WATCH
|
||||
UIImage *frameImage = [UIImage imageWithCGImage:CGImage scale:scale orientation:UIImageOrientationUp];
|
||||
staticImage = [UIImage animatedImageWithImages:@[frameImage] duration:0.0f];
|
||||
#endif
|
||||
CGImageRelease(CGImage);
|
||||
}
|
||||
|
||||
CFRelease(source);
|
||||
|
||||
return staticImage;
|
||||
#endif
|
||||
return [[SDWebImageGIFCoder sharedCoder] decodedImageWithData:data];
|
||||
}
|
||||
|
||||
- (BOOL)isGIF {
|
||||
|
|
|
@ -11,6 +11,15 @@
|
|||
|
||||
@interface UIImage (MultiFormat)
|
||||
|
||||
|
||||
/**
|
||||
* For static image format, this value is always 0.
|
||||
* For animated image format, 0 means infinite looping.
|
||||
* Note that because of the limitations of categories this property can get out of sync
|
||||
* if you create another instance with CGImage or other methods.
|
||||
*/
|
||||
@property (nonatomic, assign) NSUInteger sd_imageLoopCount;
|
||||
|
||||
+ (nullable UIImage *)sd_imageWithData:(nullable NSData *)data;
|
||||
- (nullable NSData *)sd_imageData;
|
||||
- (nullable NSData *)sd_imageDataAsFormat:(SDImageFormat)imageFormat;
|
||||
|
|
|
@ -7,115 +7,30 @@
|
|||
*/
|
||||
|
||||
#import "UIImage+MultiFormat.h"
|
||||
#import "UIImage+GIF.h"
|
||||
#import "NSData+ImageContentType.h"
|
||||
#import <ImageIO/ImageIO.h>
|
||||
|
||||
#ifdef SD_WEBP
|
||||
#import "UIImage+WebP.h"
|
||||
#endif
|
||||
#import "objc/runtime.h"
|
||||
#import "SDWebImageCodersManager.h"
|
||||
|
||||
@implementation UIImage (MultiFormat)
|
||||
|
||||
- (NSUInteger)sd_imageLoopCount {
|
||||
NSUInteger imageLoopCount = 0;
|
||||
NSNumber *value = objc_getAssociatedObject(self, @selector(sd_imageLoopCount));
|
||||
if ([value isKindOfClass:[NSNumber class]]) {
|
||||
imageLoopCount = value.unsignedIntegerValue;
|
||||
}
|
||||
return imageLoopCount;
|
||||
}
|
||||
|
||||
- (void)setSd_imageLoopCount:(NSUInteger)sd_imageLoopCount {
|
||||
NSNumber *value = @(sd_imageLoopCount);
|
||||
objc_setAssociatedObject(self, @selector(sd_imageLoopCount), value, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
||||
}
|
||||
|
||||
+ (nullable UIImage *)sd_imageWithData:(nullable NSData *)data {
|
||||
if (!data) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
UIImage *image;
|
||||
SDImageFormat imageFormat = [NSData sd_imageFormatForImageData:data];
|
||||
if (imageFormat == SDImageFormatGIF) {
|
||||
image = [UIImage sd_animatedGIFWithData:data];
|
||||
}
|
||||
#ifdef SD_WEBP
|
||||
else if (imageFormat == SDImageFormatWebP)
|
||||
{
|
||||
image = [UIImage sd_imageWithWebPData:data];
|
||||
}
|
||||
#endif
|
||||
else {
|
||||
image = [[UIImage alloc] initWithData:data];
|
||||
#if SD_UIKIT || SD_WATCH
|
||||
UIImageOrientation orientation = [self sd_imageOrientationFromImageData:data];
|
||||
if (orientation != UIImageOrientationUp) {
|
||||
image = [UIImage imageWithCGImage:image.CGImage
|
||||
scale:image.scale
|
||||
orientation:orientation];
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
return image;
|
||||
return [[SDWebImageCodersManager sharedInstance] decodedImageWithData:data];
|
||||
}
|
||||
|
||||
#if SD_UIKIT || SD_WATCH
|
||||
+(UIImageOrientation)sd_imageOrientationFromImageData:(nonnull NSData *)imageData {
|
||||
UIImageOrientation result = UIImageOrientationUp;
|
||||
CGImageSourceRef imageSource = CGImageSourceCreateWithData((__bridge CFDataRef)imageData, NULL);
|
||||
if (imageSource) {
|
||||
CFDictionaryRef properties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, NULL);
|
||||
if (properties) {
|
||||
CFTypeRef val;
|
||||
int exifOrientation;
|
||||
val = CFDictionaryGetValue(properties, kCGImagePropertyOrientation);
|
||||
if (val) {
|
||||
CFNumberGetValue(val, kCFNumberIntType, &exifOrientation);
|
||||
result = [self sd_exifOrientationToiOSOrientation:exifOrientation];
|
||||
} // else - if it's not set it remains at up
|
||||
CFRelease((CFTypeRef) properties);
|
||||
} else {
|
||||
//NSLog(@"NO PROPERTIES, FAIL");
|
||||
}
|
||||
CFRelease(imageSource);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
#pragma mark EXIF orientation tag converter
|
||||
// Convert an EXIF image orientation to an iOS one.
|
||||
// reference see here: http://sylvana.net/jpegcrop/exif_orientation.html
|
||||
+ (UIImageOrientation) sd_exifOrientationToiOSOrientation:(int)exifOrientation {
|
||||
UIImageOrientation orientation = UIImageOrientationUp;
|
||||
switch (exifOrientation) {
|
||||
case 1:
|
||||
orientation = UIImageOrientationUp;
|
||||
break;
|
||||
|
||||
case 3:
|
||||
orientation = UIImageOrientationDown;
|
||||
break;
|
||||
|
||||
case 8:
|
||||
orientation = UIImageOrientationLeft;
|
||||
break;
|
||||
|
||||
case 6:
|
||||
orientation = UIImageOrientationRight;
|
||||
break;
|
||||
|
||||
case 2:
|
||||
orientation = UIImageOrientationUpMirrored;
|
||||
break;
|
||||
|
||||
case 4:
|
||||
orientation = UIImageOrientationDownMirrored;
|
||||
break;
|
||||
|
||||
case 5:
|
||||
orientation = UIImageOrientationLeftMirrored;
|
||||
break;
|
||||
|
||||
case 7:
|
||||
orientation = UIImageOrientationRightMirrored;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return orientation;
|
||||
}
|
||||
#endif
|
||||
|
||||
- (nullable NSData *)sd_imageData {
|
||||
return [self sd_imageDataAsFormat:SDImageFormatUndefined];
|
||||
}
|
||||
|
@ -123,36 +38,7 @@
|
|||
- (nullable NSData *)sd_imageDataAsFormat:(SDImageFormat)imageFormat {
|
||||
NSData *imageData = nil;
|
||||
if (self) {
|
||||
#if SD_UIKIT || SD_WATCH
|
||||
int alphaInfo = CGImageGetAlphaInfo(self.CGImage);
|
||||
BOOL hasAlpha = !(alphaInfo == kCGImageAlphaNone ||
|
||||
alphaInfo == kCGImageAlphaNoneSkipFirst ||
|
||||
alphaInfo == kCGImageAlphaNoneSkipLast);
|
||||
|
||||
BOOL usePNG = hasAlpha;
|
||||
|
||||
// the imageFormat param has priority here. But if the format is undefined, we relly on the alpha channel
|
||||
if (imageFormat != SDImageFormatUndefined) {
|
||||
usePNG = (imageFormat == SDImageFormatPNG);
|
||||
}
|
||||
|
||||
if (usePNG) {
|
||||
imageData = UIImagePNGRepresentation(self);
|
||||
} else {
|
||||
imageData = UIImageJPEGRepresentation(self, (CGFloat)1.0);
|
||||
}
|
||||
#else
|
||||
NSBitmapImageFileType imageFileType = NSJPEGFileType;
|
||||
if (imageFormat == SDImageFormatGIF) {
|
||||
imageFileType = NSGIFFileType;
|
||||
} else if (imageFormat == SDImageFormatPNG) {
|
||||
imageFileType = NSPNGFileType;
|
||||
}
|
||||
|
||||
imageData = [NSBitmapImageRep representationOfImageRepsInArray:self.representations
|
||||
usingType:imageFileType
|
||||
properties:@{}];
|
||||
#endif
|
||||
imageData = [[SDWebImageCodersManager sharedInstance] encodedDataWithImage:self format:imageFormat];
|
||||
}
|
||||
return imageData;
|
||||
}
|
||||
|
|
|
@ -19,8 +19,9 @@
|
|||
* Note that because of the limitations of categories this property can get out of sync
|
||||
* if you create another instance with CGImage or other methods.
|
||||
* @return WebP image loop count
|
||||
* @deprecated use `sd_imageLoopCount` instead.
|
||||
*/
|
||||
- (NSInteger)sd_webpLoopCount;
|
||||
- (NSInteger)sd_webpLoopCount __deprecated_msg("Method deprecated. Use `sd_imageLoopCount` in `UIImage+MultiFormat.h`");
|
||||
|
||||
+ (nullable UIImage *)sd_imageWithWebPData:(nullable NSData *)data;
|
||||
|
||||
|
|
|
@ -9,318 +9,20 @@
|
|||
#ifdef SD_WEBP
|
||||
|
||||
#import "UIImage+WebP.h"
|
||||
#import "NSImage+WebCache.h"
|
||||
|
||||
#if __has_include(<webp/decode.h>) && __has_include(<webp/mux_types.h>) && __has_include(<webp/demux.h>)
|
||||
#import <webp/decode.h>
|
||||
#import <webp/mux_types.h>
|
||||
#import <webp/demux.h>
|
||||
#else
|
||||
#import "webp/decode.h"
|
||||
#import "webp/mux_types.h"
|
||||
#import "webp/demux.h"
|
||||
#endif
|
||||
|
||||
#import "objc/runtime.h"
|
||||
|
||||
// Callback for CGDataProviderRelease
|
||||
static void FreeImageData(void *info, const void *data, size_t size) {
|
||||
free((void *)data);
|
||||
}
|
||||
#import "SDWebImageWebPCoder.h"
|
||||
#import "UIImage+MultiFormat.h"
|
||||
|
||||
@implementation UIImage (WebP)
|
||||
|
||||
- (NSInteger)sd_webpLoopCount
|
||||
{
|
||||
NSNumber *value = objc_getAssociatedObject(self, @selector(sd_webpLoopCount));
|
||||
return value.integerValue;
|
||||
- (NSInteger)sd_webpLoopCount {
|
||||
return self.sd_imageLoopCount;
|
||||
}
|
||||
|
||||
+ (nullable UIImage *)sd_imageWithWebPData:(nullable NSData *)data {
|
||||
if (!data) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
WebPData webpData;
|
||||
WebPDataInit(&webpData);
|
||||
webpData.bytes = data.bytes;
|
||||
webpData.size = data.length;
|
||||
WebPDemuxer *demuxer = WebPDemux(&webpData);
|
||||
if (!demuxer) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
uint32_t flags = WebPDemuxGetI(demuxer, WEBP_FF_FORMAT_FLAGS);
|
||||
#if SD_UIKIT || SD_WATCH
|
||||
int loopCount = WebPDemuxGetI(demuxer, WEBP_FF_LOOP_COUNT);
|
||||
int frameCount = WebPDemuxGetI(demuxer, WEBP_FF_FRAME_COUNT);
|
||||
#endif
|
||||
int canvasWidth = WebPDemuxGetI(demuxer, WEBP_FF_CANVAS_WIDTH);
|
||||
int canvasHeight = WebPDemuxGetI(demuxer, WEBP_FF_CANVAS_HEIGHT);
|
||||
CGBitmapInfo bitmapInfo;
|
||||
if (!(flags & ALPHA_FLAG)) {
|
||||
bitmapInfo = kCGBitmapByteOrder32Big | kCGImageAlphaNoneSkipLast;
|
||||
} else {
|
||||
bitmapInfo = kCGBitmapByteOrder32Big | kCGImageAlphaPremultipliedLast;
|
||||
}
|
||||
CGContextRef canvas = CGBitmapContextCreate(NULL, canvasWidth, canvasHeight, 8, 0, SDCGColorSpaceGetDeviceRGB(), bitmapInfo);
|
||||
if (!canvas) {
|
||||
WebPDemuxDelete(demuxer);
|
||||
return nil;
|
||||
}
|
||||
|
||||
if (!(flags & ANIMATION_FLAG)) {
|
||||
// for static single webp image
|
||||
UIImage *staticImage = [self sd_rawWebpImageWithData:webpData];
|
||||
if (staticImage) {
|
||||
// draw on CGBitmapContext can reduce memory usage
|
||||
CGImageRef imageRef = staticImage.CGImage;
|
||||
size_t width = CGImageGetWidth(imageRef);
|
||||
size_t height = CGImageGetHeight(imageRef);
|
||||
CGContextDrawImage(canvas, CGRectMake(0, 0, width, height), imageRef);
|
||||
CGImageRef newImageRef = CGBitmapContextCreateImage(canvas);
|
||||
#if SD_UIKIT || SD_WATCH
|
||||
staticImage = [[UIImage alloc] initWithCGImage:newImageRef];
|
||||
#else
|
||||
staticImage = [[UIImage alloc] initWithCGImage:newImageRef size:NSZeroSize];
|
||||
#endif
|
||||
CGImageRelease(newImageRef);
|
||||
}
|
||||
WebPDemuxDelete(demuxer);
|
||||
CGContextRelease(canvas);
|
||||
return staticImage;
|
||||
}
|
||||
|
||||
// for animated webp image
|
||||
WebPIterator iter;
|
||||
if (!WebPDemuxGetFrame(demuxer, 1, &iter)) {
|
||||
WebPDemuxReleaseIterator(&iter);
|
||||
WebPDemuxDelete(demuxer);
|
||||
CGContextRelease(canvas);
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSMutableArray<UIImage *> *images = [NSMutableArray array];
|
||||
#if SD_UIKIT || SD_WATCH
|
||||
NSTimeInterval totalDuration = 0;
|
||||
int durations[frameCount];
|
||||
#endif
|
||||
|
||||
do {
|
||||
@autoreleasepool {
|
||||
UIImage *image;
|
||||
if (iter.blend_method == WEBP_MUX_BLEND) {
|
||||
image = [self sd_blendWebpImageWithCanvas:canvas iterator:iter];
|
||||
} else {
|
||||
image = [self sd_nonblendWebpImageWithCanvas:canvas iterator:iter];
|
||||
}
|
||||
|
||||
if (!image) {
|
||||
continue;
|
||||
}
|
||||
|
||||
[images addObject:image];
|
||||
|
||||
#if SD_MAC
|
||||
break;
|
||||
#else
|
||||
|
||||
int duration = iter.duration;
|
||||
if (duration <= 10) {
|
||||
// WebP standard says 0 duration is used for canvas updating but not showing image, but actually Chrome and other implementations set it to 100ms if duration is lower or equal than 10ms
|
||||
// Some animated WebP images also created without duration, we should keep compatibility
|
||||
duration = 100;
|
||||
}
|
||||
totalDuration += duration;
|
||||
size_t count = images.count;
|
||||
durations[count - 1] = duration;
|
||||
#endif
|
||||
}
|
||||
|
||||
} while (WebPDemuxNextFrame(&iter));
|
||||
|
||||
WebPDemuxReleaseIterator(&iter);
|
||||
WebPDemuxDelete(demuxer);
|
||||
CGContextRelease(canvas);
|
||||
|
||||
UIImage *finalImage = nil;
|
||||
#if SD_UIKIT || SD_WATCH
|
||||
NSArray<UIImage *> *animatedImages = [self sd_animatedImagesWithImages:images durations:durations totalDuration:totalDuration];
|
||||
finalImage = [UIImage animatedImageWithImages:animatedImages duration:totalDuration / 1000.0];
|
||||
if (finalImage) {
|
||||
objc_setAssociatedObject(finalImage, @selector(sd_webpLoopCount), @(loopCount), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
||||
}
|
||||
#elif SD_MAC
|
||||
finalImage = images.firstObject;
|
||||
#endif
|
||||
return finalImage;
|
||||
}
|
||||
|
||||
|
||||
+ (nullable UIImage *)sd_blendWebpImageWithCanvas:(CGContextRef)canvas iterator:(WebPIterator)iter {
|
||||
UIImage *image = [self sd_rawWebpImageWithData:iter.fragment];
|
||||
if (!image) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
size_t canvasWidth = CGBitmapContextGetWidth(canvas);
|
||||
size_t canvasHeight = CGBitmapContextGetHeight(canvas);
|
||||
CGSize size = CGSizeMake(canvasWidth, canvasHeight);
|
||||
CGFloat tmpX = iter.x_offset;
|
||||
CGFloat tmpY = size.height - iter.height - iter.y_offset;
|
||||
CGRect imageRect = CGRectMake(tmpX, tmpY, iter.width, iter.height);
|
||||
|
||||
CGContextDrawImage(canvas, imageRect, image.CGImage);
|
||||
CGImageRef newImageRef = CGBitmapContextCreateImage(canvas);
|
||||
|
||||
#if SD_UIKIT || SD_WATCH
|
||||
image = [UIImage imageWithCGImage:newImageRef];
|
||||
#elif SD_MAC
|
||||
image = [[UIImage alloc] initWithCGImage:newImageRef size:NSZeroSize];
|
||||
#endif
|
||||
|
||||
CGImageRelease(newImageRef);
|
||||
|
||||
if (iter.dispose_method == WEBP_MUX_DISPOSE_BACKGROUND) {
|
||||
CGContextClearRect(canvas, imageRect);
|
||||
}
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
+ (nullable UIImage *)sd_nonblendWebpImageWithCanvas:(CGContextRef)canvas iterator:(WebPIterator)iter {
|
||||
UIImage *image = [self sd_rawWebpImageWithData:iter.fragment];
|
||||
if (!image) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
size_t canvasWidth = CGBitmapContextGetWidth(canvas);
|
||||
size_t canvasHeight = CGBitmapContextGetHeight(canvas);
|
||||
CGSize size = CGSizeMake(canvasWidth, canvasHeight);
|
||||
CGFloat tmpX = iter.x_offset;
|
||||
CGFloat tmpY = size.height - iter.height - iter.y_offset;
|
||||
CGRect imageRect = CGRectMake(tmpX, tmpY, iter.width, iter.height);
|
||||
|
||||
CGContextClearRect(canvas, imageRect);
|
||||
CGContextDrawImage(canvas, imageRect, image.CGImage);
|
||||
CGImageRef newImageRef = CGBitmapContextCreateImage(canvas);
|
||||
|
||||
#if SD_UIKIT || SD_WATCH
|
||||
image = [UIImage imageWithCGImage:newImageRef];
|
||||
#elif SD_MAC
|
||||
image = [[UIImage alloc] initWithCGImage:newImageRef size:NSZeroSize];
|
||||
#endif
|
||||
|
||||
CGImageRelease(newImageRef);
|
||||
|
||||
if (iter.dispose_method == WEBP_MUX_DISPOSE_BACKGROUND) {
|
||||
CGContextClearRect(canvas, imageRect);
|
||||
}
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
+ (nullable UIImage *)sd_rawWebpImageWithData:(WebPData)webpData {
|
||||
WebPDecoderConfig config;
|
||||
if (!WebPInitDecoderConfig(&config)) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
if (WebPGetFeatures(webpData.bytes, webpData.size, &config.input) != VP8_STATUS_OK) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
config.output.colorspace = config.input.has_alpha ? MODE_rgbA : MODE_RGB;
|
||||
config.options.use_threads = 1;
|
||||
|
||||
// Decode the WebP image data into a RGBA value array
|
||||
if (WebPDecode(webpData.bytes, webpData.size, &config) != VP8_STATUS_OK) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
int width = config.input.width;
|
||||
int height = config.input.height;
|
||||
if (config.options.use_scaling) {
|
||||
width = config.options.scaled_width;
|
||||
height = config.options.scaled_height;
|
||||
}
|
||||
|
||||
// Construct a UIImage from the decoded RGBA value array
|
||||
CGDataProviderRef provider =
|
||||
CGDataProviderCreateWithData(NULL, config.output.u.RGBA.rgba, config.output.u.RGBA.size, FreeImageData);
|
||||
CGColorSpaceRef colorSpaceRef = SDCGColorSpaceGetDeviceRGB();
|
||||
CGBitmapInfo bitmapInfo = config.input.has_alpha ? kCGBitmapByteOrder32Big | kCGImageAlphaPremultipliedLast : kCGBitmapByteOrder32Big | kCGImageAlphaNoneSkipLast;
|
||||
size_t components = config.input.has_alpha ? 4 : 3;
|
||||
CGColorRenderingIntent renderingIntent = kCGRenderingIntentDefault;
|
||||
CGImageRef imageRef = CGImageCreate(width, height, 8, components * 8, components * width, colorSpaceRef, bitmapInfo, provider, NULL, NO, renderingIntent);
|
||||
|
||||
CGDataProviderRelease(provider);
|
||||
|
||||
#if SD_UIKIT || SD_WATCH
|
||||
UIImage *image = [[UIImage alloc] initWithCGImage:imageRef];
|
||||
#else
|
||||
UIImage *image = [[UIImage alloc] initWithCGImage:imageRef size:NSZeroSize];
|
||||
#endif
|
||||
CGImageRelease(imageRef);
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
+ (NSArray<UIImage *> *)sd_animatedImagesWithImages:(NSArray<UIImage *> *)images durations:(int const * const)durations totalDuration:(NSTimeInterval)totalDuration
|
||||
{
|
||||
// [UIImage animatedImageWithImages:duration:] only use the average duration for per frame
|
||||
// divide the total duration to implement per frame duration for animated WebP
|
||||
NSUInteger count = images.count;
|
||||
if (!count) {
|
||||
return nil;
|
||||
}
|
||||
if (count == 1) {
|
||||
return images;
|
||||
}
|
||||
|
||||
int const gcd = gcdArray(count, durations);
|
||||
NSMutableArray<UIImage *> *animatedImages = [NSMutableArray arrayWithCapacity:count];
|
||||
[images enumerateObjectsUsingBlock:^(UIImage * _Nonnull image, NSUInteger idx, BOOL * _Nonnull stop) {
|
||||
int duration = durations[idx];
|
||||
int repeatCount;
|
||||
if (gcd) {
|
||||
repeatCount = duration / gcd;
|
||||
} else {
|
||||
repeatCount = 1;
|
||||
}
|
||||
for (int i = 0; i < repeatCount; ++i) {
|
||||
[animatedImages addObject:image];
|
||||
}
|
||||
}];
|
||||
|
||||
return animatedImages;
|
||||
}
|
||||
|
||||
static CGColorSpaceRef SDCGColorSpaceGetDeviceRGB() {
|
||||
static CGColorSpaceRef space;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
space = CGColorSpaceCreateDeviceRGB();
|
||||
});
|
||||
return space;
|
||||
}
|
||||
|
||||
static int gcdArray(size_t const count, int const * const values) {
|
||||
int result = values[0];
|
||||
for (size_t i = 1; i < count; ++i) {
|
||||
result = gcd(values[i], result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static int gcd(int a,int b) {
|
||||
int c;
|
||||
while (a != 0) {
|
||||
c = a;
|
||||
a = b % a;
|
||||
b = c;
|
||||
}
|
||||
return b;
|
||||
return [[SDWebImageWebPCoder sharedCoder] decodedImageWithData:data];
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -9,6 +9,9 @@
|
|||
/* Begin PBXBuildFile section */
|
||||
1E3C51E919B46E370092B5E6 /* SDWebImageDownloaderTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 1E3C51E819B46E370092B5E6 /* SDWebImageDownloaderTests.m */; };
|
||||
2D7AF0601F329763000083C2 /* SDTestCase.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D7AF05F1F329763000083C2 /* SDTestCase.m */; };
|
||||
321259EC1F39E3240096FE0E /* TestImageStatic.webp in Resources */ = {isa = PBXBuildFile; fileRef = 321259EB1F39E3240096FE0E /* TestImageStatic.webp */; };
|
||||
321259EE1F39E4110096FE0E /* TestImageAnimated.webp in Resources */ = {isa = PBXBuildFile; fileRef = 321259ED1F39E4110096FE0E /* TestImageAnimated.webp */; };
|
||||
32E6F0321F3A1B4700A945E6 /* SDWebImageTestDecoder.m in Sources */ = {isa = PBXBuildFile; fileRef = 32E6F0311F3A1B4700A945E6 /* SDWebImageTestDecoder.m */; };
|
||||
433BBBB51D7EF5C00086B6E9 /* SDWebImageDecoderTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 433BBBB41D7EF5C00086B6E9 /* SDWebImageDecoderTests.m */; };
|
||||
433BBBB71D7EF8200086B6E9 /* TestImage.gif in Resources */ = {isa = PBXBuildFile; fileRef = 433BBBB61D7EF8200086B6E9 /* TestImage.gif */; };
|
||||
433BBBB91D7EF8260086B6E9 /* TestImage.png in Resources */ = {isa = PBXBuildFile; fileRef = 433BBBB81D7EF8260086B6E9 /* TestImage.png */; };
|
||||
|
@ -24,7 +27,6 @@
|
|||
DA248D61195472AA00390AB0 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = DA248D5F195472AA00390AB0 /* InfoPlist.strings */; };
|
||||
DA248D69195475D800390AB0 /* SDImageCacheTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DA248D68195475D800390AB0 /* SDImageCacheTests.m */; };
|
||||
DA248D6B195476AC00390AB0 /* SDWebImageManagerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DA248D6A195476AC00390AB0 /* SDWebImageManagerTests.m */; };
|
||||
DA91BEBC19795BC9006F2536 /* UIImageMultiFormatTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DA91BEBB19795BC9006F2536 /* UIImageMultiFormatTests.m */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
|
@ -32,6 +34,10 @@
|
|||
1E3C51E819B46E370092B5E6 /* SDWebImageDownloaderTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDWebImageDownloaderTests.m; sourceTree = "<group>"; };
|
||||
2D7AF05E1F329763000083C2 /* SDTestCase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDTestCase.h; sourceTree = "<group>"; };
|
||||
2D7AF05F1F329763000083C2 /* SDTestCase.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDTestCase.m; sourceTree = "<group>"; };
|
||||
321259EB1F39E3240096FE0E /* TestImageStatic.webp */ = {isa = PBXFileReference; lastKnownFileType = file; path = TestImageStatic.webp; sourceTree = "<group>"; };
|
||||
321259ED1F39E4110096FE0E /* TestImageAnimated.webp */ = {isa = PBXFileReference; lastKnownFileType = file; path = TestImageAnimated.webp; sourceTree = "<group>"; };
|
||||
32E6F0301F3A1B4700A945E6 /* SDWebImageTestDecoder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDWebImageTestDecoder.h; sourceTree = "<group>"; };
|
||||
32E6F0311F3A1B4700A945E6 /* SDWebImageTestDecoder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDWebImageTestDecoder.m; sourceTree = "<group>"; };
|
||||
433BBBB41D7EF5C00086B6E9 /* SDWebImageDecoderTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDWebImageDecoderTests.m; sourceTree = "<group>"; };
|
||||
433BBBB61D7EF8200086B6E9 /* TestImage.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; path = TestImage.gif; sourceTree = "<group>"; };
|
||||
433BBBB81D7EF8260086B6E9 /* TestImage.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = TestImage.png; sourceTree = "<group>"; };
|
||||
|
@ -51,7 +57,6 @@
|
|||
DA248D64195472AA00390AB0 /* Tests-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Tests-Prefix.pch"; sourceTree = "<group>"; };
|
||||
DA248D68195475D800390AB0 /* SDImageCacheTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDImageCacheTests.m; sourceTree = "<group>"; };
|
||||
DA248D6A195476AC00390AB0 /* SDWebImageManagerTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDWebImageManagerTests.m; sourceTree = "<group>"; };
|
||||
DA91BEBB19795BC9006F2536 /* UIImageMultiFormatTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UIImageMultiFormatTests.m; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
|
@ -115,16 +120,19 @@
|
|||
5F7F38AC1AE2A77A00B0E330 /* TestImage.jpg */,
|
||||
43828A441DA67F9900000E62 /* TestImageLarge.jpg */,
|
||||
433BBBB81D7EF8260086B6E9 /* TestImage.png */,
|
||||
321259ED1F39E4110096FE0E /* TestImageAnimated.webp */,
|
||||
321259EB1F39E3240096FE0E /* TestImageStatic.webp */,
|
||||
DA248D5D195472AA00390AB0 /* Supporting Files */,
|
||||
DA248D68195475D800390AB0 /* SDImageCacheTests.m */,
|
||||
DA248D6A195476AC00390AB0 /* SDWebImageManagerTests.m */,
|
||||
DA91BEBB19795BC9006F2536 /* UIImageMultiFormatTests.m */,
|
||||
1E3C51E819B46E370092B5E6 /* SDWebImageDownloaderTests.m */,
|
||||
433BBBB41D7EF5C00086B6E9 /* SDWebImageDecoderTests.m */,
|
||||
4369C1D01D97F80F007E863A /* SDWebImagePrefetcherTests.m */,
|
||||
4369C2731D9804B1007E863A /* SDCategoriesTests.m */,
|
||||
2D7AF05E1F329763000083C2 /* SDTestCase.h */,
|
||||
2D7AF05F1F329763000083C2 /* SDTestCase.m */,
|
||||
32E6F0301F3A1B4700A945E6 /* SDWebImageTestDecoder.h */,
|
||||
32E6F0311F3A1B4700A945E6 /* SDWebImageTestDecoder.m */,
|
||||
);
|
||||
path = Tests;
|
||||
sourceTree = "<group>";
|
||||
|
@ -192,9 +200,11 @@
|
|||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
321259EE1F39E4110096FE0E /* TestImageAnimated.webp in Resources */,
|
||||
5F7F38AD1AE2A77A00B0E330 /* TestImage.jpg in Resources */,
|
||||
43828A451DA67F9900000E62 /* TestImageLarge.jpg in Resources */,
|
||||
433BBBB71D7EF8200086B6E9 /* TestImage.gif in Resources */,
|
||||
321259EC1F39E3240096FE0E /* TestImageStatic.webp in Resources */,
|
||||
DA248D61195472AA00390AB0 /* InfoPlist.strings in Resources */,
|
||||
433BBBB91D7EF8260086B6E9 /* TestImage.png in Resources */,
|
||||
433BBBBB1D7EFA8B0086B6E9 /* MonochromeTestImage.jpg in Resources */,
|
||||
|
@ -210,13 +220,16 @@
|
|||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
|
||||
"${PODS_ROOT}/Manifest.lock",
|
||||
);
|
||||
name = "[CP] Check Pods Manifest.lock";
|
||||
outputPaths = (
|
||||
"$(DERIVED_FILE_DIR)/Pods-Tests-checkManifestLockResult.txt",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n";
|
||||
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
85E5D3885A03BFC23B050908 /* [CP] Copy Pods Resources */ = {
|
||||
|
@ -240,9 +253,18 @@
|
|||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
"${SRCROOT}/Pods/Target Support Files/Pods-Tests/Pods-Tests-frameworks.sh",
|
||||
"${BUILT_PRODUCTS_DIR}/Expecta/Expecta.framework",
|
||||
"${BUILT_PRODUCTS_DIR}/FLAnimatedImage/FLAnimatedImage.framework",
|
||||
"${BUILT_PRODUCTS_DIR}/SDWebImage/SDWebImage.framework",
|
||||
"${BUILT_PRODUCTS_DIR}/libwebp/libwebp.framework",
|
||||
);
|
||||
name = "[CP] Embed Pods Frameworks";
|
||||
outputPaths = (
|
||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Expecta.framework",
|
||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FLAnimatedImage.framework",
|
||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SDWebImage.framework",
|
||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/libwebp.framework",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
|
@ -256,6 +278,7 @@
|
|||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
32E6F0321F3A1B4700A945E6 /* SDWebImageTestDecoder.m in Sources */,
|
||||
1E3C51E919B46E370092B5E6 /* SDWebImageDownloaderTests.m in Sources */,
|
||||
4369C2741D9804B1007E863A /* SDCategoriesTests.m in Sources */,
|
||||
2D7AF0601F329763000083C2 /* SDTestCase.m in Sources */,
|
||||
|
@ -263,7 +286,6 @@
|
|||
DA248D69195475D800390AB0 /* SDImageCacheTests.m in Sources */,
|
||||
DA248D6B195476AC00390AB0 /* SDWebImageManagerTests.m in Sources */,
|
||||
433BBBB51D7EF5C00086B6E9 /* SDWebImageDecoderTests.m in Sources */,
|
||||
DA91BEBC19795BC9006F2536 /* UIImageMultiFormatTests.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
|
|
@ -125,7 +125,7 @@
|
|||
XCTestExpectation *expectation = [self expectationWithDescription:@"FLAnimatedImageView setImageWithURL"];
|
||||
|
||||
FLAnimatedImageView *imageView = [[FLAnimatedImageView alloc] init];
|
||||
NSURL *originalImageURL = [NSURL URLWithString:@"https://raw.githubusercontent.com/liyong03/YLGIFImage/master/YLGIFImageDemo/YLGIFImageDemo/joy.gif"];
|
||||
NSURL *originalImageURL = [NSURL URLWithString:@"https://www.interntheory.com/img/loading-small.gif"];
|
||||
|
||||
[imageView sd_setImageWithURL:originalImageURL
|
||||
completed:^(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL) {
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
|
||||
#import "SDTestCase.h"
|
||||
#import <SDWebImage/SDImageCache.h>
|
||||
#import <SDWebImage/SDWebImageCodersManager.h>
|
||||
#import "SDWebImageTestDecoder.h"
|
||||
|
||||
NSString *kImageTestKey = @"TestImageKey.jpg";
|
||||
|
||||
|
@ -190,24 +192,86 @@ NSString *kImageTestKey = @"TestImageKey.jpg";
|
|||
// TODO -- Testing image data insertion
|
||||
|
||||
- (void)test40InsertionOfImageData {
|
||||
XCTestExpectation *expectation = [self expectationWithDescription:@"Insertion of image data works"];
|
||||
|
||||
NSData *imageData = [NSData dataWithContentsOfFile:[self testImagePath]];
|
||||
UIImage *image = [UIImage imageWithContentsOfFile:[self testImagePath]];
|
||||
NSData *imageData = UIImageJPEGRepresentation(image, 1.0);
|
||||
[self.sharedImageCache storeImageDataToDisk:imageData forKey:kImageTestKey];
|
||||
|
||||
UIImage *storedImageFromMemory = [self.sharedImageCache imageFromMemoryCacheForKey:kImageTestKey];
|
||||
expect(storedImageFromMemory).to.equal(nil);
|
||||
|
||||
NSString *cachePath = [self.sharedImageCache defaultCachePathForKey:kImageTestKey];
|
||||
NSData *storedImageData = [NSData dataWithContentsOfFile:cachePath];
|
||||
expect([storedImageData isEqualToData:imageData]).will.beTruthy;
|
||||
UIImage *cachedImage = [UIImage imageWithContentsOfFile:cachePath];
|
||||
NSData *storedImageData = UIImageJPEGRepresentation(cachedImage, 1.0);
|
||||
expect(storedImageData.length).to.beGreaterThan(0);
|
||||
expect(cachedImage.size).to.equal(image.size);
|
||||
// can't directly compare image and cachedImage because apparently there are some slight differences, even though the image is the same
|
||||
|
||||
__block int blocksCalled = 0;
|
||||
|
||||
[self.sharedImageCache diskImageExistsWithKey:kImageTestKey completion:^(BOOL isInCache) {
|
||||
expect(isInCache).to.equal(YES);
|
||||
blocksCalled += 1;
|
||||
if (blocksCalled == 2) {
|
||||
[expectation fulfill];
|
||||
}
|
||||
}];
|
||||
|
||||
[self.sharedImageCache calculateSizeWithCompletionBlock:^(NSUInteger fileCount, NSUInteger totalSize) {
|
||||
expect(fileCount).to.beLessThan(100);
|
||||
blocksCalled += 1;
|
||||
if (blocksCalled == 2) {
|
||||
[expectation fulfill];
|
||||
}
|
||||
}];
|
||||
|
||||
[self waitForExpectationsWithCommonTimeout];
|
||||
}
|
||||
|
||||
- (void)test41ThatCustomDecoderWorksForImageCache {
|
||||
XCTestExpectation *expectation = [self expectationWithDescription:@"Custom decoder for SDImageCache not works"];
|
||||
SDImageCache *cache = [[SDImageCache alloc] initWithNamespace:@"TestDecode"];
|
||||
SDWebImageTestDecoder *testDecoder = [[SDWebImageTestDecoder alloc] init];
|
||||
[[SDWebImageCodersManager sharedInstance] addCoder:testDecoder];
|
||||
NSString * testImagePath = [[NSBundle bundleForClass:[self class]] pathForResource:@"TestImage" ofType:@"png"];
|
||||
UIImage *image = [UIImage imageWithContentsOfFile:testImagePath];
|
||||
NSString *key = @"TestPNGImageEncodedToDataAndRetrieveToJPEG";
|
||||
|
||||
[cache storeImage:image imageData:nil forKey:key toDisk:YES completion:^{
|
||||
[cache clearMemory];
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wundeclared-selector"
|
||||
SEL diskImageDataBySearchingAllPathsForKey = @selector(diskImageDataBySearchingAllPathsForKey:);
|
||||
#pragma clang diagnostic pop
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
|
||||
NSData *data = [cache performSelector:diskImageDataBySearchingAllPathsForKey withObject:key];
|
||||
#pragma clang diagnostic pop
|
||||
NSString *str1 = @"TestEncode";
|
||||
NSString *str2 = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
|
||||
if (![str1 isEqualToString:str2]) {
|
||||
XCTFail(@"Custom decoder not work for SDImageCache, check -[SDWebImageTestDecoder encodedDataWithImage:format:]");
|
||||
}
|
||||
|
||||
UIImage *diskCacheImage = [cache imageFromDiskCacheForKey:key];
|
||||
|
||||
// Decoded result is JPEG
|
||||
NSString * decodedImagePath = [[NSBundle bundleForClass:[self class]] pathForResource:@"TestImage" ofType:@"jpg"];
|
||||
UIImage *testJPEGImage = [UIImage imageWithContentsOfFile:decodedImagePath];
|
||||
|
||||
NSData *data1 = UIImagePNGRepresentation(testJPEGImage);
|
||||
NSData *data2 = UIImagePNGRepresentation(diskCacheImage);
|
||||
|
||||
if (![data1 isEqualToData:data2]) {
|
||||
XCTFail(@"Custom decoder not work for SDImageCache, check -[SDWebImageTestDecoder decodedImageWithData:]");
|
||||
}
|
||||
|
||||
[[SDWebImageCodersManager sharedInstance] removeCoder:testDecoder];
|
||||
[expectation fulfill];
|
||||
}];
|
||||
|
||||
[self waitForExpectationsWithCommonTimeout];
|
||||
}
|
||||
|
||||
#pragma mark Helper methods
|
||||
|
|
|
@ -8,7 +8,9 @@
|
|||
*/
|
||||
|
||||
#import "SDTestCase.h"
|
||||
#import <SDWebImage/SDWebImageDecoder.h>
|
||||
#import <SDWebImage/SDWebImageImageIOCoder.h>
|
||||
#import <SDWebImage/SDWebImageWebPCoder.h>
|
||||
#import <SDWebImage/UIImage+ForceDecode.h>
|
||||
|
||||
@interface SDWebImageDecoderTests : SDTestCase
|
||||
|
||||
|
@ -78,4 +80,48 @@
|
|||
expect(decodedImage.size.height).to.equal(image.size.height);
|
||||
}
|
||||
|
||||
- (void)test08ImageOrientationFromImageDataWithInvalidData {
|
||||
// sync download image
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wundeclared-selector"
|
||||
SEL selector = @selector(sd_imageOrientationFromImageData:);
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
|
||||
UIImageOrientation orientation = (UIImageOrientation)[[SDWebImageImageIOCoder class] performSelector:selector withObject:nil];
|
||||
#pragma clang diagnostic pop
|
||||
expect(orientation).to.equal(UIImageOrientationUp);
|
||||
}
|
||||
|
||||
- (void)test09ThatStaticWebPCoderWorks {
|
||||
NSURL *staticWebPURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"TestImageStatic" withExtension:@"webp"];
|
||||
NSData *staticWebPData = [NSData dataWithContentsOfURL:staticWebPURL];
|
||||
UIImage *staticWebPImage = [[SDWebImageWebPCoder sharedCoder] decodedImageWithData:staticWebPData];
|
||||
expect(staticWebPImage).toNot.beNil();
|
||||
|
||||
NSData *outputData = [[SDWebImageWebPCoder sharedCoder] encodedDataWithImage:staticWebPImage format:SDImageFormatWebP];
|
||||
expect(outputData).toNot.beNil();
|
||||
}
|
||||
|
||||
- (void)test10ThatAnimatedWebPCoderWorks {
|
||||
NSURL *animatedWebPURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"TestImageAnimated" withExtension:@"webp"];
|
||||
NSData *animatedWebPData = [NSData dataWithContentsOfURL:animatedWebPURL];
|
||||
UIImage *animatedWebPImage = [[SDWebImageWebPCoder sharedCoder] decodedImageWithData:animatedWebPData];
|
||||
expect(animatedWebPImage).toNot.beNil();
|
||||
expect(animatedWebPImage.images.count).to.beGreaterThan(0);
|
||||
CGSize imageSize = animatedWebPImage.size;
|
||||
CGFloat imageScale = animatedWebPImage.scale;
|
||||
[animatedWebPImage.images enumerateObjectsUsingBlock:^(UIImage * _Nonnull image, NSUInteger idx, BOOL * _Nonnull stop) {
|
||||
CGSize size = image.size;
|
||||
CGFloat scale = image.scale;
|
||||
expect(imageSize.width).to.equal(size.width);
|
||||
expect(imageSize.height).to.equal(size.height);
|
||||
expect(imageScale).to.equal(scale);
|
||||
}];
|
||||
|
||||
NSData *outputData = [[SDWebImageWebPCoder sharedCoder] encodedDataWithImage:animatedWebPImage format:SDImageFormatWebP];
|
||||
expect(outputData).toNot.beNil();
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -10,6 +10,8 @@
|
|||
#import "SDTestCase.h"
|
||||
#import <SDWebImage/SDWebImageDownloader.h>
|
||||
#import <SDWebImage/SDWebImageDownloaderOperation.h>
|
||||
#import <SDWebImage/SDWebImageCodersManager.h>
|
||||
#import "SDWebImageTestDecoder.h"
|
||||
|
||||
/**
|
||||
* Category for SDWebImageDownloader so we can access the operationClass
|
||||
|
@ -256,6 +258,21 @@
|
|||
[self waitForExpectationsWithCommonTimeout];
|
||||
}
|
||||
|
||||
- (void)test16ThatProgressiveWebPWorks {
|
||||
XCTestExpectation *expectation = [self expectationWithDescription:@"Progressive WebP download"];
|
||||
NSURL *imageURL = [NSURL URLWithString:@"http://www.ioncannon.net/wp-content/uploads/2011/06/test9.webp"];
|
||||
[[SDWebImageDownloader sharedDownloader] downloadImageWithURL:imageURL options:SDWebImageDownloaderProgressiveDownload progress:nil completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, BOOL finished) {
|
||||
if (image && data && !error && finished) {
|
||||
[expectation fulfill];
|
||||
} else if (finished) {
|
||||
XCTFail(@"Something went wrong");
|
||||
} else {
|
||||
// progressive updates
|
||||
}
|
||||
}];
|
||||
[self waitForExpectationsWithCommonTimeout];
|
||||
}
|
||||
|
||||
/**
|
||||
* Per #883 - Fix multiple requests for same image and then canceling one
|
||||
* Old SDWebImage (3.x) could not handle correctly multiple requests for the same image + cancel
|
||||
|
@ -333,4 +350,33 @@
|
|||
[self waitForExpectationsWithCommonTimeout];
|
||||
}
|
||||
|
||||
- (void)test22ThatCustomDecoderWorksForImageDownload {
|
||||
XCTestExpectation *expectation = [self expectationWithDescription:@"Custom decoder for SDWebImageDownloader not works"];
|
||||
SDWebImageDownloader *downloader = [[SDWebImageDownloader alloc] init];
|
||||
SDWebImageTestDecoder *testDecoder = [[SDWebImageTestDecoder alloc] init];
|
||||
[[SDWebImageCodersManager sharedInstance] addCoder:testDecoder];
|
||||
NSURL * testImageURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"TestImage" withExtension:@"png"];
|
||||
|
||||
// Decoded result is JPEG
|
||||
NSString *testJPEGImagePath = [[NSBundle bundleForClass:[self class]] pathForResource:@"TestImage" ofType:@"jpg"];
|
||||
UIImage *testJPEGImage = [UIImage imageWithContentsOfFile:testJPEGImagePath];
|
||||
|
||||
[downloader downloadImageWithURL:testImageURL options:0 progress:nil completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, BOOL finished) {
|
||||
NSData *data1 = UIImagePNGRepresentation(testJPEGImage);
|
||||
NSData *data2 = UIImagePNGRepresentation(image);
|
||||
if (![data1 isEqualToData:data2]) {
|
||||
XCTFail(@"The image data is not equal to cutom decoder, check -[SDWebImageTestDecoder decodedImageWithData:]");
|
||||
}
|
||||
NSString *str1 = @"TestDecompress";
|
||||
NSString *str2 = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
|
||||
if (![str1 isEqualToString:str2]) {
|
||||
XCTFail(@"The image data is not modified by the custom decoder, check -[SDWebImageTestDecoder decompressedImageWithImage:data:options:]");
|
||||
}
|
||||
[[SDWebImageCodersManager sharedInstance] removeCoder:testDecoder];
|
||||
[expectation fulfill];
|
||||
}];
|
||||
|
||||
[self waitForExpectationsWithCommonTimeout];
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
/*
|
||||
* This file is part of the SDWebImage package.
|
||||
* (c) Olivier Poitrey <rs@dailymotion.com>
|
||||
* (c) Matt Galloway
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <SDWebImage/SDWebImageCoder.h>
|
||||
|
||||
@interface SDWebImageTestDecoder : NSObject <SDWebImageCoder>
|
||||
|
||||
@end
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* This file is part of the SDWebImage package.
|
||||
* (c) Olivier Poitrey <rs@dailymotion.com>
|
||||
* (c) Matt Galloway
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
#import "SDWebImageTestDecoder.h"
|
||||
|
||||
@implementation SDWebImageTestDecoder
|
||||
|
||||
- (BOOL)canDecodeFromData:(nullable NSData *)data {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)canEncodeToFormat:(SDImageFormat)format {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (UIImage *)decodedImageWithData:(NSData *)data {
|
||||
NSString * testImagePath = [[NSBundle bundleForClass:[self class]] pathForResource:@"TestImage" ofType:@"jpg"];
|
||||
UIImage *image = [UIImage imageWithContentsOfFile:testImagePath];
|
||||
return image;
|
||||
}
|
||||
|
||||
- (UIImage *)incrementallyDecodedImageWithData:(NSData *)data finished:(BOOL)finished {
|
||||
NSString * testImagePath = [[NSBundle bundleForClass:[self class]] pathForResource:@"TestImage" ofType:@"gif"];
|
||||
UIImage *image = [UIImage imageWithContentsOfFile:testImagePath];
|
||||
return image;
|
||||
}
|
||||
|
||||
- (UIImage *)decompressedImageWithImage:(UIImage *)image
|
||||
data:(NSData *__autoreleasing _Nullable *)data
|
||||
options:(nullable NSDictionary<NSString*, NSObject*>*)optionsDict {
|
||||
NSString *testString = @"TestDecompress";
|
||||
NSData *testData = [testString dataUsingEncoding:NSUTF8StringEncoding];
|
||||
*data = testData;
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
- (NSData *)encodedDataWithImage:(UIImage *)image format:(SDImageFormat)format {
|
||||
NSString *testString = @"TestEncode";
|
||||
NSData *data = [testString dataUsingEncoding:NSUTF8StringEncoding];
|
||||
return data;
|
||||
}
|
||||
|
||||
@end
|
Binary file not shown.
After Width: | Height: | Size: 4.7 KiB |
Binary file not shown.
After Width: | Height: | Size: 30 KiB |
|
@ -1,56 +0,0 @@
|
|||
/*
|
||||
* This file is part of the SDWebImage package.
|
||||
* (c) Olivier Poitrey <rs@dailymotion.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
#import "SDTestCase.h"
|
||||
#import <SDWebImage/UIImage+MultiFormat.h>
|
||||
|
||||
@interface UIImageMultiFormatTests : SDTestCase
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation UIImageMultiFormatTests
|
||||
|
||||
- (void)test01ImageOrientationFromImageDataWithInvalidData {
|
||||
// sync download image
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wundeclared-selector"
|
||||
SEL selector = @selector(sd_imageOrientationFromImageData:);
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
|
||||
UIImageOrientation orientation = (UIImageOrientation)[[UIImage class] performSelector:selector withObject:nil];
|
||||
#pragma clang diagnostic pop
|
||||
expect(orientation).to.equal(UIImageOrientationUp);
|
||||
}
|
||||
|
||||
- (void)test02AnimatedWebPImageArrayWithEqualSizeAndScale {
|
||||
NSURL *webpURL = [NSURL URLWithString:@"https://isparta.github.io/compare-webp/image/gif_webp/webp/2.webp"];
|
||||
NSData *data = [NSData dataWithContentsOfURL:webpURL];
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wundeclared-selector"
|
||||
SEL selector = @selector(sd_imageWithWebPData:);
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
|
||||
UIImage *animatedImage = [[UIImage class] performSelector:selector withObject:data];
|
||||
#pragma clang diagnostic pop
|
||||
CGSize imageSize = animatedImage.size;
|
||||
CGFloat imageScale = animatedImage.scale;
|
||||
[animatedImage.images enumerateObjectsUsingBlock:^(UIImage * _Nonnull image, NSUInteger idx, BOOL * _Nonnull stop) {
|
||||
CGSize size = image.size;
|
||||
CGFloat scale = image.scale;
|
||||
expect(imageSize.width).to.equal(size.width);
|
||||
expect(imageSize.height).to.equal(size.height);
|
||||
expect(imageScale).to.equal(scale);
|
||||
}];
|
||||
}
|
||||
|
||||
@end
|
|
@ -39,9 +39,14 @@ FOUNDATION_EXPORT const unsigned char WebImageVersionString[];
|
|||
#import <SDWebImage/MKAnnotationView+WebCache.h>
|
||||
#endif
|
||||
|
||||
#import <SDWebImage/SDWebImageDecoder.h>
|
||||
#import <SDWebImage/SDWebImageCodersManager.h>
|
||||
#import <SDWebImage/SDWebImageCoder.h>
|
||||
#import <SDWebImage/SDWebImageWebPCoder.h>
|
||||
#import <SDWebImage/SDWebImageGIFCoder.h>
|
||||
#import <SDWebImage/SDWebImageImageIOCoder.h>
|
||||
#import <SDWebImage/UIImage+WebP.h>
|
||||
#import <SDWebImage/UIImage+GIF.h>
|
||||
#import <SDWebImage/UIImage+ForceDecode.h>
|
||||
#import <SDWebImage/NSData+ImageContentType.h>
|
||||
|
||||
#if SD_MAC
|
||||
|
|
Loading…
Reference in New Issue