From 22c293738ad00dac212f575e9b4c9861a48e1847 Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Mon, 9 Apr 2018 13:42:52 +0800 Subject: [PATCH 1/2] Update the progressive coder/ animated coder init API, which pass the options to support extensibility --- SDWebImage/SDAnimatedImage.m | 2 +- SDWebImage/SDWebImageAPNGCoder.m | 37 ++++++++++++++++------ SDWebImage/SDWebImageCoder.h | 32 +++++++++++++++---- SDWebImage/SDWebImageCoder.m | 2 ++ SDWebImage/SDWebImageDownloaderOperation.m | 2 +- SDWebImage/SDWebImageGIFCoder.m | 21 +++++++++--- SDWebImage/SDWebImageImageIOCoder.m | 2 +- SDWebImage/SDWebImageWebPCoder.m | 25 +++++++++++---- Tests/Tests/SDAnimatedImageTest.m | 17 +++++++--- Tests/Tests/SDWebImageTestDecoder.m | 2 +- 10 files changed, 106 insertions(+), 36 deletions(-) diff --git a/SDWebImage/SDAnimatedImage.m b/SDWebImage/SDAnimatedImage.m index 3dccbdeb..6a715658 100644 --- a/SDWebImage/SDAnimatedImage.m +++ b/SDWebImage/SDAnimatedImage.m @@ -287,7 +287,7 @@ static NSArray *SDBundlePreferredScales() { for (idcoder in [SDWebImageCodersManager sharedManager].coders) { if ([coder conformsToProtocol:@protocol(SDWebImageAnimatedCoder)]) { if ([coder canDecodeFromData:data]) { - animatedCoder = [[[coder class] alloc] initWithAnimatedImageData:data]; + animatedCoder = [[[coder class] alloc] initWithAnimatedImageData:data options:@{SDWebImageCoderDecodeScaleFactor : @(scale)}]; break; } } diff --git a/SDWebImage/SDWebImageAPNGCoder.m b/SDWebImage/SDWebImageAPNGCoder.m index 0fea08c6..c3bb1e82 100644 --- a/SDWebImage/SDWebImageAPNGCoder.m +++ b/SDWebImage/SDWebImageAPNGCoder.m @@ -38,6 +38,7 @@ const CFStringRef kCGImagePropertyAPNGUnclampedDelayTime = (__bridge CFStringRef #endif CGImageSourceRef _imageSource; NSData *_imageData; + CGFloat _scale; NSUInteger _loopCount; NSUInteger _frameCount; NSArray *_frames; @@ -106,7 +107,8 @@ const CFStringRef kCGImagePropertyAPNGUnclampedDelayTime = (__bridge CFStringRef size_t count = CGImageSourceGetCount(source); UIImage *animatedImage; - if (count <= 1) { + BOOL decodeFirstFrame = [options[SDWebImageCoderDecodeFirstFrameOnly] boolValue]; + if (decodeFirstFrame || count <= 1) { animatedImage = [[UIImage alloc] initWithData:data scale:scale]; } else { NSMutableArray *frames = [NSMutableArray array]; @@ -198,14 +200,23 @@ const CFStringRef kCGImagePropertyAPNGUnclampedDelayTime = (__bridge CFStringRef // Handle failure. return nil; } - if (frames.count == 0) { + NSMutableDictionary *properties = [NSMutableDictionary dictionary]; + double compressionQuality = 1; + if ([options valueForKey:SDWebImageCoderEncodeCompressionQuality]) { + compressionQuality = [[options valueForKey:SDWebImageCoderEncodeCompressionQuality] doubleValue]; + } + [properties setValue:@(compressionQuality) forKey:(__bridge_transfer NSString *)kCGImageDestinationLossyCompressionQuality]; + + BOOL encodeFirstFrame = [options[SDWebImageCoderEncodeFirstFrameOnly] boolValue]; + if (encodeFirstFrame || frames.count == 0) { // for static single PNG images - CGImageDestinationAddImage(imageDestination, image.CGImage, nil); + CGImageDestinationAddImage(imageDestination, image.CGImage, (__bridge CFDictionaryRef)properties); } else { // for animated APNG images NSUInteger loopCount = image.sd_imageLoopCount; - NSDictionary *pngProperties = @{(__bridge_transfer NSString *)kCGImagePropertyPNGDictionary: @{(__bridge_transfer NSString *)kCGImagePropertyAPNGLoopCount : @(loopCount)}}; - CGImageDestinationSetProperties(imageDestination, (__bridge CFDictionaryRef)pngProperties); + NSDictionary *pngProperties = @{(__bridge_transfer NSString *)kCGImagePropertyAPNGLoopCount : @(loopCount)}; + [properties setValue:pngProperties forKey:(__bridge_transfer NSString *)kCGImagePropertyPNGDictionary]; + CGImageDestinationSetProperties(imageDestination, (__bridge CFDictionaryRef)properties); for (size_t i = 0; i < frames.count; i++) { SDWebImageFrame *frame = frames[i]; @@ -232,7 +243,7 @@ const CFStringRef kCGImagePropertyAPNGUnclampedDelayTime = (__bridge CFStringRef return ([NSData sd_imageFormatForImageData:data] == SDImageFormatPNG); } -- (instancetype)initIncremental { +- (instancetype)initIncrementalWithOptions:(nullable SDWebImageCoderOptions *)options { self = [super init]; if (self) { _imageSource = CGImageSourceCreateIncremental((__bridge CFDictionaryRef)@{(__bridge_transfer NSString *)kCGImageSourceShouldCache : @(YES)}); @@ -299,7 +310,7 @@ const CFStringRef kCGImagePropertyAPNGUnclampedDelayTime = (__bridge CFStringRef } #pragma mark - SDWebImageAnimatedCoder -- (nullable instancetype)initWithAnimatedImageData:(nullable NSData *)data { +- (nullable instancetype)initWithAnimatedImageData:(nullable NSData *)data options:(nullable SDWebImageCoderOptions *)options { if (!data) { return nil; } @@ -315,6 +326,14 @@ const CFStringRef kCGImagePropertyAPNGUnclampedDelayTime = (__bridge CFStringRef CFRelease(imageSource); return nil; } + CGFloat scale = 1; + if ([options valueForKey:SDWebImageCoderDecodeScaleFactor]) { + scale = [[options valueForKey:SDWebImageCoderDecodeScaleFactor] doubleValue]; + if (scale < 1) { + scale = 1; + } + } + _scale = scale; _imageSource = imageSource; _imageData = data; #if SD_UIKIT @@ -384,9 +403,9 @@ const CFStringRef kCGImagePropertyAPNGUnclampedDelayTime = (__bridge CFStringRef CGImageRelease(imageRef); } #if SD_MAC - UIImage *image = [[UIImage alloc] initWithCGImage:newImageRef scale:1]; + UIImage *image = [[UIImage alloc] initWithCGImage:newImageRef scale:_scale]; #else - UIImage *image = [UIImage imageWithCGImage:newImageRef]; + UIImage *image = [UIImage imageWithCGImage:newImageRef scale:_scale orientation:UIImageOrientationUp]; #endif CGImageRelease(newImageRef); return image; diff --git a/SDWebImage/SDWebImageCoder.h b/SDWebImage/SDWebImageCoder.h index 22fbe61e..de73401c 100644 --- a/SDWebImage/SDWebImageCoder.h +++ b/SDWebImage/SDWebImageCoder.h @@ -13,19 +13,32 @@ typedef NSString * SDWebImageCoderOption NS_STRING_ENUM; typedef NSDictionary SDWebImageCoderOptions; +#pragma mark - Coder Options +// These options are for image decoding /** - A Boolean value indicating whether to decode the first frame only for animated image during decoding. (NSNumber) + A Boolean value indicating whether to decode the first frame only for animated image during decoding. (NSNumber). If not provide, decode animated image if need. + @note works for `SDWebImageCoder`. */ FOUNDATION_EXPORT SDWebImageCoderOption _Nonnull const SDWebImageCoderDecodeFirstFrameOnly; /** A CGFloat value which is greater than or equal to 1.0. This value specify the image scale factor for decoding. If not provide, use 1.0. (NSNumber) + @note works for `SDWebImageCoder`, `SDWebImageProgressiveCoder`, `SDWebImageAnimatedCoder`. */ FOUNDATION_EXPORT SDWebImageCoderOption _Nonnull const SDWebImageCoderDecodeScaleFactor; + +// These options are for image encoding +/** + A Boolean value indicating whether to encode the first frame only for animated image during encoding. (NSNumber). If not provide, encode animated image if need. + @note works for `SDWebImageCoder`. + */ +FOUNDATION_EXPORT SDWebImageCoderOption _Nonnull const SDWebImageCoderEncodeFirstFrameOnly; /** A double value between 0.0-1.0 indicating the encode compression quality to produce the image data. If not provide, use 1.0. (NSNumber) + @note works for `SDWebImageCoder` */ FOUNDATION_EXPORT SDWebImageCoderOption _Nonnull const SDWebImageCoderEncodeCompressionQuality; +#pragma mark - Coder /** This is the image coder protocol to provide custom image decoding/encoding. These methods are all required to implement. @@ -46,6 +59,7 @@ FOUNDATION_EXPORT SDWebImageCoderOption _Nonnull const SDWebImageCoderEncodeComp /** Decode the image data to image. + @note This protocol may supports decode animated image frames. You can use `+[SDWebImageCoderHelper animatedImageWithFrames:]` to produce an animated image with frames. @param data The image data to be decoded @param options A dictionary containing any decoding options. Pass @{SDWebImageCoderDecodeFirstFrameOnly: @(YES)} to decode the first frame only. @@ -66,6 +80,7 @@ FOUNDATION_EXPORT SDWebImageCoderOption _Nonnull const SDWebImageCoderEncodeComp /** Encode the image to image data. + @note This protocol may supports encode animated image frames. You can use `+[SDWebImageCoderHelper framesFromAnimatedImage:]` to assemble an animated image with frames. @param image The image to be encoded @param format The image format to encode, you should note `SDImageFormatUndefined` format is also possible @@ -78,7 +93,7 @@ FOUNDATION_EXPORT SDWebImageCoderOption _Nonnull const SDWebImageCoderEncodeComp @end - +#pragma mark - Progressive Coder /** This is the image coder protocol to provide custom progressive image decoding. These methods are all required to implement. @@ -99,9 +114,10 @@ FOUNDATION_EXPORT SDWebImageCoderOption _Nonnull const SDWebImageCoderEncodeComp 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 This init method should not return nil + @param options A dictionary containing any progressive decoding options (instance-level). Currentlly there is no options for this and always pass nil. Kept for extensibility. @return A new instance to do incremental decoding for the specify image format */ -- (nonnull instancetype)initIncremental; +- (nonnull instancetype)initIncrementalWithOptions:(nullable SDWebImageCoderOptions *)options; /** Update the incremental decoding when new image data available @@ -113,14 +129,16 @@ FOUNDATION_EXPORT SDWebImageCoderOption _Nonnull const SDWebImageCoderEncodeComp /** Incremental decode the current image data to image. + @note Due to the performance issue for progressive decoding and the integration for image view. This method may only return the first frame image even if the image data is animated image. If you want progressive animated image decoding, also conform to `SDWebImageAnimatedCoder` and use `animatedImageFrameAtIndex` instead. - @param options A dictionary containing any decoding options. + @param options A dictionary containing any progressive decoding options. Pass @{SDWebImageCoderDecodeScaleFactor: @(1.0)} to specify scale factor for progressive image @return The decoded image from current data */ - (nullable UIImage *)incrementalDecodedImageWithOptions:(nullable SDWebImageCoderOptions *)options; @end +#pragma mark - Animated Image Provider /** This is the animated image protocol to provide the basic function for animated image rendering. It's adopted by `SDAnimatedImage` and `SDWebImageAnimatedCoder` */ @@ -167,6 +185,7 @@ FOUNDATION_EXPORT SDWebImageCoderOption _Nonnull const SDWebImageCoderEncodeComp @end +#pragma mark - Animated Coder /** This is the animated image coder protocol for custom animated image class like `SDAnimatedImage`. Through it inherit from `SDWebImageCoder`. We currentlly only use the method `canDecodeFromData:` to detect the proper coder for specify animated image format. */ @@ -176,11 +195,12 @@ FOUNDATION_EXPORT SDWebImageCoderOption _Nonnull const SDWebImageCoderEncodeComp /** Because animated image coder should keep the original data, we will alloc a new instance with the same class for the specify animated image data The init method should return nil if it can't decode the specify animated image data to produce any frame. - After the instance created, we may call methods in `SDAnimatedImage` to produce animated image frame. + After the instance created, we may call methods in `SDAnimatedImageProvider` to produce animated image frame. @param data The animated image data to be decode + @param options A dictionary containing any animated decoding options (instance-level). Pass @{SDWebImageCoderDecodeScaleFactor: @(1.0)} to specify scale factor for animated image (each frames should use the same scale). @return A new instance to do animated decoding for specify image data */ -- (nullable instancetype)initWithAnimatedImageData:(nullable NSData *)data; +- (nullable instancetype)initWithAnimatedImageData:(nullable NSData *)data options:(nullable SDWebImageCoderOptions *)options; @end diff --git a/SDWebImage/SDWebImageCoder.m b/SDWebImage/SDWebImageCoder.m index c7efce68..2778ea3c 100644 --- a/SDWebImage/SDWebImageCoder.m +++ b/SDWebImage/SDWebImageCoder.m @@ -10,4 +10,6 @@ SDWebImageCoderOption const SDWebImageCoderDecodeFirstFrameOnly = @"decodeFirstFrameOnly"; SDWebImageCoderOption const SDWebImageCoderDecodeScaleFactor = @"decodeScaleFactor"; + +SDWebImageCoderOption const SDWebImageCoderEncodeFirstFrameOnly = @"encodeFirstFrameOnly"; SDWebImageCoderOption const SDWebImageCoderEncodeCompressionQuality = @"encodeCompressionQuality"; diff --git a/SDWebImage/SDWebImageDownloaderOperation.m b/SDWebImage/SDWebImageDownloaderOperation.m index 7dd973b4..3b2cd0f0 100644 --- a/SDWebImage/SDWebImageDownloaderOperation.m +++ b/SDWebImage/SDWebImageDownloaderOperation.m @@ -352,7 +352,7 @@ didReceiveResponse:(NSURLResponse *)response for (idcoder in [SDWebImageCodersManager sharedManager].coders) { if ([coder conformsToProtocol:@protocol(SDWebImageProgressiveCoder)] && [((id)coder) canIncrementalDecodeFromData:imageData]) { - self.progressiveCoder = [[[coder class] alloc] initIncremental]; + self.progressiveCoder = [[[coder class] alloc] initIncrementalWithOptions:nil]; break; } } diff --git a/SDWebImage/SDWebImageGIFCoder.m b/SDWebImage/SDWebImageGIFCoder.m index 43e646a3..4b95b7bb 100644 --- a/SDWebImage/SDWebImageGIFCoder.m +++ b/SDWebImage/SDWebImageGIFCoder.m @@ -28,6 +28,7 @@ size_t _width, _height; CGImageSourceRef _imageSource; NSData *_imageData; + CGFloat _scale; NSUInteger _loopCount; NSUInteger _frameCount; NSArray *_frames; @@ -179,7 +180,7 @@ return ([NSData sd_imageFormatForImageData:data] == SDImageFormatGIF); } -- (instancetype)initIncremental { +- (instancetype)initIncrementalWithOptions:(nullable SDWebImageCoderOptions *)options { self = [super init]; if (self) { _imageSource = CGImageSourceCreateIncremental((__bridge CFDictionaryRef)@{(__bridge_transfer NSString *)kCGImageSourceShouldCache : @(YES)}); @@ -275,7 +276,9 @@ compressionQuality = [[options valueForKey:SDWebImageCoderEncodeCompressionQuality] doubleValue]; } [properties setValue:@(compressionQuality) forKey:(__bridge_transfer NSString *)kCGImageDestinationLossyCompressionQuality]; - if (frames.count == 0) { + + BOOL encodeFirstFrame = [options[SDWebImageCoderEncodeFirstFrameOnly] boolValue]; + if (encodeFirstFrame || frames.count == 0) { // for static single GIF images CGImageDestinationAddImage(imageDestination, image.CGImage, (__bridge CFDictionaryRef)properties); } else { @@ -305,7 +308,7 @@ } #pragma mark - SDWebImageAnimatedCoder -- (nullable instancetype)initWithAnimatedImageData:(nullable NSData *)data { +- (nullable instancetype)initWithAnimatedImageData:(nullable NSData *)data options:(nullable SDWebImageCoderOptions *)options { if (!data) { return nil; } @@ -321,6 +324,14 @@ CFRelease(imageSource); return nil; } + CGFloat scale = 1; + if ([options valueForKey:SDWebImageCoderDecodeScaleFactor]) { + scale = [[options valueForKey:SDWebImageCoderDecodeScaleFactor] doubleValue]; + if (scale < 1) { + scale = 1; + } + } + _scale = scale; _imageSource = imageSource; _imageData = data; #if SD_UIKIT @@ -384,9 +395,9 @@ CGImageRelease(imageRef); } #if SD_MAC - UIImage *image = [[UIImage alloc] initWithCGImage:newImageRef scale:1]; + UIImage *image = [[UIImage alloc] initWithCGImage:newImageRef scale:_scale]; #else - UIImage *image = [[UIImage alloc] initWithCGImage:newImageRef]; + UIImage *image = [[UIImage alloc] initWithCGImage:newImageRef scale:_scale orientation:UIImageOrientationUp]; #endif CGImageRelease(newImageRef); return image; diff --git a/SDWebImage/SDWebImageImageIOCoder.m b/SDWebImage/SDWebImageImageIOCoder.m index 47e622df..92101d25 100644 --- a/SDWebImage/SDWebImageImageIOCoder.m +++ b/SDWebImage/SDWebImageImageIOCoder.m @@ -107,7 +107,7 @@ } #pragma mark - Progressive Decode -- (instancetype)initIncremental { +- (instancetype)initIncrementalWithOptions:(nullable SDWebImageCoderOptions *)options { self = [super init]; if (self) { _imageSource = CGImageSourceCreateIncremental(NULL); diff --git a/SDWebImage/SDWebImageWebPCoder.m b/SDWebImage/SDWebImageWebPCoder.m index 2c91178d..4d18d7bf 100644 --- a/SDWebImage/SDWebImageWebPCoder.m +++ b/SDWebImage/SDWebImageWebPCoder.m @@ -51,6 +51,7 @@ dispatch_semaphore_signal(self->_lock); WebPIDecoder *_idec; WebPDemuxer *_demux; NSData *_imageData; + CGFloat _scale; NSUInteger _loopCount; NSUInteger _frameCount; NSArray *_frames; @@ -205,7 +206,7 @@ dispatch_semaphore_signal(self->_lock); } #pragma mark - Progressive Decode -- (instancetype)initIncremental { +- (instancetype)initIncrementalWithOptions:(nullable SDWebImageCoderOptions *)options { self = [super init]; if (self) { // Progressive images need transparent, so always use premultiplied RGBA @@ -421,7 +422,9 @@ dispatch_semaphore_signal(self->_lock); compressionQuality = [[options valueForKey:SDWebImageCoderEncodeCompressionQuality] doubleValue]; } NSArray *frames = [SDWebImageCoderHelper framesFromAnimatedImage:image]; - if (frames.count == 0) { + + BOOL encodeFirstFrame = [options[SDWebImageCoderEncodeFirstFrameOnly] boolValue]; + if (encodeFirstFrame || frames.count == 0) { // for static single webp image data = [self sd_encodedWebpDataWithImage:image quality:compressionQuality]; } else { @@ -516,7 +519,7 @@ static void FreeImageData(void *info, const void *data, size_t size) { } #pragma mark - SDWebImageAnimatedCoder -- (instancetype)initWithAnimatedImageData:(NSData *)data { +- (instancetype)initWithAnimatedImageData:(NSData *)data options:(nullable SDWebImageCoderOptions *)options { if (!data) { return nil; } @@ -534,6 +537,14 @@ static void FreeImageData(void *info, const void *data, size_t size) { WebPDemuxDelete(demuxer); return nil; } + CGFloat scale = 1; + if ([options valueForKey:SDWebImageCoderDecodeScaleFactor]) { + scale = [[options valueForKey:SDWebImageCoderDecodeScaleFactor] doubleValue]; + if (scale < 1) { + scale = 1; + } + } + _scale = scale; _demux = demuxer; _imageData = data; _currentBlendIndex = NSNotFound; @@ -667,9 +678,9 @@ static void FreeImageData(void *info, const void *data, size_t size) { return nil; } #if SD_UIKIT || SD_WATCH - image = [[UIImage alloc] initWithCGImage:imageRef]; + image = [[UIImage alloc] initWithCGImage:imageRef scale:_scale orientation:UIImageOrientationUp]; #else - image = [[UIImage alloc] initWithCGImage:imageRef scale:1]; + image = [[UIImage alloc] initWithCGImage:imageRef scale:_scale]; #endif CGImageRelease(imageRef); } else { @@ -697,9 +708,9 @@ static void FreeImageData(void *info, const void *data, size_t size) { return nil; } #if SD_UIKIT || SD_WATCH - image = [[UIImage alloc] initWithCGImage:imageRef]; + image = [[UIImage alloc] initWithCGImage:imageRef scale:_scale orientation:UIImageOrientationUp]; #else - image = [[UIImage alloc] initWithCGImage:imageRef scale:1]; + image = [[UIImage alloc] initWithCGImage:imageRef scale:_scale]; #endif CGImageRelease(imageRef); } diff --git a/Tests/Tests/SDAnimatedImageTest.m b/Tests/Tests/SDAnimatedImageTest.m index b3127a24..add6c40a 100644 --- a/Tests/Tests/SDAnimatedImageTest.m +++ b/Tests/Tests/SDAnimatedImageTest.m @@ -17,6 +17,13 @@ static const NSUInteger kTestGIFFrameCount = 5; // local TestImage.gif loop count +// Internal header +@interface SDAnimatedImageView () + +@property (nonatomic, assign) BOOL isProgressive; + +@end + @interface SDAnimatedImageTest : SDTestCase @property (nonatomic, strong) UIWindow *window; @@ -55,7 +62,7 @@ static const NSUInteger kTestGIFFrameCount = 5; // local TestImage.gif loop coun - (void)test03AnimatedImageInitWithAnimatedCoder { NSData *validData = [self testGIFData]; - SDWebImageGIFCoder *coder = [[SDWebImageGIFCoder alloc] initWithAnimatedImageData:validData]; + SDWebImageGIFCoder *coder = [[SDWebImageGIFCoder alloc] initWithAnimatedImageData:validData options:nil]; SDAnimatedImage *image = [[SDAnimatedImage alloc] initWithAnimatedCoder:coder scale:1]; expect(image).notTo.beNil(); // enough, other can be test with InitWithData @@ -134,7 +141,7 @@ static const NSUInteger kTestGIFFrameCount = 5; // local TestImage.gif loop coun - (void)test09AnimatedImageViewSetProgressiveAnimatedImage { NSData *gifData = [self testGIFData]; - SDWebImageGIFCoder *progressiveCoder = [[SDWebImageGIFCoder alloc] initIncremental]; + SDWebImageGIFCoder *progressiveCoder = [[SDWebImageGIFCoder alloc] initIncrementalWithOptions:nil]; // simulate progressive decode, pass partial data NSData *partialData = [gifData subdataWithRange:NSMakeRange(0, gifData.length - 1)]; [progressiveCoder updateIncrementalData:partialData finished:NO]; @@ -145,7 +152,7 @@ static const NSUInteger kTestGIFFrameCount = 5; // local TestImage.gif loop coun SDAnimatedImageView *imageView = [[SDAnimatedImageView alloc] init]; imageView.image = partialImage; - BOOL isProgressive = [[imageView valueForKey:@"isProgressive"] boolValue]; + BOOL isProgressive = imageView.isProgressive; expect(isProgressive).equal(YES); // pass full data @@ -155,7 +162,7 @@ static const NSUInteger kTestGIFFrameCount = 5; // local TestImage.gif loop coun imageView.image = fullImage; - isProgressive = [[imageView valueForKey:@"isProgressive"] boolValue]; + isProgressive = imageView.isProgressive; expect(isProgressive).equal(NO); } @@ -182,7 +189,7 @@ static const NSUInteger kTestGIFFrameCount = 5; // local TestImage.gif loop coun // Progressive image may be nil when download data is not enough if (image) { expect(image.sd_isIncremental).beTruthy(); - BOOL isProgressive = [[imageView valueForKey:@"isProgressive"] boolValue]; + BOOL isProgressive = imageView.isProgressive; expect(isProgressive).equal(YES); } }); diff --git a/Tests/Tests/SDWebImageTestDecoder.m b/Tests/Tests/SDWebImageTestDecoder.m index 1ccd5043..feda04ae 100644 --- a/Tests/Tests/SDWebImageTestDecoder.m +++ b/Tests/Tests/SDWebImageTestDecoder.m @@ -25,7 +25,7 @@ return image; } -- (instancetype)initIncremental +- (instancetype)initIncrementalWithOptions:(nullable SDWebImageCoderOptions *)options { self = [super init]; if (self) { From 0705a973be4c8679a397cbf86096f06f7f3579c7 Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Mon, 9 Apr 2018 14:16:35 +0800 Subject: [PATCH 2/2] Update to import sd_isDecoded to avoid extra decode. And change exif orientation to CGImagePropertyOrientation define --- SDWebImage/SDWebImageCoderHelper.h | 6 ++-- SDWebImage/SDWebImageCoderHelper.m | 48 ++++++++++++++++------------- SDWebImage/SDWebImageImageIOCoder.m | 4 +-- SDWebImage/UIImage+ForceDecode.h | 5 +++ SDWebImage/UIImage+ForceDecode.m | 10 ++++++ SDWebImage/UIImage+WebCache.h | 2 +- 6 files changed, 47 insertions(+), 28 deletions(-) diff --git a/SDWebImage/SDWebImageCoderHelper.h b/SDWebImage/SDWebImageCoderHelper.h index 4a4cbd31..f582a400 100644 --- a/SDWebImage/SDWebImageCoderHelper.h +++ b/SDWebImage/SDWebImageCoderHelper.h @@ -6,7 +6,7 @@ * file that was distributed with this source code. */ -#import +#import #import "SDWebImageCompat.h" #import "SDWebImageFrame.h" @@ -90,7 +90,7 @@ @param exifOrientation EXIF orientation @return iOS orientation */ -+ (UIImageOrientation)imageOrientationFromEXIFOrientation:(NSInteger)exifOrientation NS_SWIFT_NAME(imageOrientation(from:)); ++ (UIImageOrientation)imageOrientationFromEXIFOrientation:(CGImagePropertyOrientation)exifOrientation NS_SWIFT_NAME(imageOrientation(from:)); /** Convert an iOS orientation to an EXIF image orientation. @@ -98,7 +98,7 @@ @param imageOrientation iOS orientation @return EXIF orientation */ -+ (NSInteger)exifOrientationFromImageOrientation:(UIImageOrientation)imageOrientation; ++ (CGImagePropertyOrientation)exifOrientationFromImageOrientation:(UIImageOrientation)imageOrientation; #endif @end diff --git a/SDWebImage/SDWebImageCoderHelper.m b/SDWebImage/SDWebImageCoderHelper.m index 472d1ee6..6d22c4dd 100644 --- a/SDWebImage/SDWebImageCoderHelper.m +++ b/SDWebImage/SDWebImageCoderHelper.m @@ -10,8 +10,8 @@ #import "SDWebImageFrame.h" #import "NSImage+Additions.h" #import "NSData+ImageContentType.h" -#import #import "SDAnimatedImageRep.h" +#import "UIImage+ForceDecode.h" #if SD_UIKIT || SD_WATCH static const size_t kBytesPerPixel = 4; @@ -286,6 +286,7 @@ static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to over } UIImage *decodedImage = [[UIImage alloc] initWithCGImage:imageRef scale:image.scale orientation:image.imageOrientation]; CGImageRelease(imageRef); + decodedImage.sd_isDecoded = YES; return decodedImage; #endif } @@ -414,6 +415,7 @@ static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to over if (destImage == nil) { return image; } + destImage.sd_isDecoded = YES; return destImage; } #endif @@ -421,32 +423,31 @@ static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to over #if SD_UIKIT || SD_WATCH // Convert an EXIF image orientation to an iOS one. -+ (UIImageOrientation)imageOrientationFromEXIFOrientation:(NSInteger)exifOrientation { - // CGImagePropertyOrientation is available on iOS 8 above. Currently kept for compatibility ++ (UIImageOrientation)imageOrientationFromEXIFOrientation:(CGImagePropertyOrientation)exifOrientation { UIImageOrientation imageOrientation = UIImageOrientationUp; switch (exifOrientation) { - case 1: + case kCGImagePropertyOrientationUp: imageOrientation = UIImageOrientationUp; break; - case 3: + case kCGImagePropertyOrientationDown: imageOrientation = UIImageOrientationDown; break; - case 8: + case kCGImagePropertyOrientationLeft: imageOrientation = UIImageOrientationLeft; break; - case 6: + case kCGImagePropertyOrientationRight: imageOrientation = UIImageOrientationRight; break; - case 2: + case kCGImagePropertyOrientationUpMirrored: imageOrientation = UIImageOrientationUpMirrored; break; - case 4: + case kCGImagePropertyOrientationDownMirrored: imageOrientation = UIImageOrientationDownMirrored; break; - case 5: + case kCGImagePropertyOrientationLeftMirrored: imageOrientation = UIImageOrientationLeftMirrored; break; - case 7: + case kCGImagePropertyOrientationRightMirrored: imageOrientation = UIImageOrientationRightMirrored; break; default: @@ -456,33 +457,32 @@ static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to over } // Convert an iOS orientation to an EXIF image orientation. -+ (NSInteger)exifOrientationFromImageOrientation:(UIImageOrientation)imageOrientation { - // CGImagePropertyOrientation is available on iOS 8 above. Currently kept for compatibility - NSInteger exifOrientation = 1; ++ (CGImagePropertyOrientation)exifOrientationFromImageOrientation:(UIImageOrientation)imageOrientation { + CGImagePropertyOrientation exifOrientation = kCGImagePropertyOrientationUp; switch (imageOrientation) { case UIImageOrientationUp: - exifOrientation = 1; + exifOrientation = kCGImagePropertyOrientationUp; break; case UIImageOrientationDown: - exifOrientation = 3; + exifOrientation = kCGImagePropertyOrientationDown; break; case UIImageOrientationLeft: - exifOrientation = 8; + exifOrientation = kCGImagePropertyOrientationLeft; break; case UIImageOrientationRight: - exifOrientation = 6; + exifOrientation = kCGImagePropertyOrientationRight; break; case UIImageOrientationUpMirrored: - exifOrientation = 2; + exifOrientation = kCGImagePropertyOrientationUpMirrored; break; case UIImageOrientationDownMirrored: - exifOrientation = 4; + exifOrientation = kCGImagePropertyOrientationDownMirrored; break; case UIImageOrientationLeftMirrored: - exifOrientation = 5; + exifOrientation = kCGImagePropertyOrientationLeftMirrored; break; case UIImageOrientationRightMirrored: - exifOrientation = 7; + exifOrientation = kCGImagePropertyOrientationRightMirrored; break; default: break; @@ -494,6 +494,10 @@ static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to over #pragma mark - Helper Fuction #if SD_UIKIT || SD_WATCH + (BOOL)shouldDecodeImage:(nullable UIImage *)image { + // Avoid extra decode + if (image.sd_isDecoded) { + return NO; + } // Prevent "CGBitmapContextCreateImage: invalid context 0x0" error if (image == nil) { return NO; diff --git a/SDWebImage/SDWebImageImageIOCoder.m b/SDWebImage/SDWebImageImageIOCoder.m index 92101d25..b068fa02 100644 --- a/SDWebImage/SDWebImageImageIOCoder.m +++ b/SDWebImage/SDWebImageImageIOCoder.m @@ -148,7 +148,7 @@ // 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 = [SDWebImageCoderHelper imageOrientationFromEXIFOrientation:orientationValue]; + _orientation = [SDWebImageCoderHelper imageOrientationFromEXIFOrientation:(CGImagePropertyOrientation)orientationValue]; #endif } } @@ -327,7 +327,7 @@ val = CFDictionaryGetValue(properties, kCGImagePropertyOrientation); if (val) { CFNumberGetValue(val, kCFNumberNSIntegerType, &exifOrientation); - result = [SDWebImageCoderHelper imageOrientationFromEXIFOrientation:exifOrientation]; + result = [SDWebImageCoderHelper imageOrientationFromEXIFOrientation:(CGImagePropertyOrientation)exifOrientation]; } // else - if it's not set it remains at up CFRelease((CFTypeRef) properties); } diff --git a/SDWebImage/UIImage+ForceDecode.h b/SDWebImage/UIImage+ForceDecode.h index ae85114b..0911a54c 100644 --- a/SDWebImage/UIImage+ForceDecode.h +++ b/SDWebImage/UIImage+ForceDecode.h @@ -10,6 +10,11 @@ @interface UIImage (ForceDecode) +/** + A bool value indicating whether the image has already been decoded. This can help to avoid extra force decode. + */ +@property (nonatomic, assign) BOOL sd_isDecoded; + /** Decompress (force decode before rendering) the provided image diff --git a/SDWebImage/UIImage+ForceDecode.m b/SDWebImage/UIImage+ForceDecode.m index 3edc796a..3ddd2078 100644 --- a/SDWebImage/UIImage+ForceDecode.m +++ b/SDWebImage/UIImage+ForceDecode.m @@ -8,9 +8,19 @@ #import "UIImage+ForceDecode.h" #import "SDWebImageCoderHelper.h" +#import "objc/runtime.h" @implementation UIImage (ForceDecode) +- (BOOL)sd_isDecoded { + NSNumber *value = objc_getAssociatedObject(self, @selector(sd_isDecoded)); + return value.boolValue; +} + +- (void)setSd_isDecoded:(BOOL)sd_isDecoded { + objc_setAssociatedObject(self, @selector(sd_isDecoded), @(sd_isDecoded), OBJC_ASSOCIATION_RETAIN_NONATOMIC); +} + + (UIImage *)sd_decodedImageWithImage:(UIImage *)image { if (!image) { return nil; diff --git a/SDWebImage/UIImage+WebCache.h b/SDWebImage/UIImage+WebCache.h index b7991976..b53b17bb 100644 --- a/SDWebImage/UIImage+WebCache.h +++ b/SDWebImage/UIImage+WebCache.h @@ -30,7 +30,7 @@ @property (nonatomic, assign, readonly) BOOL sd_isAnimated; /** - Indicating whether the image is during incremental decoding and may not contains full pixels. + A bool value indicating whether the image is during incremental decoding and may not contains full pixels. */ @property (nonatomic, assign) BOOL sd_isIncremental;