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:
commit
e11ac90387
|
@ -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.
|
||||
|
|
|
@ -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 */,
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue