Merge pull request #2223 from dreampiggy/improvement_macOS_gif_duration

Create a subclass of NSBitmapImageRep to fix the GIF frame duration issue on macOS
This commit is contained in:
DreamPiggy 2018-02-21 15:45:23 +08:00 committed by GitHub
commit e11ac90387
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 112 additions and 7 deletions

View File

@ -25,6 +25,9 @@
- (void)viewDidLoad {
[super viewDidLoad];
//Add GIF coder for better animated image rendering
[[SDWebImageCodersManager sharedInstance] addCoder:[SDWebImageGIFCoder sharedCoder]];
// NOTE: https links or authentication ones do not work (there is a crash)
// Do any additional setup after loading the view.

View File

@ -37,6 +37,8 @@
00733A711BC4880E00A5A117 /* UIImageView+WebCache.h in Headers */ = {isa = PBXBuildFile; fileRef = 53922D95148C56230056699D /* UIImageView+WebCache.h */; settings = {ATTRIBUTES = (Public, ); }; };
00733A721BC4880E00A5A117 /* UIView+WebCacheOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = AB615301192DA24600A2D8E9 /* UIView+WebCacheOperation.h */; settings = {ATTRIBUTES = (Public, ); }; };
00733A731BC4880E00A5A117 /* SDWebImage.h in Headers */ = {isa = PBXBuildFile; fileRef = 4A2CAE031AB4BB5400B6BC39 /* SDWebImage.h */; settings = {ATTRIBUTES = (Public, ); }; };
320224BB203979BA00E9F285 /* SDAnimatedImageRep.h in Headers */ = {isa = PBXBuildFile; fileRef = 320224B9203979BA00E9F285 /* SDAnimatedImageRep.h */; settings = {ATTRIBUTES = (Public, ); }; };
320224BC203979BA00E9F285 /* SDAnimatedImageRep.m in Sources */ = {isa = PBXBuildFile; fileRef = 320224BA203979BA00E9F285 /* SDAnimatedImageRep.m */; };
321DB3612011D4D70015D2CB /* NSButton+WebCache.h in Headers */ = {isa = PBXBuildFile; fileRef = 321DB35F2011D4D60015D2CB /* NSButton+WebCache.h */; settings = {ATTRIBUTES = (Public, ); }; };
321DB3622011D4D70015D2CB /* NSButton+WebCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 321DB3602011D4D60015D2CB /* NSButton+WebCache.m */; };
321E60861F38E8C800405457 /* SDWebImageCoder.h in Headers */ = {isa = PBXBuildFile; fileRef = 321E60841F38E8C800405457 /* SDWebImageCoder.h */; settings = {ATTRIBUTES = (Public, ); }; };
@ -1287,6 +1289,8 @@
/* Begin PBXFileReference section */
00733A4C1BC487C000A5A117 /* SDWebImage.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SDWebImage.framework; sourceTree = BUILT_PRODUCTS_DIR; };
320224B9203979BA00E9F285 /* SDAnimatedImageRep.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDAnimatedImageRep.h; sourceTree = "<group>"; };
320224BA203979BA00E9F285 /* SDAnimatedImageRep.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDAnimatedImageRep.m; sourceTree = "<group>"; };
321DB35F2011D4D60015D2CB /* NSButton+WebCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "NSButton+WebCache.h"; path = "SDWebImage/NSButton+WebCache.h"; sourceTree = "<group>"; };
321DB3602011D4D60015D2CB /* NSButton+WebCache.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "NSButton+WebCache.m"; path = "SDWebImage/NSButton+WebCache.m"; sourceTree = "<group>"; };
321E60841F38E8C800405457 /* SDWebImageCoder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDWebImageCoder.h; sourceTree = "<group>"; };
@ -1586,6 +1590,8 @@
3290FA031FA478AF0047D20C /* SDWebImageFrame.m */,
32CF1C051FA496B000004BD1 /* SDWebImageCoderHelper.h */,
32CF1C061FA496B000004BD1 /* SDWebImageCoderHelper.m */,
320224B9203979BA00E9F285 /* SDAnimatedImageRep.h */,
320224BA203979BA00E9F285 /* SDAnimatedImageRep.m */,
);
name = Decoder;
sourceTree = "<group>";
@ -2263,6 +2269,7 @@
4397D2D11D0DDD8C00BB2784 /* decode.h in Headers */,
80377E481F2F66A800F89830 /* dsp.h in Headers */,
323F8BE91F38EF770092B609 /* vp8li_enc.h in Headers */,
320224BB203979BA00E9F285 /* SDAnimatedImageRep.h in Headers */,
80377E761F2F66A800F89830 /* yuv.h in Headers */,
80377C7A1F2F666400F89830 /* bit_reader_inl_utils.h in Headers */,
80377E631F2F66A800F89830 /* lossless.h in Headers */,
@ -3229,6 +3236,7 @@
4397D2A61D0DDD8C00BB2784 /* SDWebImageCompat.m in Sources */,
80377E6F1F2F66A800F89830 /* upsampling_neon.c in Sources */,
4397D2A81D0DDD8C00BB2784 /* UIButton+WebCache.m in Sources */,
320224BC203979BA00E9F285 /* SDAnimatedImageRep.m in Sources */,
80377C8E1F2F666400F89830 /* rescaler_utils.c in Sources */,
80377E5C1F2F66A800F89830 /* lossless_enc_sse41.c in Sources */,
323F8BE31F38EF770092B609 /* vp8l_enc.c in Sources */,

View File

@ -0,0 +1,20 @@
/*
* 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 "SDWebImageCompat.h"
#if SD_MAC
// A subclass of `NSBitmapImageRep` to fix that GIF loop count issue because `NSBitmapImageRep` will reset `NSImageCurrentFrameDuration` by using `kCGImagePropertyGIFDelayTime` but not `kCGImagePropertyGIFUnclampedDelayTime`.
// Built in GIF coder use this instead of `NSBitmapImageRep` for better GIF rendering. If you do not want this, only enable `SDWebImageImageIOCoder`, which just call `NSImage` API and actually use `NSBitmapImageRep` for GIF image.
@interface SDAnimatedImageRep : NSBitmapImageRep
@end
#endif

View File

@ -0,0 +1,66 @@
/*
* 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 "SDAnimatedImageRep.h"
#if SD_MAC
#import "SDWebImageGIFCoder.h"
@interface SDWebImageGIFCoder ()
- (float)sd_frameDurationAtIndex:(NSUInteger)index source:(CGImageSourceRef)source;
@end
@interface SDAnimatedImageRep ()
@property (nonatomic, assign, readonly, nullable) CGImageSourceRef imageSource;
@end
@implementation SDAnimatedImageRep
// `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];
if ([property isEqualToString:NSImageCurrentFrame]) {
// Access the image source
CGImageSourceRef imageSource = self.imageSource;
if (!imageSource) {
return;
}
// Check format type
CFStringRef type = CGImageSourceGetType(imageSource);
if (!type) {
return;
}
NSUInteger index = [value unsignedIntegerValue];
float frameDuration = 0;
// 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];
}
if (!frameDuration) {
return;
}
// Reset super frame duration with the actual frame duration
[super setProperty:NSImageCurrentFrameDuration withValue:@(frameDuration)];
}
}
- (CGImageSourceRef)imageSource {
if (_tiffData) {
return (__bridge CGImageSourceRef)(_tiffData);
}
return NULL;
}
@end
#endif

View File

@ -14,8 +14,8 @@
/**
Return an animated image with frames array.
For UIKit, this will apply the patch and then create animated UIImage. The patch is because that `+[UIImage animatedImageWithImages:duration:]` just use the averate of duration for each image. So it will not work if different frame has different duration. Therefore we repeat the specify frame for specify times to let it work.
For AppKit, NSImage does not support animates other than GIF. This will try to encode the frames to GIF format and then create an animated NSImage for rendering.
For UIKit, this will apply the patch and then create animated UIImage. The patch is because that `+[UIImage animatedImageWithImages:duration:]` just use the average of duration for each image. So it will not work if different frame has different duration. Therefore we repeat the specify frame for specify times to let it work.
For AppKit, NSImage does not support animates other than GIF. This will try to encode the frames to GIF format and then create an animated NSImage for rendering. Attention the animated image may loss some detail if the input frames contain full alpha channel because GIF only supports 1 bit alpha channel. (For 1 pixel, either transparent or not)
@param frames The frames array. If no frames or frames is empty, return nil
@return A animated image for rendering on UIImageView(UIKit) or NSImageView(AppKit)

View File

@ -11,6 +11,7 @@
#import "UIImage+MultiFormat.h"
#import "NSImage+WebCache.h"
#import <ImageIO/ImageIO.h>
#import "SDAnimatedImageRep.h"
@implementation SDWebImageCoderHelper
@ -63,7 +64,7 @@
SDWebImageFrame *frame = frames[i];
float frameDuration = frame.duration;
CGImageRef frameImageRef = frame.image.CGImage;
NSDictionary *frameProperties = @{(__bridge_transfer NSString *)kCGImagePropertyGIFDictionary : @{(__bridge_transfer NSString *)kCGImagePropertyGIFUnclampedDelayTime : @(frameDuration)}};
NSDictionary *frameProperties = @{(__bridge_transfer NSString *)kCGImagePropertyGIFDictionary : @{(__bridge_transfer NSString *)kCGImagePropertyGIFDelayTime : @(frameDuration)}};
CGImageDestinationAddImage(imageDestination, frameImageRef, (__bridge CFDictionaryRef)frameProperties);
}
}
@ -74,7 +75,9 @@
return nil;
}
CFRelease(imageDestination);
animatedImage = [[NSImage alloc] initWithData:imageData];
SDAnimatedImageRep *imageRep = [[SDAnimatedImageRep alloc] initWithData:imageData];
animatedImage = [[NSImage alloc] initWithSize:imageRep.size];
[animatedImage addRepresentation:imageRep];
#endif
return animatedImage;

View File

@ -11,7 +11,7 @@
@interface SDWebImageFrame : NSObject
// This class is used for creating animated images via `animatedImageWithFrames` in `SDWebImageCoderHelper`. Attension if you need animated images loop count, use `sd_imageLoopCount` property in `UIImage+MultiFormat`
// This class is used for creating animated images via `animatedImageWithFrames` in `SDWebImageCoderHelper`. Attention if you need to specify animated images loop count, use `sd_imageLoopCount` property in `UIImage+MultiFormat`.
/**
The image of current frame. You should not set an animated image.

View File

@ -12,6 +12,7 @@
#import "NSData+ImageContentType.h"
#import "UIImage+MultiFormat.h"
#import "SDWebImageCoderHelper.h"
#import "SDAnimatedImageRep.h"
@implementation SDWebImageGIFCoder
@ -35,7 +36,10 @@
}
#if SD_MAC
return [[UIImage alloc] initWithData:data];
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);
@ -161,7 +165,7 @@
SDWebImageFrame *frame = frames[i];
float frameDuration = frame.duration;
CGImageRef frameImageRef = frame.image.CGImage;
NSDictionary *frameProperties = @{(__bridge_transfer NSString *)kCGImagePropertyGIFDictionary : @{(__bridge_transfer NSString *)kCGImagePropertyGIFUnclampedDelayTime : @(frameDuration)}};
NSDictionary *frameProperties = @{(__bridge_transfer NSString *)kCGImagePropertyGIFDictionary : @{(__bridge_transfer NSString *)kCGImagePropertyGIFDelayTime : @(frameDuration)}};
CGImageDestinationAddImage(imageDestination, frameImageRef, (__bridge CFDictionaryRef)frameProperties);
}
}

View File

@ -55,6 +55,7 @@ FOUNDATION_EXPORT const unsigned char WebImageVersionString[];
#if SD_MAC
#import <SDWebImage/NSImage+WebCache.h>
#import <SDWebImage/NSButton+WebCache.h>
#import <SDWebImage/SDAnimatedImageRep.h>
#endif
#if SD_UIKIT