Update the progressive coder/ animated coder init API, which pass the options to support extensibility

This commit is contained in:
DreamPiggy 2018-04-09 13:42:52 +08:00
parent 02dfed984e
commit 22c293738a
10 changed files with 106 additions and 36 deletions

View File

@ -287,7 +287,7 @@ static NSArray *SDBundlePreferredScales() {
for (id<SDWebImageCoder>coder 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;
}
}

View File

@ -38,6 +38,7 @@ const CFStringRef kCGImagePropertyAPNGUnclampedDelayTime = (__bridge CFStringRef
#endif
CGImageSourceRef _imageSource;
NSData *_imageData;
CGFloat _scale;
NSUInteger _loopCount;
NSUInteger _frameCount;
NSArray<SDAPNGCoderFrame *> *_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<SDWebImageFrame *> *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;

View File

@ -13,19 +13,32 @@
typedef NSString * SDWebImageCoderOption NS_STRING_ENUM;
typedef NSDictionary<SDWebImageCoderOption, id> 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

View File

@ -10,4 +10,6 @@
SDWebImageCoderOption const SDWebImageCoderDecodeFirstFrameOnly = @"decodeFirstFrameOnly";
SDWebImageCoderOption const SDWebImageCoderDecodeScaleFactor = @"decodeScaleFactor";
SDWebImageCoderOption const SDWebImageCoderEncodeFirstFrameOnly = @"encodeFirstFrameOnly";
SDWebImageCoderOption const SDWebImageCoderEncodeCompressionQuality = @"encodeCompressionQuality";

View File

@ -352,7 +352,7 @@ didReceiveResponse:(NSURLResponse *)response
for (id<SDWebImageCoder>coder in [SDWebImageCodersManager sharedManager].coders) {
if ([coder conformsToProtocol:@protocol(SDWebImageProgressiveCoder)] &&
[((id<SDWebImageProgressiveCoder>)coder) canIncrementalDecodeFromData:imageData]) {
self.progressiveCoder = [[[coder class] alloc] initIncremental];
self.progressiveCoder = [[[coder class] alloc] initIncrementalWithOptions:nil];
break;
}
}

View File

@ -28,6 +28,7 @@
size_t _width, _height;
CGImageSourceRef _imageSource;
NSData *_imageData;
CGFloat _scale;
NSUInteger _loopCount;
NSUInteger _frameCount;
NSArray<SDGIFCoderFrame *> *_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;

View File

@ -107,7 +107,7 @@
}
#pragma mark - Progressive Decode
- (instancetype)initIncremental {
- (instancetype)initIncrementalWithOptions:(nullable SDWebImageCoderOptions *)options {
self = [super init];
if (self) {
_imageSource = CGImageSourceCreateIncremental(NULL);

View File

@ -51,6 +51,7 @@ dispatch_semaphore_signal(self->_lock);
WebPIDecoder *_idec;
WebPDemuxer *_demux;
NSData *_imageData;
CGFloat _scale;
NSUInteger _loopCount;
NSUInteger _frameCount;
NSArray<SDWebPCoderFrame *> *_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<SDWebImageFrame *> *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);
}

View File

@ -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);
}
});

View File

@ -25,7 +25,7 @@
return image;
}
- (instancetype)initIncremental
- (instancetype)initIncrementalWithOptions:(nullable SDWebImageCoderOptions *)options
{
self = [super init];
if (self) {