From a244962926f9e6fea3bcf2f77519b0252d88ea82 Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Sun, 5 Jan 2020 20:20:50 +0800 Subject: [PATCH] Add the thumbnail decoding API for protocols, update the code for ImageIO coders --- SDWebImage/Core/SDImageCoder.h | 15 ++++ SDWebImage/Core/SDImageCoder.m | 2 + SDWebImage/Core/SDImageIOAnimatedCoder.m | 75 ++++++++++++++++--- SDWebImage/Core/SDImageIOCoder.m | 29 ++++++- .../Private/SDImageIOAnimatedCoderInternal.h | 1 + 5 files changed, 109 insertions(+), 13 deletions(-) diff --git a/SDWebImage/Core/SDImageCoder.h b/SDWebImage/Core/SDImageCoder.h index 3b2049e5..817ce825 100644 --- a/SDWebImage/Core/SDImageCoder.h +++ b/SDWebImage/Core/SDImageCoder.h @@ -27,6 +27,21 @@ FOUNDATION_EXPORT SDImageCoderOption _Nonnull const SDImageCoderDecodeFirstFrame */ FOUNDATION_EXPORT SDImageCoderOption _Nonnull const SDImageCoderDecodeScaleFactor; +/** + A Boolean value indicating whether to keep the original aspect ratio when generating thumbnail images (or bitmap images from vector format). + Defaults to YES. + @note works for `SDImageCoder`, `SDProgressiveImageCoder`, `SDAnimatedImageCoder`. + */ +FOUNDATION_EXPORT SDImageCoderOption _Nonnull const SDImageCoderDecodePreserveAspectRatio; + +/** + A CGSize value indicating whether or not to generate the thumbnail images (or bitmap images from vector format). When this value is provided, the decoder will generate a thumbnail image which pixel size is smaller than or equal to (depends the `.generationRule`) the value size. + Defaults to CGSizeZero, which means no thumbnail generation at all. + @note works for `SDImageCoder`, `SDProgressiveImageCoder`, `SDAnimatedImageCoder`. + */ +FOUNDATION_EXPORT SDImageCoderOption _Nonnull const SDImageCoderDecodeThumbnailPixelSize; + + // 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. diff --git a/SDWebImage/Core/SDImageCoder.m b/SDWebImage/Core/SDImageCoder.m index c963376b..df5224ae 100644 --- a/SDWebImage/Core/SDImageCoder.m +++ b/SDWebImage/Core/SDImageCoder.m @@ -10,6 +10,8 @@ SDImageCoderOption const SDImageCoderDecodeFirstFrameOnly = @"decodeFirstFrameOnly"; SDImageCoderOption const SDImageCoderDecodeScaleFactor = @"decodeScaleFactor"; +SDImageCoderOption const SDImageCoderDecodePreserveAspectRatio = @"decodePreserveAspectRatio"; +SDImageCoderOption const SDImageCoderDecodeThumbnailPixelSize = @"decodeThumbnailPixelSize"; SDImageCoderOption const SDImageCoderEncodeFirstFrameOnly = @"encodeFirstFrameOnly"; SDImageCoderOption const SDImageCoderEncodeCompressionQuality = @"encodeCompressionQuality"; diff --git a/SDWebImage/Core/SDImageIOAnimatedCoder.m b/SDWebImage/Core/SDImageIOAnimatedCoder.m index b554fceb..1b71b54e 100644 --- a/SDWebImage/Core/SDImageIOAnimatedCoder.m +++ b/SDWebImage/Core/SDImageIOAnimatedCoder.m @@ -145,6 +145,40 @@ return frameDuration; } ++ (UIImage *)createFrameAtIndex:(NSUInteger)index source:(CGImageSourceRef)source scale:(CGFloat)scale preserveAspectRatio:(BOOL)preserveAspectRatio thumbnailSize:(CGSize)thumbnailSize { + // Parse the image properties + NSDictionary *properties = (__bridge_transfer NSDictionary *)CGImageSourceCopyPropertiesAtIndex(source, index, NULL); + NSUInteger pixelWidth = [properties[(__bridge NSString *)kCGImagePropertyPixelWidth] unsignedIntegerValue]; + NSUInteger pixelHeight = [properties[(__bridge NSString *)kCGImagePropertyPixelHeight] unsignedIntegerValue]; + CGImagePropertyOrientation exifOrientation = (CGImagePropertyOrientation)[properties[(__bridge NSString *)kCGImagePropertyOrientation] unsignedIntegerValue]; + if (!exifOrientation) { + exifOrientation = kCGImagePropertyOrientationUp; + } + + CGImageRef imageRef; + if (CGSizeEqualToSize(thumbnailSize, CGSizeZero) || (pixelWidth <= thumbnailSize.width && pixelHeight <= thumbnailSize.height)) { + imageRef = CGImageSourceCreateImageAtIndex(source, index, NULL); + } else { + NSMutableDictionary *thumbnailOptions = [NSMutableDictionary dictionary]; + thumbnailOptions[(__bridge NSString *)kCGImageSourceCreateThumbnailWithTransform] = @(preserveAspectRatio); + thumbnailOptions[(__bridge NSString *)kCGImageSourceThumbnailMaxPixelSize] = preserveAspectRatio ? @(MAX(thumbnailSize.width, thumbnailSize.height)) : @(MIN(thumbnailSize.width, thumbnailSize.height)); + thumbnailOptions[(__bridge NSString *)kCGImageSourceCreateThumbnailFromImageIfAbsent] = @(YES); + imageRef = CGImageSourceCreateThumbnailAtIndex(source, index, (__bridge CFDictionaryRef)thumbnailOptions); + } + if (!imageRef) { + return nil; + } + +#if SD_UIKIT || SD_WATCH + UIImageOrientation imageOrientation = [SDImageCoderHelper imageOrientationFromEXIFOrientation:exifOrientation]; + UIImage *image = [[UIImage alloc] initWithCGImage:imageRef scale:scale orientation:imageOrientation]; +#else + UIImage *image = [[UIImage alloc] initWithCGImage:imageRef scale:scale orientation:exifOrientation]; +#endif + CGImageRelease(imageRef); + return image; +} + #pragma mark - Decode - (BOOL)canDecodeFromData:(nullable NSData *)data { return ([NSData sd_imageFormatForImageData:data] == self.class.imageFormat); @@ -160,14 +194,34 @@ scale = MAX([scaleFactor doubleValue], 1); } + CGSize thumbnailSize = CGSizeZero; + NSValue *thumbnailSizeValue = options[SDImageCoderDecodeThumbnailPixelSize]; + if (thumbnailSizeValue != nil) { #if SD_MAC - SDAnimatedImageRep *imageRep = [[SDAnimatedImageRep alloc] initWithData:data]; - NSSize size = NSMakeSize(imageRep.pixelsWide / scale, imageRep.pixelsHigh / scale); - imageRep.size = size; - NSImage *animatedImage = [[NSImage alloc] initWithSize:size]; - [animatedImage addRepresentation:imageRep]; - return animatedImage; + thumbnailSize = thumbnailSizeValue.sizeValue; #else + thumbnailSize = thumbnailSizeValue.CGSizeValue; +#endif + } + + BOOL preserveAspectRatio = NO; + NSNumber *preserveAspectRatioValue = options[SDImageCoderDecodePreserveAspectRatio]; + if (preserveAspectRatioValue != nil) { + preserveAspectRatio = preserveAspectRatioValue.boolValue; + } + +#if SD_MAC + // If don't use thumbnail, prefers the built-in generation of frames (GIF/APNG) + // Which decode frames in time and reduce memory usage + if (CGSizeEqualToSize(thumbnailSize, CGSizeZero)) { + SDAnimatedImageRep *imageRep = [[SDAnimatedImageRep alloc] initWithData:data]; + NSSize size = NSMakeSize(imageRep.pixelsWide / scale, imageRep.pixelsHigh / scale); + imageRep.size = size; + NSImage *animatedImage = [[NSImage alloc] initWithSize:size]; + [animatedImage addRepresentation:imageRep]; + return animatedImage; + } +#endif CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL); if (!source) { @@ -178,19 +232,17 @@ BOOL decodeFirstFrame = [options[SDImageCoderDecodeFirstFrameOnly] boolValue]; if (decodeFirstFrame || count <= 1) { - animatedImage = [[UIImage alloc] initWithData:data scale:scale]; + animatedImage = [self.class createFrameAtIndex:0 source:source scale:scale preserveAspectRatio:preserveAspectRatio thumbnailSize:thumbnailSize]; } else { NSMutableArray *frames = [NSMutableArray array]; for (size_t i = 0; i < count; i++) { - CGImageRef imageRef = CGImageSourceCreateImageAtIndex(source, i, NULL); - if (!imageRef) { + UIImage *image = [self.class createFrameAtIndex:i source:source scale:scale preserveAspectRatio:preserveAspectRatio thumbnailSize:thumbnailSize]; + if (!image) { continue; } NSTimeInterval duration = [self.class frameDurationAtIndex:i source:source]; - UIImage *image = [[UIImage alloc] initWithCGImage:imageRef scale:scale orientation:UIImageOrientationUp]; - CGImageRelease(imageRef); SDImageFrame *frame = [SDImageFrame frameWithImage:image duration:duration]; [frames addObject:frame]; @@ -205,7 +257,6 @@ CFRelease(source); return animatedImage; -#endif } #pragma mark - Progressive Decode diff --git a/SDWebImage/Core/SDImageIOCoder.m b/SDWebImage/Core/SDImageIOCoder.m index a6fa10af..c17c9f16 100644 --- a/SDWebImage/Core/SDImageIOCoder.m +++ b/SDWebImage/Core/SDImageIOCoder.m @@ -12,6 +12,7 @@ #import #import "UIImage+Metadata.h" #import "SDImageHEICCoderInternal.h" +#import "SDImageIOAnimatedCoderInternal.h" @implementation SDImageIOCoder { size_t _width, _height; @@ -74,7 +75,33 @@ scale = MAX([scaleFactor doubleValue], 1) ; } - UIImage *image = [[UIImage alloc] initWithData:data scale:scale]; + CGSize thumbnailSize = CGSizeZero; + NSValue *thumbnailSizeValue = options[SDImageCoderDecodeThumbnailPixelSize]; + if (thumbnailSizeValue != nil) { +#if SD_MAC + thumbnailSize = thumbnailSizeValue.sizeValue; +#else + thumbnailSize = thumbnailSizeValue.CGSizeValue; +#endif + } + + BOOL preserveAspectRatio = NO; + NSNumber *preserveAspectRatioValue = options[SDImageCoderDecodePreserveAspectRatio]; + if (preserveAspectRatioValue != nil) { + preserveAspectRatio = preserveAspectRatioValue.boolValue; + } + + CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL); + if (!source) { + return nil; + } + + UIImage *image = [SDImageIOAnimatedCoder createFrameAtIndex:0 source:source scale:scale preserveAspectRatio:preserveAspectRatio thumbnailSize:thumbnailSize]; + CFRelease(source); + if (!image) { + return nil; + } + image.sd_imageFormat = [NSData sd_imageFormatForImageData:data]; return image; } diff --git a/SDWebImage/Private/SDImageIOAnimatedCoderInternal.h b/SDWebImage/Private/SDImageIOAnimatedCoderInternal.h index 4b500131..f2976ea8 100644 --- a/SDWebImage/Private/SDImageIOAnimatedCoderInternal.h +++ b/SDWebImage/Private/SDImageIOAnimatedCoderInternal.h @@ -13,5 +13,6 @@ + (NSTimeInterval)frameDurationAtIndex:(NSUInteger)index source:(nonnull CGImageSourceRef)source; + (NSUInteger)imageLoopCountWithSource:(nonnull CGImageSourceRef)source; ++ (nullable UIImage *)createFrameAtIndex:(NSUInteger)index source:(nonnull CGImageSourceRef)source scale:(CGFloat)scale preserveAspectRatio:(BOOL)preserveAspectRatio thumbnailSize:(CGSize)thumbnailSize; @end