Add the thumbnail decoding API for protocols, update the code for ImageIO coders

This commit is contained in:
DreamPiggy 2020-01-05 20:20:50 +08:00
parent e7d8341e8b
commit a244962926
5 changed files with 109 additions and 13 deletions

View File

@ -27,6 +27,21 @@ FOUNDATION_EXPORT SDImageCoderOption _Nonnull const SDImageCoderDecodeFirstFrame
*/ */
FOUNDATION_EXPORT SDImageCoderOption _Nonnull const SDImageCoderDecodeScaleFactor; 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 // 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. 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.

View File

@ -10,6 +10,8 @@
SDImageCoderOption const SDImageCoderDecodeFirstFrameOnly = @"decodeFirstFrameOnly"; SDImageCoderOption const SDImageCoderDecodeFirstFrameOnly = @"decodeFirstFrameOnly";
SDImageCoderOption const SDImageCoderDecodeScaleFactor = @"decodeScaleFactor"; SDImageCoderOption const SDImageCoderDecodeScaleFactor = @"decodeScaleFactor";
SDImageCoderOption const SDImageCoderDecodePreserveAspectRatio = @"decodePreserveAspectRatio";
SDImageCoderOption const SDImageCoderDecodeThumbnailPixelSize = @"decodeThumbnailPixelSize";
SDImageCoderOption const SDImageCoderEncodeFirstFrameOnly = @"encodeFirstFrameOnly"; SDImageCoderOption const SDImageCoderEncodeFirstFrameOnly = @"encodeFirstFrameOnly";
SDImageCoderOption const SDImageCoderEncodeCompressionQuality = @"encodeCompressionQuality"; SDImageCoderOption const SDImageCoderEncodeCompressionQuality = @"encodeCompressionQuality";

View File

@ -145,6 +145,40 @@
return frameDuration; 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 #pragma mark - Decode
- (BOOL)canDecodeFromData:(nullable NSData *)data { - (BOOL)canDecodeFromData:(nullable NSData *)data {
return ([NSData sd_imageFormatForImageData:data] == self.class.imageFormat); return ([NSData sd_imageFormatForImageData:data] == self.class.imageFormat);
@ -160,14 +194,34 @@
scale = MAX([scaleFactor doubleValue], 1); scale = MAX([scaleFactor doubleValue], 1);
} }
CGSize thumbnailSize = CGSizeZero;
NSValue *thumbnailSizeValue = options[SDImageCoderDecodeThumbnailPixelSize];
if (thumbnailSizeValue != nil) {
#if SD_MAC #if SD_MAC
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]; SDAnimatedImageRep *imageRep = [[SDAnimatedImageRep alloc] initWithData:data];
NSSize size = NSMakeSize(imageRep.pixelsWide / scale, imageRep.pixelsHigh / scale); NSSize size = NSMakeSize(imageRep.pixelsWide / scale, imageRep.pixelsHigh / scale);
imageRep.size = size; imageRep.size = size;
NSImage *animatedImage = [[NSImage alloc] initWithSize:size]; NSImage *animatedImage = [[NSImage alloc] initWithSize:size];
[animatedImage addRepresentation:imageRep]; [animatedImage addRepresentation:imageRep];
return animatedImage; return animatedImage;
#else }
#endif
CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL); CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL);
if (!source) { if (!source) {
@ -178,19 +232,17 @@
BOOL decodeFirstFrame = [options[SDImageCoderDecodeFirstFrameOnly] boolValue]; BOOL decodeFirstFrame = [options[SDImageCoderDecodeFirstFrameOnly] boolValue];
if (decodeFirstFrame || count <= 1) { 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 { } else {
NSMutableArray<SDImageFrame *> *frames = [NSMutableArray array]; NSMutableArray<SDImageFrame *> *frames = [NSMutableArray array];
for (size_t i = 0; i < count; i++) { for (size_t i = 0; i < count; i++) {
CGImageRef imageRef = CGImageSourceCreateImageAtIndex(source, i, NULL); UIImage *image = [self.class createFrameAtIndex:i source:source scale:scale preserveAspectRatio:preserveAspectRatio thumbnailSize:thumbnailSize];
if (!imageRef) { if (!image) {
continue; continue;
} }
NSTimeInterval duration = [self.class frameDurationAtIndex:i source:source]; 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]; SDImageFrame *frame = [SDImageFrame frameWithImage:image duration:duration];
[frames addObject:frame]; [frames addObject:frame];
@ -205,7 +257,6 @@
CFRelease(source); CFRelease(source);
return animatedImage; return animatedImage;
#endif
} }
#pragma mark - Progressive Decode #pragma mark - Progressive Decode

View File

@ -12,6 +12,7 @@
#import <ImageIO/ImageIO.h> #import <ImageIO/ImageIO.h>
#import "UIImage+Metadata.h" #import "UIImage+Metadata.h"
#import "SDImageHEICCoderInternal.h" #import "SDImageHEICCoderInternal.h"
#import "SDImageIOAnimatedCoderInternal.h"
@implementation SDImageIOCoder { @implementation SDImageIOCoder {
size_t _width, _height; size_t _width, _height;
@ -74,7 +75,33 @@
scale = MAX([scaleFactor doubleValue], 1) ; 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]; image.sd_imageFormat = [NSData sd_imageFormatForImageData:data];
return image; return image;
} }

View File

@ -13,5 +13,6 @@
+ (NSTimeInterval)frameDurationAtIndex:(NSUInteger)index source:(nonnull CGImageSourceRef)source; + (NSTimeInterval)frameDurationAtIndex:(NSUInteger)index source:(nonnull CGImageSourceRef)source;
+ (NSUInteger)imageLoopCountWithSource:(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 @end