Add APNG coder to support static PNG & APNG rendering. Also update macOS's SDAnimatedImageRep

This commit is contained in:
DreamPiggy 2018-03-29 17:45:56 +08:00
parent beb958bd08
commit 082c5af565
7 changed files with 478 additions and 1 deletions

View File

@ -63,6 +63,7 @@
@"http://www.httpwatch.com/httpgallery/authentication/authenticatedimage/default.aspx?0.35786508303135633", // requires HTTP auth, used to demo the NTLM auth
@"http://assets.sbnation.com/assets/2512203/dogflops.gif",
@"https://raw.githubusercontent.com/liyong03/YLGIFImage/master/YLGIFImageDemo/YLGIFImageDemo/joy.gif",
@"https://raw.githubusercontent.com/onevcat/APNGKit/master/TestImages/APNG-cube.apng",
@"http://www.ioncannon.net/wp-content/uploads/2011/06/test2.webp",
@"http://www.ioncannon.net/wp-content/uploads/2011/06/test9.webp",
@"http://littlesvr.ca/apng/images/SteamEngine.webp",

View File

@ -376,6 +376,18 @@
325312D1200F09910046BF1E /* SDWebImageTransition.m in Sources */ = {isa = PBXBuildFile; fileRef = 325312C7200F09910046BF1E /* SDWebImageTransition.m */; };
325312D2200F09910046BF1E /* SDWebImageTransition.m in Sources */ = {isa = PBXBuildFile; fileRef = 325312C7200F09910046BF1E /* SDWebImageTransition.m */; };
325312D3200F09910046BF1E /* SDWebImageTransition.m in Sources */ = {isa = PBXBuildFile; fileRef = 325312C7200F09910046BF1E /* SDWebImageTransition.m */; };
327054D4206CD8B3006EA328 /* SDWebImageAPNGCoder.h in Headers */ = {isa = PBXBuildFile; fileRef = 327054D2206CD8B3006EA328 /* SDWebImageAPNGCoder.h */; settings = {ATTRIBUTES = (Public, ); }; };
327054D5206CD8B3006EA328 /* SDWebImageAPNGCoder.h in Headers */ = {isa = PBXBuildFile; fileRef = 327054D2206CD8B3006EA328 /* SDWebImageAPNGCoder.h */; settings = {ATTRIBUTES = (Public, ); }; };
327054D6206CD8B3006EA328 /* SDWebImageAPNGCoder.h in Headers */ = {isa = PBXBuildFile; fileRef = 327054D2206CD8B3006EA328 /* SDWebImageAPNGCoder.h */; settings = {ATTRIBUTES = (Public, ); }; };
327054D7206CD8B3006EA328 /* SDWebImageAPNGCoder.h in Headers */ = {isa = PBXBuildFile; fileRef = 327054D2206CD8B3006EA328 /* SDWebImageAPNGCoder.h */; settings = {ATTRIBUTES = (Public, ); }; };
327054D8206CD8B3006EA328 /* SDWebImageAPNGCoder.h in Headers */ = {isa = PBXBuildFile; fileRef = 327054D2206CD8B3006EA328 /* SDWebImageAPNGCoder.h */; settings = {ATTRIBUTES = (Public, ); }; };
327054D9206CD8B3006EA328 /* SDWebImageAPNGCoder.h in Headers */ = {isa = PBXBuildFile; fileRef = 327054D2206CD8B3006EA328 /* SDWebImageAPNGCoder.h */; settings = {ATTRIBUTES = (Public, ); }; };
327054DA206CD8B3006EA328 /* SDWebImageAPNGCoder.m in Sources */ = {isa = PBXBuildFile; fileRef = 327054D3206CD8B3006EA328 /* SDWebImageAPNGCoder.m */; };
327054DB206CD8B3006EA328 /* SDWebImageAPNGCoder.m in Sources */ = {isa = PBXBuildFile; fileRef = 327054D3206CD8B3006EA328 /* SDWebImageAPNGCoder.m */; };
327054DC206CD8B3006EA328 /* SDWebImageAPNGCoder.m in Sources */ = {isa = PBXBuildFile; fileRef = 327054D3206CD8B3006EA328 /* SDWebImageAPNGCoder.m */; };
327054DD206CD8B3006EA328 /* SDWebImageAPNGCoder.m in Sources */ = {isa = PBXBuildFile; fileRef = 327054D3206CD8B3006EA328 /* SDWebImageAPNGCoder.m */; };
327054DE206CD8B3006EA328 /* SDWebImageAPNGCoder.m in Sources */ = {isa = PBXBuildFile; fileRef = 327054D3206CD8B3006EA328 /* SDWebImageAPNGCoder.m */; };
327054DF206CD8B3006EA328 /* SDWebImageAPNGCoder.m in Sources */ = {isa = PBXBuildFile; fileRef = 327054D3206CD8B3006EA328 /* SDWebImageAPNGCoder.m */; };
3290FA041FA478AF0047D20C /* SDWebImageFrame.h in Headers */ = {isa = PBXBuildFile; fileRef = 3290FA021FA478AF0047D20C /* SDWebImageFrame.h */; settings = {ATTRIBUTES = (Public, ); }; };
3290FA051FA478AF0047D20C /* SDWebImageFrame.h in Headers */ = {isa = PBXBuildFile; fileRef = 3290FA021FA478AF0047D20C /* SDWebImageFrame.h */; settings = {ATTRIBUTES = (Public, ); }; };
3290FA061FA478AF0047D20C /* SDWebImageFrame.h in Headers */ = {isa = PBXBuildFile; fileRef = 3290FA021FA478AF0047D20C /* SDWebImageFrame.h */; settings = {ATTRIBUTES = (Public, ); }; };
@ -1449,6 +1461,8 @@
324DF4B3200A14DC008A84CC /* SDWebImageDefine.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDWebImageDefine.m; sourceTree = "<group>"; };
325312C6200F09910046BF1E /* SDWebImageTransition.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDWebImageTransition.h; sourceTree = "<group>"; };
325312C7200F09910046BF1E /* SDWebImageTransition.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDWebImageTransition.m; sourceTree = "<group>"; };
327054D2206CD8B3006EA328 /* SDWebImageAPNGCoder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDWebImageAPNGCoder.h; sourceTree = "<group>"; };
327054D3206CD8B3006EA328 /* SDWebImageAPNGCoder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDWebImageAPNGCoder.m; sourceTree = "<group>"; };
3290FA021FA478AF0047D20C /* SDWebImageFrame.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDWebImageFrame.h; sourceTree = "<group>"; };
3290FA031FA478AF0047D20C /* SDWebImageFrame.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDWebImageFrame.m; sourceTree = "<group>"; };
329A18571FFF5DFD008C9A2F /* UIImage+WebCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "UIImage+WebCache.h"; path = "SDWebImage/UIImage+WebCache.h"; sourceTree = "<group>"; };
@ -1703,6 +1717,8 @@
321E60A11F38E8F600405457 /* SDWebImageGIFCoder.m */,
321E60AE1F38E90100405457 /* SDWebImageWebPCoder.h */,
321E60AF1F38E90100405457 /* SDWebImageWebPCoder.m */,
327054D2206CD8B3006EA328 /* SDWebImageAPNGCoder.h */,
327054D3206CD8B3006EA328 /* SDWebImageAPNGCoder.m */,
3290FA021FA478AF0047D20C /* SDWebImageFrame.h */,
3290FA031FA478AF0047D20C /* SDWebImageFrame.m */,
32CF1C051FA496B000004BD1 /* SDWebImageCoderHelper.h */,
@ -2161,6 +2177,7 @@
80377DCC1F2F66A700F89830 /* lossless_common.h in Headers */,
321E60971F38E8ED00405457 /* SDWebImageImageIOCoder.h in Headers */,
43A918671D8308FE00B3925F /* SDImageCacheConfig.h in Headers */,
327054D7206CD8B3006EA328 /* SDWebImageAPNGCoder.h in Headers */,
431739571CDFC8B70008FEB9 /* encode.h in Headers */,
00733A6F1BC4880E00A5A117 /* UIImage+WebP.h in Headers */,
323F8B711F38EF770092B609 /* delta_palettization_enc.h in Headers */,
@ -2241,6 +2258,7 @@
80377C1D1F2F666300F89830 /* huffman_encode_utils.h in Headers */,
321E60B11F38E90100405457 /* SDWebImageWebPCoder.h in Headers */,
80377E9A1F2F66D400F89830 /* common_dec.h in Headers */,
327054D5206CD8B3006EA328 /* SDWebImageAPNGCoder.h in Headers */,
80377C231F2F666300F89830 /* quant_levels_utils.h in Headers */,
321E60BF1F38E91700405457 /* UIImage+ForceDecode.h in Headers */,
80377EA61F2F66D400F89830 /* webpi_dec.h in Headers */,
@ -2396,6 +2414,7 @@
323F8BDC1F38EF770092B609 /* vp8i_enc.h in Headers */,
80377ED21F2F66D500F89830 /* vp8i_dec.h in Headers */,
32484779201775F600AF9E5A /* SDAnimatedImage.h in Headers */,
327054D8206CD8B3006EA328 /* SDWebImageAPNGCoder.h in Headers */,
43A918681D8308FE00B3925F /* SDImageCacheConfig.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
@ -2419,6 +2438,7 @@
80377C8D1F2F666400F89830 /* random_utils.h in Headers */,
4397D2C31D0DDD8C00BB2784 /* SDWebImageManager.h in Headers */,
323F8B551F38EF770092B609 /* backward_references_enc.h in Headers */,
327054D9206CD8B3006EA328 /* SDWebImageAPNGCoder.h in Headers */,
80377C811F2F666400F89830 /* endian_inl_utils.h in Headers */,
321E60991F38E8ED00405457 /* SDWebImageImageIOCoder.h in Headers */,
323F8B8B1F38EF770092B609 /* histogram_enc.h in Headers */,
@ -2508,6 +2528,7 @@
80377D871F2F66A700F89830 /* lossless_common.h in Headers */,
321E60961F38E8ED00405457 /* SDWebImageImageIOCoder.h in Headers */,
4A2CAE041AB4BB5400B6BC39 /* SDWebImage.h in Headers */,
327054D6206CD8B3006EA328 /* SDWebImageAPNGCoder.h in Headers */,
431739511CDFC8B70008FEB9 /* format_constants.h in Headers */,
43A918661D8308FE00B3925F /* SDImageCacheConfig.h in Headers */,
323F8B701F38EF770092B609 /* delta_palettization_enc.h in Headers */,
@ -2628,6 +2649,7 @@
80377C0B1F2F665300F89830 /* random_utils.h in Headers */,
80377E921F2F66D000F89830 /* vp8i_dec.h in Headers */,
5376131F155AD0D5005750A4 /* UIButton+WebCache.h in Headers */,
327054D4206CD8B3006EA328 /* SDWebImageAPNGCoder.h in Headers */,
53761320155AD0D5005750A4 /* UIImageView+WebCache.h in Headers */,
530E49E816464C25002868E7 /* SDWebImageOperation.h in Headers */,
32484769201775F600AF9E5A /* SDAnimatedImageView.h in Headers */,
@ -2951,6 +2973,7 @@
323F8B991F38EF770092B609 /* near_lossless_enc.c in Sources */,
80377DE81F2F66A700F89830 /* yuv_mips_dsp_r2.c in Sources */,
80377EC31F2F66D500F89830 /* vp8l_dec.c in Sources */,
327054DD206CD8B3006EA328 /* SDWebImageAPNGCoder.m in Sources */,
325312D1200F09910046BF1E /* SDWebImageTransition.m in Sources */,
321E609D1F38E8ED00405457 /* SDWebImageImageIOCoder.m in Sources */,
323F8B9F1F38EF770092B609 /* picture_csp_enc.c in Sources */,
@ -3151,6 +3174,7 @@
80377C221F2F666300F89830 /* quant_levels_utils.c in Sources */,
80377D2E1F2F66A700F89830 /* dec_mips32.c in Sources */,
323F8BD31F38EF770092B609 /* tree_enc.c in Sources */,
327054DB206CD8B3006EA328 /* SDWebImageAPNGCoder.m in Sources */,
80377D5C1F2F66A700F89830 /* upsampling_sse2.c in Sources */,
323F8BC71F38EF770092B609 /* syntax_enc.c in Sources */,
80377D321F2F66A700F89830 /* dec_sse41.c in Sources */,
@ -3304,6 +3328,7 @@
80377E301F2F66A800F89830 /* yuv.c in Sources */,
43A9186F1D8308FE00B3925F /* SDImageCacheConfig.m in Sources */,
323F8BD61F38EF770092B609 /* tree_enc.c in Sources */,
327054DE206CD8B3006EA328 /* SDWebImageAPNGCoder.m in Sources */,
80377DFD1F2F66A800F89830 /* dec_mips32.c in Sources */,
323F8BCA1F38EF770092B609 /* syntax_enc.c in Sources */,
80377E2B1F2F66A800F89830 /* upsampling_sse2.c in Sources */,
@ -3451,6 +3476,7 @@
4397D2A11D0DDD8C00BB2784 /* SDWebImageManager.m in Sources */,
323F8BCB1F38EF770092B609 /* syntax_enc.c in Sources */,
321E60AD1F38E8F600405457 /* SDWebImageGIFCoder.m in Sources */,
327054DF206CD8B3006EA328 /* SDWebImageAPNGCoder.m in Sources */,
80377E341F2F66A800F89830 /* alpha_processing_sse2.c in Sources */,
4397D2A61D0DDD8C00BB2784 /* SDWebImageCompat.m in Sources */,
80377E6F1F2F66A800F89830 /* upsampling_neon.c in Sources */,
@ -3570,6 +3596,7 @@
323F8B981F38EF770092B609 /* near_lossless_enc.c in Sources */,
80377D6F1F2F66A700F89830 /* cost.c in Sources */,
80377EB31F2F66D400F89830 /* vp8l_dec.c in Sources */,
327054DC206CD8B3006EA328 /* SDWebImageAPNGCoder.m in Sources */,
325312D0200F09910046BF1E /* SDWebImageTransition.m in Sources */,
321E609C1F38E8ED00405457 /* SDWebImageImageIOCoder.m in Sources */,
323F8B9E1F38EF770092B609 /* picture_csp_enc.c in Sources */,
@ -3727,6 +3754,7 @@
80377CE51F2F66A100F89830 /* cost.c in Sources */,
80377E931F2F66D000F89830 /* vp8l_dec.c in Sources */,
321E609A1F38E8ED00405457 /* SDWebImageImageIOCoder.m in Sources */,
327054DA206CD8B3006EA328 /* SDWebImageAPNGCoder.m in Sources */,
325312CE200F09910046BF1E /* SDWebImageTransition.m in Sources */,
323F8B9C1F38EF770092B609 /* picture_csp_enc.c in Sources */,
80377D141F2F66A100F89830 /* upsampling_mips_dsp_r2.c in Sources */,

View File

@ -11,6 +11,7 @@
#if SD_MAC
#import "SDWebImageGIFCoder.h"
#import "SDWebImageAPNGCoder.h"
@interface SDWebImageGIFCoder ()
@ -18,6 +19,13 @@
@end
@interface SDWebImageAPNGCoder ()
- (float)sd_frameDurationAtIndex:(NSUInteger)index source:(CGImageSourceRef)source;
- (NSUInteger)sd_imageLoopCountWithSource:(CGImageSourceRef)source;
@end
@interface SDAnimatedImageRep ()
@property (nonatomic, assign, readonly, nullable) CGImageSourceRef imageSource;
@ -26,6 +34,43 @@
@implementation SDAnimatedImageRep
// `NSBitmapImageRep`'s `imageRepWithData:` is not designed initlizer
+ (instancetype)imageRepWithData:(NSData *)data {
SDAnimatedImageRep *imageRep = [[SDAnimatedImageRep alloc] initWithData:data];
return imageRep;
}
// We should override init method for `NSBitmapImageRep` to do initlize about animated image format
- (instancetype)initWithData:(NSData *)data {
self = [super initWithData:data];
if (self) {
CGImageSourceRef imageSource = self.imageSource;
if (!imageSource) {
return self;
}
NSUInteger frameCount = CGImageSourceGetCount(imageSource);
if (frameCount <= 1) {
return self;
}
CFStringRef type = CGImageSourceGetType(imageSource);
if (!type) {
return self;
}
if (CFStringCompare(type, kUTTypeGIF, 0) == kCFCompareEqualTo) {
// GIF
// Do nothing because NSBitmapImageRep support it
} else if (CFStringCompare(type, kUTTypePNG, 0) == kCFCompareEqualTo) {
// APNG
// Do initilize about frame count, current frame/duration and loop count
[self setProperty:NSImageFrameCount withValue:@(frameCount)];
[self setProperty:NSImageCurrentFrame withValue:@(0)];
NSUInteger loopCount = [[SDWebImageAPNGCoder sharedCoder] sd_imageLoopCountWithSource:imageSource];
[self setProperty:NSImageLoopCount withValue:@(loopCount)];
}
}
return self;
}
// `NSBitmapImageRep` will use `kCGImagePropertyGIFDelayTime` whenever you call `setProperty:withValue:` with `NSImageCurrentFrame` to change the current frame. We override it and use the actual `kCGImagePropertyGIFUnclampedDelayTime` if need.
- (void)setProperty:(NSBitmapImageRepPropertyKey)property withValue:(id)value {
[super setProperty:property withValue:value];
@ -45,6 +90,8 @@
// Through we currently process GIF only, in the 5.x we support APNG so we keep the extensibility
if (CFStringCompare(type, kUTTypeGIF, 0) == kCFCompareEqualTo) {
frameDuration = [[SDWebImageGIFCoder sharedCoder] sd_frameDurationAtIndex:index source:imageSource];
} else if (CFStringCompare(type, kUTTypePNG, 0) == kCFCompareEqualTo) {
frameDuration = [[SDWebImageAPNGCoder sharedCoder] sd_frameDurationAtIndex:index source:imageSource];
}
if (!frameDuration) {
return;

View File

@ -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 using ImageIO that supports APNG encoding/decoding
*/
@interface SDWebImageAPNGCoder : NSObject <SDWebImageProgressiveCoder, SDWebImageAnimatedCoder>
@property (nonatomic, class, readonly, nonnull) SDWebImageAPNGCoder *sharedCoder;
@end

View File

@ -0,0 +1,380 @@
/*
* 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 "SDWebImageAPNGCoder.h"
#import <ImageIO/ImageIO.h>
#import "NSData+ImageContentType.h"
#import "UIImage+WebCache.h"
#import "NSImage+Additions.h"
#import "SDWebImageCoderHelper.h"
#import "SDAnimatedImageRep.h"
// iOS 8 Image/IO framework binary does not contains these APNG contants, so we define them. Thanks Apple :)
#if (__IPHONE_OS_VERSION_MIN_REQUIRED && __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_9_0)
const CFStringRef kCGImagePropertyAPNGLoopCount = (__bridge CFStringRef)@"LoopCount";
const CFStringRef kCGImagePropertyAPNGDelayTime = (__bridge CFStringRef)@"DelayTime";
const CFStringRef kCGImagePropertyAPNGUnclampedDelayTime = (__bridge CFStringRef)@"UnclampedDelayTime";
#endif
@interface SDAPNGCoderFrame : NSObject
@property (nonatomic, assign) NSUInteger index; // Frame index (zero based)
@property (nonatomic, assign) NSTimeInterval duration; // Frame duration in seconds
@end
@implementation SDAPNGCoderFrame
@end
@implementation SDWebImageAPNGCoder {
size_t _width, _height;
#if SD_UIKIT || SD_WATCH
UIImageOrientation _orientation;
#endif
CGImageSourceRef _imageSource;
NSData *_imageData;
NSUInteger _loopCount;
NSUInteger _frameCount;
NSArray<SDAPNGCoderFrame *> *_frames;
BOOL _finished;
}
- (void)dealloc
{
if (_imageSource) {
CFRelease(_imageSource);
_imageSource = NULL;
}
#if SD_UIKIT
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
#endif
}
- (void)didReceiveMemoryWarning:(NSNotification *)notification
{
if (_imageSource) {
for (size_t i = 0; i < _frameCount; i++) {
CGImageSourceRemoveCacheAtIndex(_imageSource, i);
}
}
}
+ (instancetype)sharedCoder {
static SDWebImageAPNGCoder *coder;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
coder = [[SDWebImageAPNGCoder alloc] init];
});
return coder;
}
#pragma mark - Decode
- (BOOL)canDecodeFromData:(nullable NSData *)data {
return ([NSData sd_imageFormatForImageData:data] == SDImageFormatPNG);
}
- (UIImage *)decodedImageWithData:(NSData *)data options:(nullable SDWebImageCoderOptions *)options {
if (!data) {
return nil;
}
#if SD_MAC
SDAnimatedImageRep *imageRep = [[SDAnimatedImageRep alloc] initWithData:data];
NSImage *animatedImage = [[NSImage alloc] initWithSize:imageRep.size];
[animatedImage addRepresentation:imageRep];
return animatedImage;
#else
CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL);
if (!source) {
return nil;
}
size_t count = CGImageSourceGetCount(source);
UIImage *animatedImage;
if (count <= 1) {
animatedImage = [[UIImage alloc] initWithData:data];
} else {
NSMutableArray<SDWebImageFrame *> *frames = [NSMutableArray array];
for (size_t i = 0; i < count; i++) {
CGImageRef imageRef = CGImageSourceCreateImageAtIndex(source, i, NULL);
if (!imageRef) {
continue;
}
float duration = [self sd_frameDurationAtIndex:i source:source];
UIImage *image = [[UIImage alloc] initWithCGImage:imageRef];
CGImageRelease(imageRef);
SDWebImageFrame *frame = [SDWebImageFrame frameWithImage:image duration:duration];
[frames addObject:frame];
}
NSUInteger loopCount = [self sd_imageLoopCountWithSource:source];
animatedImage = [SDWebImageCoderHelper animatedImageWithFrames:frames];
animatedImage.sd_imageLoopCount = loopCount;
}
CFRelease(source);
return animatedImage;
#endif
}
- (NSUInteger)sd_imageLoopCountWithSource:(CGImageSourceRef)source {
NSUInteger loopCount = 0;
NSDictionary *imageProperties = (__bridge_transfer NSDictionary *)CGImageSourceCopyProperties(source, nil);
NSDictionary *pngProperties = [imageProperties valueForKey:(__bridge_transfer NSString *)kCGImagePropertyPNGDictionary];
if (pngProperties) {
NSNumber *apngLoopCount = [pngProperties valueForKey:(__bridge_transfer NSString *)kCGImagePropertyAPNGLoopCount];
if (apngLoopCount != nil) {
loopCount = apngLoopCount.unsignedIntegerValue;
}
}
return loopCount;
}
- (float)sd_frameDurationAtIndex:(NSUInteger)index source:(CGImageSourceRef)source {
float frameDuration = 0.1f;
CFDictionaryRef cfFrameProperties = CGImageSourceCopyPropertiesAtIndex(source, index, nil);
NSDictionary *frameProperties = (__bridge NSDictionary *)cfFrameProperties;
NSDictionary *pngProperties = frameProperties[(NSString *)kCGImagePropertyPNGDictionary];
NSNumber *delayTimeUnclampedProp = pngProperties[(__bridge_transfer NSString *)kCGImagePropertyAPNGUnclampedDelayTime];
if (delayTimeUnclampedProp != nil) {
frameDuration = [delayTimeUnclampedProp floatValue];
} else {
NSNumber *delayTimeProp = pngProperties[(__bridge_transfer NSString *)kCGImagePropertyAPNGDelayTime];
if (delayTimeProp != nil) {
frameDuration = [delayTimeProp floatValue];
}
}
if (frameDuration < 0.011f) {
frameDuration = 0.100f;
}
CFRelease(cfFrameProperties);
return frameDuration;
}
#pragma mark - Encode
- (BOOL)canEncodeToFormat:(SDImageFormat)format {
return (format == SDImageFormatPNG);
}
- (NSData *)encodedDataWithImage:(UIImage *)image format:(SDImageFormat)format options:(nullable SDWebImageCoderOptions *)options {
if (!image) {
return nil;
}
if (format != SDImageFormatPNG) {
return nil;
}
NSMutableData *imageData = [NSMutableData data];
CFStringRef imageUTType = [NSData sd_UTTypeFromSDImageFormat:SDImageFormatPNG];
NSArray<SDWebImageFrame *> *frames = [SDWebImageCoderHelper framesFromAnimatedImage:image];
// Create an image destination. APNG does not support EXIF image orientation
CGImageDestinationRef imageDestination = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)imageData, imageUTType, frames.count, NULL);
if (!imageDestination) {
// Handle failure.
return nil;
}
if (frames.count == 0) {
// for static single PNG images
CGImageDestinationAddImage(imageDestination, image.CGImage, nil);
} 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);
for (size_t i = 0; i < frames.count; i++) {
SDWebImageFrame *frame = frames[i];
float frameDuration = frame.duration;
CGImageRef frameImageRef = frame.image.CGImage;
NSDictionary *frameProperties = @{(__bridge_transfer NSString *)kCGImagePropertyPNGDictionary : @{(__bridge_transfer NSString *)kCGImagePropertyAPNGDelayTime : @(frameDuration)}};
CGImageDestinationAddImage(imageDestination, frameImageRef, (__bridge CFDictionaryRef)frameProperties);
}
}
// Finalize the destination.
if (CGImageDestinationFinalize(imageDestination) == NO) {
// Handle failure.
imageData = nil;
}
CFRelease(imageDestination);
return [imageData copy];
}
#pragma mark - Progressive Decode
- (BOOL)canIncrementalDecodeFromData:(NSData *)data {
return ([NSData sd_imageFormatForImageData:data] == SDImageFormatPNG);
}
- (instancetype)initIncremental {
self = [super init];
if (self) {
_imageSource = CGImageSourceCreateIncremental((__bridge CFDictionaryRef)@{(__bridge_transfer NSString *)kCGImageSourceShouldCache : @(YES)});
#if SD_UIKIT
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveMemoryWarning:) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
#endif
}
return self;
}
- (void)updateIncrementalData:(NSData *)data finished:(BOOL)finished {
if (_finished) {
return;
}
_imageData = data;
_finished = finished;
// 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) {
CFTypeRef val = CFDictionaryGetValue(properties, kCGImagePropertyPixelHeight);
if (val) CFNumberGetValue(val, kCFNumberLongType, &_height);
val = CFDictionaryGetValue(properties, kCGImagePropertyPixelWidth);
if (val) CFNumberGetValue(val, kCFNumberLongType, &_width);
CFRelease(properties);
}
}
// For animated image progressive decoding because the frame count and duration may be changed.
[self scanAndCheckFramesValidWithImageSource:_imageSource];
}
- (UIImage *)incrementalDecodedImageWithOptions:(SDWebImageCoderOptions *)options {
UIImage *image;
if (_width + _height > 0) {
// Create the image
CGImageRef partialImageRef = CGImageSourceCreateImageAtIndex(_imageSource, 0, NULL);
if (partialImageRef) {
#if SD_UIKIT || SD_WATCH
image = [[UIImage alloc] initWithCGImage:partialImageRef];
#elif SD_MAC
image = [[UIImage alloc] initWithCGImage:partialImageRef size:NSZeroSize];
#endif
CGImageRelease(partialImageRef);
}
}
return image;
}
#pragma mark - SDWebImageAnimatedCoder
- (nullable instancetype)initWithAnimatedImageData:(nullable NSData *)data {
if (!data) {
return nil;
}
self = [super init];
if (self) {
// use Image/IO cache because it's already keep a balance between CPU & memory
CGImageSourceRef imageSource = CGImageSourceCreateWithData((__bridge CFDataRef)data, (__bridge CFDictionaryRef)@{(__bridge_transfer NSString *)kCGImageSourceShouldCache : @(YES)});
if (!imageSource) {
return nil;
}
BOOL framesValid = [self scanAndCheckFramesValidWithImageSource:imageSource];
if (!framesValid) {
CFRelease(imageSource);
return nil;
}
_imageSource = imageSource;
_imageData = data;
#if SD_UIKIT
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveMemoryWarning:) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
#endif
}
return self;
}
- (BOOL)scanAndCheckFramesValidWithImageSource:(CGImageSourceRef)imageSource
{
if (!imageSource) {
return NO;
}
NSUInteger frameCount = CGImageSourceGetCount(imageSource);
NSUInteger loopCount = [self sd_imageLoopCountWithSource:imageSource];
NSMutableArray<SDAPNGCoderFrame *> *frames = [NSMutableArray array];
for (size_t i = 0; i < frameCount; i++) {
SDAPNGCoderFrame *frame = [[SDAPNGCoderFrame alloc] init];
frame.index = i;
frame.duration = [self sd_frameDurationAtIndex:i source:imageSource];
[frames addObject:frame];
}
_frameCount = frameCount;
_loopCount = loopCount;
_frames = [frames copy];
return YES;
}
- (NSData *)animatedImageData
{
return _imageData;
}
- (NSUInteger)animatedImageLoopCount
{
return _loopCount;
}
- (NSUInteger)animatedImageFrameCount
{
return _frameCount;
}
- (NSTimeInterval)animatedImageDurationAtIndex:(NSUInteger)index
{
if (index >= _frameCount) {
return 0;
}
return _frames[index].duration;
}
- (UIImage *)animatedImageFrameAtIndex:(NSUInteger)index
{
CGImageRef imageRef = CGImageSourceCreateImageAtIndex(_imageSource, index, NULL);
if (!imageRef) {
return nil;
}
// Image/IO create CGImage does not decompressed, so we do this because this is called background queue, this can avoid main queue block when rendering(especially when one more imageViews use the same image instance)
CGImageRef newImageRef = [SDWebImageCoderHelper imageRefCreateDecoded:imageRef];
if (!newImageRef) {
newImageRef = imageRef;
} else {
CGImageRelease(imageRef);
}
#if SD_MAC
UIImage *image = [[UIImage alloc] initWithCGImage:newImageRef size:NSZeroSize];
#else
UIImage *image = [UIImage imageWithCGImage:newImageRef];
#endif
CGImageRelease(newImageRef);
return image;
}
@end

View File

@ -9,6 +9,7 @@
#import "SDWebImageCodersManager.h"
#import "SDWebImageImageIOCoder.h"
#import "SDWebImageGIFCoder.h"
#import "SDWebImageAPNGCoder.h"
#ifdef SD_WEBP
#import "SDWebImageWebPCoder.h"
#endif
@ -36,7 +37,7 @@
- (instancetype)init {
if (self = [super init]) {
// initialize with default coders
_mutableCoders = [@[[SDWebImageImageIOCoder sharedCoder], [SDWebImageGIFCoder sharedCoder]] mutableCopy];
_mutableCoders = [@[[SDWebImageImageIOCoder sharedCoder], [SDWebImageGIFCoder sharedCoder], [SDWebImageAPNGCoder sharedCoder]] mutableCopy];
#ifdef SD_WEBP
[_mutableCoders addObject:[SDWebImageWebPCoder sharedCoder]];
#endif

View File

@ -49,6 +49,7 @@ FOUNDATION_EXPORT const unsigned char WebImageVersionString[];
#import <SDWebImage/SDAnimatedImageView+WebCache.h>
#import <SDWebImage/SDWebImageCodersManager.h>
#import <SDWebImage/SDWebImageCoder.h>
#import <SDWebImage/SDWebImageAPNGCoder.h>
#import <SDWebImage/SDWebImageWebPCoder.h>
#import <SDWebImage/SDWebImageGIFCoder.h>
#import <SDWebImage/SDWebImageImageIOCoder.h>