diff --git a/Examples/SDWebImage OSX Demo/ViewController.m b/Examples/SDWebImage OSX Demo/ViewController.m index ad534b77..c746d4c5 100644 --- a/Examples/SDWebImage OSX Demo/ViewController.m +++ b/Examples/SDWebImage OSX Demo/ViewController.m @@ -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. diff --git a/SDWebImage.xcodeproj/project.pbxproj b/SDWebImage.xcodeproj/project.pbxproj index abf63968..158e6699 100644 --- a/SDWebImage.xcodeproj/project.pbxproj +++ b/SDWebImage.xcodeproj/project.pbxproj @@ -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 = ""; }; + 320224BA203979BA00E9F285 /* SDAnimatedImageRep.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDAnimatedImageRep.m; sourceTree = ""; }; 321DB35F2011D4D60015D2CB /* NSButton+WebCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "NSButton+WebCache.h"; path = "SDWebImage/NSButton+WebCache.h"; sourceTree = ""; }; 321DB3602011D4D60015D2CB /* NSButton+WebCache.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "NSButton+WebCache.m"; path = "SDWebImage/NSButton+WebCache.m"; sourceTree = ""; }; 321E60841F38E8C800405457 /* SDWebImageCoder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDWebImageCoder.h; sourceTree = ""; }; @@ -1586,6 +1590,8 @@ 3290FA031FA478AF0047D20C /* SDWebImageFrame.m */, 32CF1C051FA496B000004BD1 /* SDWebImageCoderHelper.h */, 32CF1C061FA496B000004BD1 /* SDWebImageCoderHelper.m */, + 320224B9203979BA00E9F285 /* SDAnimatedImageRep.h */, + 320224BA203979BA00E9F285 /* SDAnimatedImageRep.m */, ); name = Decoder; sourceTree = ""; @@ -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 */, diff --git a/SDWebImage/SDAnimatedImageRep.h b/SDWebImage/SDAnimatedImageRep.h new file mode 100644 index 00000000..c794c4b8 --- /dev/null +++ b/SDWebImage/SDAnimatedImageRep.h @@ -0,0 +1,20 @@ +/* + * This file is part of the SDWebImage package. + * (c) Olivier Poitrey + * + * 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 diff --git a/SDWebImage/SDAnimatedImageRep.m b/SDWebImage/SDAnimatedImageRep.m new file mode 100644 index 00000000..f0907d5c --- /dev/null +++ b/SDWebImage/SDAnimatedImageRep.m @@ -0,0 +1,66 @@ +/* + * This file is part of the SDWebImage package. + * (c) Olivier Poitrey + * + * 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 diff --git a/SDWebImage/SDWebImageCoderHelper.h b/SDWebImage/SDWebImageCoderHelper.h index cdafd88d..ad2fe855 100644 --- a/SDWebImage/SDWebImageCoderHelper.h +++ b/SDWebImage/SDWebImageCoderHelper.h @@ -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) diff --git a/SDWebImage/SDWebImageCoderHelper.m b/SDWebImage/SDWebImageCoderHelper.m index b2b651a2..0cdd4a5a 100644 --- a/SDWebImage/SDWebImageCoderHelper.m +++ b/SDWebImage/SDWebImageCoderHelper.m @@ -11,6 +11,7 @@ #import "UIImage+MultiFormat.h" #import "NSImage+WebCache.h" #import +#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; diff --git a/SDWebImage/SDWebImageFrame.h b/SDWebImage/SDWebImageFrame.h index d8ba1812..c3a0f712 100644 --- a/SDWebImage/SDWebImageFrame.h +++ b/SDWebImage/SDWebImageFrame.h @@ -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. diff --git a/SDWebImage/SDWebImageGIFCoder.m b/SDWebImage/SDWebImageGIFCoder.m index e63afa04..b9ebf1a5 100644 --- a/SDWebImage/SDWebImageGIFCoder.m +++ b/SDWebImage/SDWebImageGIFCoder.m @@ -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); } } diff --git a/WebImage/SDWebImage.h b/WebImage/SDWebImage.h index 5e02f993..07c173e3 100644 --- a/WebImage/SDWebImage.h +++ b/WebImage/SDWebImage.h @@ -55,6 +55,7 @@ FOUNDATION_EXPORT const unsigned char WebImageVersionString[]; #if SD_MAC #import #import + #import #endif #if SD_UIKIT