Feature refactor built-in coders and support animated webp on macOS (#2082)

* Refactor code. Move the C global function to a new SDWebImageCoderHelper class.

1. Create two method for animated image parse. Provide the way to allow animates on macOS
2. Create a new class called SDWebImageFrame to allow abstract usage for animated image
3. Fix EXIF orientation method will crash on iOS 7 because it’s a iOS 8 above API

* Change sd_imageLoopCount to retrieve GIF loop count for NSImage on macOS

* Adopt the refactor code, change our build-in coder with that coder helper method to reduce complexity

* Update the demo project on macOS to show animated WebP
This commit is contained in:
DreamPiggy 2017-11-01 02:34:06 +08:00 committed by Bogdan Poplauschi
parent 3064ef6889
commit 19f45a3cf4
13 changed files with 512 additions and 295 deletions

View File

@ -29,6 +29,7 @@
// Do any additional setup after loading the view.
// For animated GIF rendering, set `animates` to YES or will only show the first frame
self.imageView1.animates = YES;
self.imageView3.animates = YES;
[self.imageView1 sd_setImageWithURL:[NSURL URLWithString:@"http://assets.sbnation.com/assets/2512203/dogflops.gif"]];
[self.imageView2 sd_setImageWithURL:[NSURL URLWithString:@"http://www.ioncannon.net/wp-content/uploads/2011/06/test2.webp"]];
[self.imageView3 sd_setImageWithURL:[NSURL URLWithString:@"http://littlesvr.ca/apng/images/SteamEngine.webp"]];

View File

@ -307,6 +307,30 @@
323F8C1D1F38EF770092B609 /* muxread.c in Sources */ = {isa = PBXBuildFile; fileRef = 323F8B3D1F38EF770092B609 /* muxread.c */; };
323F8C1E1F38EF770092B609 /* muxread.c in Sources */ = {isa = PBXBuildFile; fileRef = 323F8B3D1F38EF770092B609 /* muxread.c */; };
323F8C1F1F38EF770092B609 /* muxread.c in Sources */ = {isa = PBXBuildFile; fileRef = 323F8B3D1F38EF770092B609 /* muxread.c */; };
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, ); }; };
3290FA071FA478AF0047D20C /* SDWebImageFrame.h in Headers */ = {isa = PBXBuildFile; fileRef = 3290FA021FA478AF0047D20C /* SDWebImageFrame.h */; settings = {ATTRIBUTES = (Public, ); }; };
3290FA081FA478AF0047D20C /* SDWebImageFrame.h in Headers */ = {isa = PBXBuildFile; fileRef = 3290FA021FA478AF0047D20C /* SDWebImageFrame.h */; settings = {ATTRIBUTES = (Public, ); }; };
3290FA091FA478AF0047D20C /* SDWebImageFrame.h in Headers */ = {isa = PBXBuildFile; fileRef = 3290FA021FA478AF0047D20C /* SDWebImageFrame.h */; settings = {ATTRIBUTES = (Public, ); }; };
3290FA0A1FA478AF0047D20C /* SDWebImageFrame.m in Sources */ = {isa = PBXBuildFile; fileRef = 3290FA031FA478AF0047D20C /* SDWebImageFrame.m */; };
3290FA0B1FA478AF0047D20C /* SDWebImageFrame.m in Sources */ = {isa = PBXBuildFile; fileRef = 3290FA031FA478AF0047D20C /* SDWebImageFrame.m */; };
3290FA0C1FA478AF0047D20C /* SDWebImageFrame.m in Sources */ = {isa = PBXBuildFile; fileRef = 3290FA031FA478AF0047D20C /* SDWebImageFrame.m */; };
3290FA0D1FA478AF0047D20C /* SDWebImageFrame.m in Sources */ = {isa = PBXBuildFile; fileRef = 3290FA031FA478AF0047D20C /* SDWebImageFrame.m */; };
3290FA0E1FA478AF0047D20C /* SDWebImageFrame.m in Sources */ = {isa = PBXBuildFile; fileRef = 3290FA031FA478AF0047D20C /* SDWebImageFrame.m */; };
3290FA0F1FA478AF0047D20C /* SDWebImageFrame.m in Sources */ = {isa = PBXBuildFile; fileRef = 3290FA031FA478AF0047D20C /* SDWebImageFrame.m */; };
32CF1C071FA496B000004BD1 /* SDWebImageCoderHelper.h in Headers */ = {isa = PBXBuildFile; fileRef = 32CF1C051FA496B000004BD1 /* SDWebImageCoderHelper.h */; settings = {ATTRIBUTES = (Public, ); }; };
32CF1C081FA496B000004BD1 /* SDWebImageCoderHelper.h in Headers */ = {isa = PBXBuildFile; fileRef = 32CF1C051FA496B000004BD1 /* SDWebImageCoderHelper.h */; settings = {ATTRIBUTES = (Public, ); }; };
32CF1C091FA496B000004BD1 /* SDWebImageCoderHelper.h in Headers */ = {isa = PBXBuildFile; fileRef = 32CF1C051FA496B000004BD1 /* SDWebImageCoderHelper.h */; settings = {ATTRIBUTES = (Public, ); }; };
32CF1C0A1FA496B000004BD1 /* SDWebImageCoderHelper.h in Headers */ = {isa = PBXBuildFile; fileRef = 32CF1C051FA496B000004BD1 /* SDWebImageCoderHelper.h */; settings = {ATTRIBUTES = (Public, ); }; };
32CF1C0B1FA496B000004BD1 /* SDWebImageCoderHelper.h in Headers */ = {isa = PBXBuildFile; fileRef = 32CF1C051FA496B000004BD1 /* SDWebImageCoderHelper.h */; settings = {ATTRIBUTES = (Public, ); }; };
32CF1C0C1FA496B000004BD1 /* SDWebImageCoderHelper.h in Headers */ = {isa = PBXBuildFile; fileRef = 32CF1C051FA496B000004BD1 /* SDWebImageCoderHelper.h */; settings = {ATTRIBUTES = (Public, ); }; };
32CF1C0D1FA496B000004BD1 /* SDWebImageCoderHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 32CF1C061FA496B000004BD1 /* SDWebImageCoderHelper.m */; };
32CF1C0E1FA496B000004BD1 /* SDWebImageCoderHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 32CF1C061FA496B000004BD1 /* SDWebImageCoderHelper.m */; };
32CF1C0F1FA496B000004BD1 /* SDWebImageCoderHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 32CF1C061FA496B000004BD1 /* SDWebImageCoderHelper.m */; };
32CF1C101FA496B000004BD1 /* SDWebImageCoderHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 32CF1C061FA496B000004BD1 /* SDWebImageCoderHelper.m */; };
32CF1C111FA496B000004BD1 /* SDWebImageCoderHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 32CF1C061FA496B000004BD1 /* SDWebImageCoderHelper.m */; };
32CF1C121FA496B000004BD1 /* SDWebImageCoderHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 32CF1C061FA496B000004BD1 /* SDWebImageCoderHelper.m */; };
4314D1231D0E0E3B004B36C9 /* SDImageCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 53922D86148C56230056699D /* SDImageCache.m */; };
4314D1311D0E0E3B004B36C9 /* SDWebImageDownloader.m in Sources */ = {isa = PBXBuildFile; fileRef = 53922D8C148C56230056699D /* SDWebImageDownloader.m */; };
4314D1341D0E0E3B004B36C9 /* UIImage+WebP.m in Sources */ = {isa = PBXBuildFile; fileRef = 53EDFB921762547C00698166 /* UIImage+WebP.m */; };
@ -1294,6 +1318,10 @@
323F8B3B1F38EF770092B609 /* muxi.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = muxi.h; sourceTree = "<group>"; };
323F8B3C1F38EF770092B609 /* muxinternal.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = muxinternal.c; sourceTree = "<group>"; };
323F8B3D1F38EF770092B609 /* muxread.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = muxread.c; 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>"; };
32CF1C051FA496B000004BD1 /* SDWebImageCoderHelper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDWebImageCoderHelper.h; sourceTree = "<group>"; };
32CF1C061FA496B000004BD1 /* SDWebImageCoderHelper.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDWebImageCoderHelper.m; sourceTree = "<group>"; };
4314D1991D0E0E3B004B36C9 /* libSDWebImage watchOS static.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libSDWebImage watchOS static.a"; sourceTree = BUILT_PRODUCTS_DIR; };
431BB7031D06D2C1006A3455 /* SDWebImage.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SDWebImage.framework; sourceTree = BUILT_PRODUCTS_DIR; };
4369C2751D9807EC007E863A /* UIView+WebCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "UIView+WebCache.h"; path = "SDWebImage/UIView+WebCache.h"; sourceTree = "<group>"; };
@ -1536,6 +1564,10 @@
321E60A11F38E8F600405457 /* SDWebImageGIFCoder.m */,
321E60AE1F38E90100405457 /* SDWebImageWebPCoder.h */,
321E60AF1F38E90100405457 /* SDWebImageWebPCoder.m */,
3290FA021FA478AF0047D20C /* SDWebImageFrame.h */,
3290FA031FA478AF0047D20C /* SDWebImageFrame.m */,
32CF1C051FA496B000004BD1 /* SDWebImageCoderHelper.h */,
32CF1C061FA496B000004BD1 /* SDWebImageCoderHelper.m */,
);
name = Decoder;
sourceTree = "<group>";
@ -1963,6 +1995,7 @@
00733A6F1BC4880E00A5A117 /* UIImage+WebP.h in Headers */,
323F8B711F38EF770092B609 /* delta_palettization_enc.h in Headers */,
321E60B31F38E90100405457 /* SDWebImageWebPCoder.h in Headers */,
3290FA071FA478AF0047D20C /* SDWebImageFrame.h in Headers */,
807A122B1F89636300EC2A9B /* SDWebImageCodersManager.h in Headers */,
80377EC61F2F66D500F89830 /* webpi_dec.h in Headers */,
80377C591F2F666300F89830 /* random_utils.h in Headers */,
@ -1996,6 +2029,7 @@
00733A6A1BC4880E00A5A117 /* SDWebImagePrefetcher.h in Headers */,
00733A641BC4880E00A5A117 /* SDWebImageOperation.h in Headers */,
321E60A51F38E8F600405457 /* SDWebImageGIFCoder.h in Headers */,
32CF1C0A1FA496B000004BD1 /* SDWebImageCoderHelper.h in Headers */,
80377C4D1F2F666300F89830 /* endian_inl_utils.h in Headers */,
431739581CDFC8B70008FEB9 /* format_constants.h in Headers */,
43CE75781CFE9427006C64D0 /* FLAnimatedImage.h in Headers */,
@ -2025,6 +2059,7 @@
files = (
80377D521F2F66A700F89830 /* neon.h in Headers */,
80377D261F2F66A700F89830 /* common_sse2.h in Headers */,
3290FA051FA478AF0047D20C /* SDWebImageFrame.h in Headers */,
80377C1D1F2F666300F89830 /* huffman_encode_utils.h in Headers */,
321E60B11F38E90100405457 /* SDWebImageWebPCoder.h in Headers */,
80377E9A1F2F66D400F89830 /* common_dec.h in Headers */,
@ -2038,6 +2073,7 @@
4314D1621D0E0E3B004B36C9 /* mux_types.h in Headers */,
4314D1631D0E0E3B004B36C9 /* demux.h in Headers */,
80377D421F2F66A700F89830 /* lossless_common.h in Headers */,
32CF1C081FA496B000004BD1 /* SDWebImageCoderHelper.h in Headers */,
4314D16B1D0E0E3B004B36C9 /* encode.h in Headers */,
80377D501F2F66A700F89830 /* mips_macro.h in Headers */,
80377C291F2F666300F89830 /* thread_utils.h in Headers */,
@ -2098,6 +2134,7 @@
files = (
80377C791F2F666400F89830 /* utils.h in Headers */,
323F8B721F38EF770092B609 /* delta_palettization_enc.h in Headers */,
32CF1C0B1FA496B000004BD1 /* SDWebImageCoderHelper.h in Headers */,
431BB6D71D06D2C1006A3455 /* UIImage+WebP.h in Headers */,
431BB6D91D06D2C1006A3455 /* SDWebImageManager.h in Headers */,
80377C691F2F666400F89830 /* filters_utils.h in Headers */,
@ -2122,6 +2159,7 @@
80377E201F2F66A800F89830 /* msa_macro.h in Headers */,
80377E031F2F66A800F89830 /* dsp.h in Headers */,
80377C661F2F666400F89830 /* color_cache_utils.h in Headers */,
3290FA081FA478AF0047D20C /* SDWebImageFrame.h in Headers */,
321E60A61F38E8F600405457 /* SDWebImageGIFCoder.h in Headers */,
431BB6E71D06D2C1006A3455 /* SDWebImageCompat.h in Headers */,
80377E211F2F66A800F89830 /* neon.h in Headers */,
@ -2188,6 +2226,7 @@
323F8B8B1F38EF770092B609 /* histogram_enc.h in Headers */,
4397D2C41D0DDD8C00BB2784 /* SDImageCache.h in Headers */,
4397D2C51D0DDD8C00BB2784 /* UIImageView+WebCache.h in Headers */,
3290FA091FA478AF0047D20C /* SDWebImageFrame.h in Headers */,
4369C27C1D9807EC007E863A /* UIView+WebCache.h in Headers */,
80377EE21F2F66D500F89830 /* vp8i_dec.h in Headers */,
80377C8B1F2F666400F89830 /* quant_levels_utils.h in Headers */,
@ -2202,6 +2241,7 @@
80377E761F2F66A800F89830 /* yuv.h in Headers */,
80377C7A1F2F666400F89830 /* bit_reader_inl_utils.h in Headers */,
80377E631F2F66A800F89830 /* lossless.h in Headers */,
32CF1C0C1FA496B000004BD1 /* SDWebImageCoderHelper.h in Headers */,
43A918691D8308FE00B3925F /* SDImageCacheConfig.h in Headers */,
4397D2D81D0DDD8C00BB2784 /* UIButton+WebCache.h in Headers */,
80377E641F2F66A800F89830 /* mips_macro.h in Headers */,
@ -2264,6 +2304,7 @@
43A918661D8308FE00B3925F /* SDImageCacheConfig.h in Headers */,
323F8B701F38EF770092B609 /* delta_palettization_enc.h in Headers */,
321E60B21F38E90100405457 /* SDWebImageWebPCoder.h in Headers */,
3290FA061FA478AF0047D20C /* SDWebImageFrame.h in Headers */,
807A122A1F89636300EC2A9B /* SDWebImageCodersManager.h in Headers */,
80377EB61F2F66D400F89830 /* webpi_dec.h in Headers */,
4A2CAE211AB4BB7000B6BC39 /* SDWebImageManager.h in Headers */,
@ -2297,6 +2338,7 @@
4A2CAE1A1AB4BB6400B6BC39 /* SDWebImageOperation.h in Headers */,
80377C331F2F666300F89830 /* endian_inl_utils.h in Headers */,
321E60A41F38E8F600405457 /* SDWebImageGIFCoder.h in Headers */,
32CF1C091FA496B000004BD1 /* SDWebImageCoderHelper.h in Headers */,
4A2CAE1B1AB4BB6800B6BC39 /* SDWebImageDownloader.h in Headers */,
4A2CAE271AB4BB7500B6BC39 /* MKAnnotationView+WebCache.h in Headers */,
431739501CDFC8B70008FEB9 /* encode.h in Headers */,
@ -2327,6 +2369,7 @@
431738C21CDFC2660008FEB9 /* mux_types.h in Headers */,
431738BE1CDFC2660008FEB9 /* demux.h in Headers */,
80377BFC1F2F665300F89830 /* bit_writer_utils.h in Headers */,
32CF1C071FA496B000004BD1 /* SDWebImageCoderHelper.h in Headers */,
431738BF1CDFC2660008FEB9 /* encode.h in Headers */,
53761316155AD0D5005750A4 /* SDImageCache.h in Headers */,
323F8C0E1F38EF770092B609 /* muxi.h in Headers */,
@ -2339,6 +2382,7 @@
80377D0D1F2F66A100F89830 /* neon.h in Headers */,
431738C31CDFC2660008FEB9 /* types.h in Headers */,
80377D0C1F2F66A100F89830 /* msa_macro.h in Headers */,
3290FA041FA478AF0047D20C /* SDWebImageFrame.h in Headers */,
80377D1D1F2F66A100F89830 /* yuv.h in Headers */,
43CE75D01CFE98E0006C64D0 /* FLAnimatedImageView+WebCache.h in Headers */,
807A12281F89636300EC2A9B /* SDWebImageCodersManager.h in Headers */,
@ -2616,6 +2660,7 @@
files = (
80377DD31F2F66A700F89830 /* lossless_enc.c in Sources */,
323F8BBD1F38EF770092B609 /* predictor_enc.c in Sources */,
3290FA0D1FA478AF0047D20C /* SDWebImageFrame.m in Sources */,
80377DBD1F2F66A700F89830 /* dec.c in Sources */,
00733A561BC4880000A5A117 /* SDWebImageDownloaderOperation.m in Sources */,
80377DE71F2F66A700F89830 /* upsampling.c in Sources */,
@ -2640,6 +2685,7 @@
80377DC31F2F66A700F89830 /* enc_neon.c in Sources */,
80377C501F2F666300F89830 /* huffman_encode_utils.c in Sources */,
80377DE51F2F66A700F89830 /* upsampling_neon.c in Sources */,
32CF1C101FA496B000004BD1 /* SDWebImageCoderHelper.m in Sources */,
80377DAE1F2F66A700F89830 /* argb_sse2.c in Sources */,
323F8B7D1F38EF770092B609 /* frame_enc.c in Sources */,
80377EBB1F2F66D500F89830 /* frame_dec.c in Sources */,
@ -2807,6 +2853,7 @@
80377D291F2F66A700F89830 /* cost_sse2.c in Sources */,
80377D601F2F66A700F89830 /* yuv_sse2.c in Sources */,
80377C281F2F666300F89830 /* thread_utils.c in Sources */,
3290FA0B1FA478AF0047D20C /* SDWebImageFrame.m in Sources */,
80377C2A1F2F666300F89830 /* utils.c in Sources */,
323F8B4B1F38EF770092B609 /* backward_references_enc.c in Sources */,
807A122F1F89636300EC2A9B /* SDWebImageCodersManager.m in Sources */,
@ -2869,6 +2916,7 @@
323F8B9D1F38EF770092B609 /* picture_csp_enc.c in Sources */,
4314D14B1D0E0E3B004B36C9 /* SDWebImageCompat.m in Sources */,
4314D14D1D0E0E3B004B36C9 /* UIImage+GIF.m in Sources */,
32CF1C0E1FA496B000004BD1 /* SDWebImageCoderHelper.m in Sources */,
323F8B571F38EF770092B609 /* config_enc.c in Sources */,
80377D371F2F66A700F89830 /* enc_mips32.c in Sources */,
80377D431F2F66A700F89830 /* lossless_enc_mips_dsp_r2.c in Sources */,
@ -2948,6 +2996,7 @@
80377E0F1F2F66A800F89830 /* filters_sse2.c in Sources */,
80377DED1F2F66A800F89830 /* alpha_processing_mips_dsp_r2.c in Sources */,
80377DF81F2F66A800F89830 /* cost_sse2.c in Sources */,
3290FA0E1FA478AF0047D20C /* SDWebImageFrame.m in Sources */,
80377E2F1F2F66A800F89830 /* yuv_sse2.c in Sources */,
431BB6AA1D06D2C1006A3455 /* SDWebImageManager.m in Sources */,
323F8B4E1F38EF770092B609 /* backward_references_enc.c in Sources */,
@ -3010,6 +3059,7 @@
323F8BA01F38EF770092B609 /* picture_csp_enc.c in Sources */,
80377C701F2F666400F89830 /* quant_levels_utils.c in Sources */,
431BB6BD1D06D2C1006A3455 /* UIImage+GIF.m in Sources */,
32CF1C111FA496B000004BD1 /* SDWebImageCoderHelper.m in Sources */,
323F8B5A1F38EF770092B609 /* config_enc.c in Sources */,
80377E061F2F66A800F89830 /* enc_mips32.c in Sources */,
80377E121F2F66A800F89830 /* lossless_enc_mips_dsp_r2.c in Sources */,
@ -3070,7 +3120,9 @@
323F8B491F38EF770092B609 /* analysis_enc.c in Sources */,
80377E6E1F2F66A800F89830 /* upsampling_msa.c in Sources */,
323F8B911F38EF770092B609 /* iterator_enc.c in Sources */,
3290FA0F1FA478AF0047D20C /* SDWebImageFrame.m in Sources */,
80377EE01F2F66D500F89830 /* vp8_dec.c in Sources */,
32CF1C121FA496B000004BD1 /* SDWebImageCoderHelper.m in Sources */,
80377E521F2F66A800F89830 /* filters_msa.c in Sources */,
80377C821F2F666400F89830 /* filters_utils.c in Sources */,
4397D28C1D0DDD8C00BB2784 /* UIImageView+WebCache.m in Sources */,
@ -3186,6 +3238,7 @@
files = (
80377D8E1F2F66A700F89830 /* lossless_enc.c in Sources */,
323F8BBC1F38EF770092B609 /* predictor_enc.c in Sources */,
3290FA0C1FA478AF0047D20C /* SDWebImageFrame.m in Sources */,
80377D781F2F66A700F89830 /* dec.c in Sources */,
80377DA21F2F66A700F89830 /* upsampling.c in Sources */,
80377C401F2F666300F89830 /* rescaler_utils.c in Sources */,
@ -3210,6 +3263,7 @@
4A2CAE321AB4BB7500B6BC39 /* UIImage+WebP.m in Sources */,
80377DA01F2F66A700F89830 /* upsampling_neon.c in Sources */,
80377D691F2F66A700F89830 /* argb_sse2.c in Sources */,
32CF1C0F1FA496B000004BD1 /* SDWebImageCoderHelper.m in Sources */,
80377D6A1F2F66A700F89830 /* argb.c in Sources */,
323F8B7C1F38EF770092B609 /* frame_enc.c in Sources */,
80377EAB1F2F66D400F89830 /* frame_dec.c in Sources */,
@ -3331,6 +3385,7 @@
files = (
80377D041F2F66A100F89830 /* lossless_enc.c in Sources */,
323F8BBA1F38EF770092B609 /* predictor_enc.c in Sources */,
3290FA0A1FA478AF0047D20C /* SDWebImageFrame.m in Sources */,
80377CEE1F2F66A100F89830 /* dec.c in Sources */,
80377D181F2F66A100F89830 /* upsampling.c in Sources */,
80377C0C1F2F665300F89830 /* rescaler_utils.c in Sources */,
@ -3355,6 +3410,7 @@
80377D161F2F66A100F89830 /* upsampling_neon.c in Sources */,
80377CDF1F2F66A100F89830 /* argb_sse2.c in Sources */,
80377CE01F2F66A100F89830 /* argb.c in Sources */,
32CF1C0D1FA496B000004BD1 /* SDWebImageCoderHelper.m in Sources */,
323F8B7A1F38EF770092B609 /* frame_enc.c in Sources */,
80377E8B1F2F66D000F89830 /* frame_dec.c in Sources */,
43C8929A1D9D6DD70022038D /* anim_decode.c in Sources */,

View File

@ -28,12 +28,10 @@
if ([rep isKindOfClass:[NSBitmapImageRep class]]) {
NSBitmapImageRep *bitmapRep = (NSBitmapImageRep *)rep;
NSUInteger frameCount = [[bitmapRep valueForProperty:NSImageFrameCount] unsignedIntegerValue];
if (frameCount > 1) {
isGIF = YES;
isGIF = frameCount > 1 ? YES : NO;
break;
}
}
}
return isGIF;
}

View File

@ -0,0 +1,52 @@
/*
* 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 "SDWebImageCompat.h"
#import "SDWebImageFrame.h"
@interface SDWebImageCoderHelper : NSObject
/**
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.
@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)
*/
+ (UIImage * _Nullable)animatedImageWithFrames:(NSArray<SDWebImageFrame *> * _Nullable)frames;
/**
Return frames array from an animated image.
For UIKit, this will unapply the patch for the description above and then create frames array. This will also work for normal animated UIImage.
For AppKit, NSImage does not support animates other than GIF. This will try to decode the GIF imageRep and then create frames array.
@param animatedImage A animated image. If it's not animated, return nil
@return The frames array
*/
+ (NSArray<SDWebImageFrame *> * _Nullable)framesFromAnimatedImage:(UIImage * _Nullable)animatedImage;
#if SD_UIKIT || SD_WATCH
/**
Convert an EXIF image orientation to an iOS one.
@param exifOrientation EXIF orientation
@return iOS orientation
*/
+ (UIImageOrientation)imageOrientationFromEXIFOrientation:(NSInteger)exifOrientation;
/**
Convert an iOS orientation to an EXIF image orientation.
@param imageOrientation iOS orientation
@return EXIF orientation
*/
+ (NSInteger)exifOrientationFromImageOrientation:(UIImageOrientation)imageOrientation;
#endif
@end

View File

@ -0,0 +1,255 @@
/*
* 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 "SDWebImageCoderHelper.h"
#import "SDWebImageFrame.h"
#import "UIImage+MultiFormat.h"
#import "NSImage+WebCache.h"
#import <ImageIO/ImageIO.h>
@implementation SDWebImageCoderHelper
+ (UIImage *)animatedImageWithFrames:(NSArray<SDWebImageFrame *> *)frames {
NSUInteger frameCount = frames.count;
if (frameCount == 0) {
return nil;
}
UIImage *animatedImage;
#if SD_UIKIT || SD_WATCH
NSUInteger durations[frameCount];
for (size_t i = 0; i < frameCount; i++) {
durations[i] = frames[i].duration * 1000;
}
NSUInteger const gcd = gcdArray(frameCount, durations);
__block NSUInteger totalDuration = 0;
NSMutableArray<UIImage *> *animatedImages = [NSMutableArray arrayWithCapacity:frameCount];
[frames enumerateObjectsUsingBlock:^(SDWebImageFrame * _Nonnull frame, NSUInteger idx, BOOL * _Nonnull stop) {
UIImage *image = frame.image;
NSUInteger duration = frame.duration * 1000;
totalDuration += duration;
NSUInteger repeatCount;
if (gcd) {
repeatCount = duration / gcd;
} else {
repeatCount = 1;
}
for (size_t i = 0; i < repeatCount; ++i) {
[animatedImages addObject:image];
}
}];
animatedImage = [UIImage animatedImageWithImages:animatedImages duration:totalDuration / 1000.f];
#else
NSMutableData *imageData = [NSMutableData data];
CFStringRef imageUTType = [NSData sd_UTTypeFromSDImageFormat:SDImageFormatGIF];
// Create an image destination. GIF does not support EXIF image orientation
CGImageDestinationRef imageDestination = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)imageData, imageUTType, frameCount, NULL);
if (!imageDestination) {
// Handle failure.
return nil;
}
for (size_t i = 0; i < frameCount; i++) {
@autoreleasepool {
SDWebImageFrame *frame = frames[i];
float frameDuration = frame.duration;
CGImageRef frameImageRef = frame.image.CGImage;
NSDictionary *frameProperties = @{(__bridge_transfer NSString *)kCGImagePropertyGIFDictionary : @{(__bridge_transfer NSString *)kCGImagePropertyGIFUnclampedDelayTime : @(frameDuration)}};
CGImageDestinationAddImage(imageDestination, frameImageRef, (__bridge CFDictionaryRef)frameProperties);
}
}
// Finalize the destination.
if (CGImageDestinationFinalize(imageDestination) == NO) {
// Handle failure.
CFRelease(imageDestination);
return nil;
}
CFRelease(imageDestination);
animatedImage = [[NSImage alloc] initWithData:imageData];
#endif
return animatedImage;
}
+ (NSArray<SDWebImageFrame *> *)framesFromAnimatedImage:(UIImage *)animatedImage {
if (!animatedImage) {
return nil;
}
NSMutableArray<SDWebImageFrame *> *frames = [NSMutableArray array];
NSUInteger frameCount = 0;
#if SD_UIKIT || SD_WATCH
NSArray<UIImage *> *animatedImages = animatedImage.images;
frameCount = animatedImages.count;
if (frameCount == 0) {
return nil;
}
NSTimeInterval avgDuration = animatedImage.duration / frameCount;
if (avgDuration == 0) {
avgDuration = 0.1; // if it's a animated image but no duration, set it to default 100ms (this do not have that 10ms limit like GIF or WebP to allow custom coder provide the limit)
}
__block NSUInteger index = 0;
__block NSUInteger repeatCount = 1;
__block UIImage *previousImage = animatedImages.firstObject;
[animatedImages enumerateObjectsUsingBlock:^(UIImage * _Nonnull image, NSUInteger idx, BOOL * _Nonnull stop) {
// ignore first
if (idx == 0) {
return;
}
if ([image isEqual:previousImage]) {
repeatCount++;
} else {
SDWebImageFrame *frame = [SDWebImageFrame frameWithImage:previousImage duration:avgDuration * repeatCount];
[frames addObject:frame];
repeatCount = 1;
index++;
}
previousImage = image;
// last one
if (idx == frameCount - 1) {
SDWebImageFrame *frame = [SDWebImageFrame frameWithImage:previousImage duration:avgDuration * repeatCount];
[frames addObject:frame];
}
}];
#else
NSBitmapImageRep *bitmapRep;
for (NSImageRep *imageRep in animatedImage.representations) {
if ([imageRep isKindOfClass:[NSBitmapImageRep class]]) {
bitmapRep = (NSBitmapImageRep *)imageRep;
break;
}
}
if (bitmapRep) {
frameCount = [[bitmapRep valueForProperty:NSImageFrameCount] unsignedIntegerValue];
}
if (frameCount == 0) {
return nil;
}
for (size_t i = 0; i < frameCount; i++) {
@autoreleasepool {
// NSBitmapImageRep need to manually change frame. "Good taste" API
[bitmapRep setProperty:NSImageCurrentFrame withValue:@(i)];
float frameDuration = [[bitmapRep valueForProperty:NSImageCurrentFrameDuration] floatValue];
NSImage *frameImage = [[NSImage alloc] initWithCGImage:bitmapRep.CGImage size:CGSizeZero];
SDWebImageFrame *frame = [SDWebImageFrame frameWithImage:frameImage duration:frameDuration];
[frames addObject:frame];
}
}
#endif
return frames;
}
#if SD_UIKIT || SD_WATCH
// Convert an EXIF image orientation to an iOS one.
+ (UIImageOrientation)imageOrientationFromEXIFOrientation:(NSInteger)exifOrientation {
// CGImagePropertyOrientation is available on iOS 8 above. Currently kept for compatibility
UIImageOrientation imageOrientation = UIImageOrientationUp;
switch (exifOrientation) {
case 1:
imageOrientation = UIImageOrientationUp;
break;
case 3:
imageOrientation = UIImageOrientationDown;
break;
case 8:
imageOrientation = UIImageOrientationLeft;
break;
case 6:
imageOrientation = UIImageOrientationRight;
break;
case 2:
imageOrientation = UIImageOrientationUpMirrored;
break;
case 4:
imageOrientation = UIImageOrientationDownMirrored;
break;
case 5:
imageOrientation = UIImageOrientationLeftMirrored;
break;
case 7:
imageOrientation = UIImageOrientationRightMirrored;
break;
default:
break;
}
return imageOrientation;
}
// Convert an iOS orientation to an EXIF image orientation.
+ (NSInteger)exifOrientationFromImageOrientation:(UIImageOrientation)imageOrientation {
// CGImagePropertyOrientation is available on iOS 8 above. Currently kept for compatibility
NSInteger exifOrientation = 1;
switch (imageOrientation) {
case UIImageOrientationUp:
exifOrientation = 1;
break;
case UIImageOrientationDown:
exifOrientation = 3;
break;
case UIImageOrientationLeft:
exifOrientation = 8;
break;
case UIImageOrientationRight:
exifOrientation = 6;
break;
case UIImageOrientationUpMirrored:
exifOrientation = 2;
break;
case UIImageOrientationDownMirrored:
exifOrientation = 4;
break;
case UIImageOrientationLeftMirrored:
exifOrientation = 5;
break;
case UIImageOrientationRightMirrored:
exifOrientation = 7;
break;
default:
break;
}
return exifOrientation;
}
#endif
#pragma mark - Helper Fuction
#if SD_UIKIT || SD_WATCH
static NSUInteger gcd(NSUInteger a, NSUInteger b) {
NSUInteger c;
while (a != 0) {
c = a;
a = b % a;
b = c;
}
return b;
}
static NSUInteger gcdArray(size_t const count, NSUInteger const * const values) {
if (count == 0) {
return 0;
}
NSUInteger result = values[0];
for (size_t i = 1; i < count; ++i) {
result = gcd(values[i], result);
}
return result;
}
#endif
@end

View File

@ -0,0 +1,34 @@
/*
* 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 "SDWebImageCompat.h"
@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`
/**
The image of current frame. You should not set an animated image.
*/
@property (nonatomic, strong, readonly, nonnull) UIImage *image;
/**
The duration of current frame to be displayed. The number is seconds but not milliseconds. You should not set this to zero.
*/
@property (nonatomic, readonly, assign) NSTimeInterval duration;
/**
Create a frame instance with specify image and duration
@param image current frame's image
@param duration current frame's duration
@return frame instance
*/
+ (instancetype _Nonnull)frameWithImage:(UIImage * _Nonnull)image duration:(NSTimeInterval)duration;
@end

View File

@ -0,0 +1,28 @@
/*
* 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 "SDWebImageFrame.h"
@interface SDWebImageFrame ()
@property (nonatomic, strong, readwrite, nonnull) UIImage *image;
@property (nonatomic, readwrite, assign) NSTimeInterval duration;
@end
@implementation SDWebImageFrame
+ (instancetype)frameWithImage:(UIImage *)image duration:(NSTimeInterval)duration {
SDWebImageFrame *frame = [[SDWebImageFrame alloc] init];
frame.image = image;
frame.duration = duration;
return frame;
}
@end

View File

@ -11,6 +11,7 @@
#import <ImageIO/ImageIO.h>
#import "NSData+ImageContentType.h"
#import "UIImage+MultiFormat.h"
#import "SDWebImageCoderHelper.h"
@implementation SDWebImageGIFCoder
@ -38,7 +39,9 @@
#else
CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL);
if (!source) {
return nil;
}
size_t count = CGImageSourceGetCount(source);
UIImage *animatedImage;
@ -46,17 +49,15 @@
if (count <= 1) {
animatedImage = [[UIImage alloc] initWithData:data];
} else {
NSMutableArray *images = [NSMutableArray array];
NSTimeInterval duration = 0.0f;
NSMutableArray<SDWebImageFrame *> *frames = [NSMutableArray array];
for (size_t i = 0; i < count; i++) {
CGImageRef image = CGImageSourceCreateImageAtIndex(source, i, NULL);
if (!image) {
CGImageRef imageRef = CGImageSourceCreateImageAtIndex(source, i, NULL);
if (!imageRef) {
continue;
}
duration += [self sd_frameDurationAtIndex:i source:source];
float duration = [self sd_frameDurationAtIndex:i source:source];
#if SD_WATCH
CGFloat scale = 1;
scale = [WKInterfaceDevice currentDevice].screenScale;
@ -64,17 +65,13 @@
CGFloat scale = 1;
scale = [UIScreen mainScreen].scale;
#endif
[images addObject:[UIImage imageWithCGImage:image scale:scale orientation:UIImageOrientationUp]];
UIImage *image = [UIImage imageWithCGImage:imageRef scale:scale orientation:UIImageOrientationUp];
CGImageRelease(imageRef);
CGImageRelease(image);
SDWebImageFrame *frame = [SDWebImageFrame frameWithImage:image duration:duration];
[frames addObject:frame];
}
if (!duration) {
duration = (1.0f / 10.0f) * count;
}
animatedImage = [UIImage animatedImageWithImages:images duration:duration];
NSUInteger loopCount = 0;
NSDictionary *imageProperties = (__bridge_transfer NSDictionary *)CGImageSourceCopyProperties(source, nil);
NSDictionary *gifProperties = [imageProperties valueForKey:(__bridge_transfer NSString *)kCGImagePropertyGIFDictionary];
@ -84,6 +81,8 @@
loopCount = gifLoopCount.unsignedIntegerValue;
}
}
animatedImage = [SDWebImageCoderHelper animatedImageWithFrames:frames];
animatedImage.sd_imageLoopCount = loopCount;
}
@ -144,31 +143,16 @@
}
NSMutableData *imageData = [NSMutableData data];
NSUInteger frameCount = 0; // assume static images by default
CFStringRef imageUTType = [NSData sd_UTTypeFromSDImageFormat:format];
#if SD_MAC
NSBitmapImageRep *bitmapRep;
for (NSImageRep *imageRep in image.representations) {
if ([imageRep isKindOfClass:[NSBitmapImageRep class]]) {
bitmapRep = (NSBitmapImageRep *)imageRep;
break;
}
}
if (bitmapRep) {
frameCount = [[bitmapRep valueForProperty:NSImageFrameCount] unsignedIntegerValue];
}
#else
frameCount = image.images.count;
#endif
CFStringRef imageUTType = [NSData sd_UTTypeFromSDImageFormat:SDImageFormatGIF];
NSArray<SDWebImageFrame *> *frames = [SDWebImageCoderHelper framesFromAnimatedImage:image];
// Create an image destination. GIF does not support EXIF image orientation
CGImageDestinationRef imageDestination = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)imageData, imageUTType, frameCount, NULL);
CGImageDestinationRef imageDestination = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)imageData, imageUTType, frames.count, NULL);
if (!imageDestination) {
// Handle failure.
return nil;
}
if (frameCount == 0) {
if (frames.count == 0) {
// for static single GIF images
CGImageDestinationAddImage(imageDestination, image.CGImage, nil);
} else {
@ -176,22 +160,15 @@
NSUInteger loopCount = image.sd_imageLoopCount;
NSDictionary *gifProperties = @{(__bridge_transfer NSString *)kCGImagePropertyGIFDictionary: @{(__bridge_transfer NSString *)kCGImagePropertyGIFLoopCount : @(loopCount)}};
CGImageDestinationSetProperties(imageDestination, (__bridge CFDictionaryRef)gifProperties);
for (size_t i = 0; i < frameCount; i++) {
@autoreleasepool {
#if SD_MAC
// NSBitmapImageRep need to manually change frame. "Good taste" API
[bitmapRep setProperty:NSImageCurrentFrame withValue:@(i)];
float frameDuration = [[bitmapRep valueForProperty:NSImageCurrentFrameDuration] floatValue];
CGImageRef frameImageRef = bitmapRep.CGImage;
#else
float frameDuration = image.duration / frameCount;
CGImageRef frameImageRef = image.images[i].CGImage;
#endif
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 *)kCGImagePropertyGIFDictionary : @{(__bridge_transfer NSString *)kCGImagePropertyGIFUnclampedDelayTime : @(frameDuration)}};
CGImageDestinationAddImage(imageDestination, frameImageRef, (__bridge CFDictionaryRef)frameProperties);
}
}
}
// Finalize the destination.
if (CGImageDestinationFinalize(imageDestination) == NO) {
// Handle failure.

View File

@ -7,6 +7,7 @@
*/
#import "SDWebImageImageIOCoder.h"
#import "SDWebImageCoderHelper.h"
#import "NSImage+WebCache.h"
#import <ImageIO/ImageIO.h>
#import "NSData+ImageContentType.h"
@ -145,7 +146,7 @@ static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to over
// oriented incorrectly sometimes. (Unlike the image born of initWithData
// in didCompleteWithError.) So save it here and pass it on later.
#if SD_UIKIT || SD_WATCH
_orientation = [[self class] sd_imageOrientationFromEXIFOrientation:orientationValue];
_orientation = [SDWebImageCoderHelper imageOrientationFromEXIFOrientation:orientationValue];
#endif
}
}
@ -430,8 +431,8 @@ static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to over
}
NSMutableDictionary *properties = [NSMutableDictionary dictionary];
#if SD_UIKIT
NSInteger exifOrientation = [[self class] sd_exifOrientationFromImageOrientation:image.imageOrientation];
#if SD_UIKIT || SD_WATCH
NSInteger exifOrientation = [SDWebImageCoderHelper exifOrientationFromImageOrientation:image.imageOrientation];
[properties setValue:@(exifOrientation) forKey:(__bridge_transfer NSString *)kCGImagePropertyOrientation];
#endif
@ -506,7 +507,7 @@ static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to over
val = CFDictionaryGetValue(properties, kCGImagePropertyOrientation);
if (val) {
CFNumberGetValue(val, kCFNumberNSIntegerType, &exifOrientation);
result = [self sd_imageOrientationFromEXIFOrientation:exifOrientation];
result = [SDWebImageCoderHelper imageOrientationFromEXIFOrientation:exifOrientation];
} // else - if it's not set it remains at up
CFRelease((CFTypeRef) properties);
} else {
@ -516,74 +517,6 @@ static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to over
}
return result;
}
// Convert an EXIF image orientation to an iOS one.
+ (UIImageOrientation)sd_imageOrientationFromEXIFOrientation:(NSInteger)exifOrientation {
UIImageOrientation imageOrientation = UIImageOrientationUp;
switch (exifOrientation) {
case kCGImagePropertyOrientationUp:
imageOrientation = UIImageOrientationUp;
break;
case kCGImagePropertyOrientationDown:
imageOrientation = UIImageOrientationDown;
break;
case kCGImagePropertyOrientationLeft:
imageOrientation = UIImageOrientationLeft;
break;
case kCGImagePropertyOrientationRight:
imageOrientation = UIImageOrientationRight;
break;
case kCGImagePropertyOrientationUpMirrored:
imageOrientation = UIImageOrientationUpMirrored;
break;
case kCGImagePropertyOrientationDownMirrored:
imageOrientation = UIImageOrientationDownMirrored;
break;
case kCGImagePropertyOrientationLeftMirrored:
imageOrientation = UIImageOrientationLeftMirrored;
break;
case kCGImagePropertyOrientationRightMirrored:
imageOrientation = UIImageOrientationRightMirrored;
break;
default:
break;
}
return imageOrientation;
}
// Convert an iOS orientation to an EXIF image orientation.
+ (NSInteger)sd_exifOrientationFromImageOrientation:(UIImageOrientation)imageOrientation {
NSInteger exifOrientation = kCGImagePropertyOrientationUp;
switch (imageOrientation) {
case UIImageOrientationUp:
exifOrientation = kCGImagePropertyOrientationUp;
break;
case UIImageOrientationDown:
exifOrientation = kCGImagePropertyOrientationDown;
break;
case UIImageOrientationLeft:
exifOrientation = kCGImagePropertyOrientationLeft;
break;
case UIImageOrientationRight:
exifOrientation = kCGImagePropertyOrientationRight;
break;
case UIImageOrientationUpMirrored:
exifOrientation = kCGImagePropertyOrientationUpMirrored;
break;
case UIImageOrientationDownMirrored:
exifOrientation = kCGImagePropertyOrientationDownMirrored;
break;
case UIImageOrientationLeftMirrored:
exifOrientation = kCGImagePropertyOrientationLeftMirrored;
break;
case UIImageOrientationRightMirrored:
exifOrientation = kCGImagePropertyOrientationRightMirrored;
break;
default:
break;
}
return exifOrientation;
}
#endif
#if SD_UIKIT || SD_WATCH

View File

@ -9,10 +9,9 @@
#ifdef SD_WEBP
#import "SDWebImageWebPCoder.h"
#import "SDWebImageCoderHelper.h"
#import "NSImage+WebCache.h"
#import "UIImage+MultiFormat.h"
#import "NSData+ImageContentType.h"
#import <ImageIO/ImageIO.h>
#if __has_include(<webp/decode.h>) && __has_include(<webp/encode.h>) && __has_include(<webp/demux.h>) && __has_include(<webp/mux.h>)
#import <webp/decode.h>
#import <webp/encode.h>
@ -69,10 +68,7 @@
}
uint32_t flags = WebPDemuxGetI(demuxer, WEBP_FF_FORMAT_FLAGS);
#if SD_UIKIT || SD_WATCH
int loopCount = WebPDemuxGetI(demuxer, WEBP_FF_LOOP_COUNT);
int frameCount = WebPDemuxGetI(demuxer, WEBP_FF_FRAME_COUNT);
#endif
int canvasWidth = WebPDemuxGetI(demuxer, WEBP_FF_CANVAS_WIDTH);
int canvasHeight = WebPDemuxGetI(demuxer, WEBP_FF_CANVAS_HEIGHT);
CGBitmapInfo bitmapInfo;
@ -118,41 +114,23 @@
return nil;
}
NSMutableArray<UIImage *> *images = [NSMutableArray array];
#if SD_UIKIT || SD_WATCH
NSTimeInterval totalDuration = 0;
int durations[frameCount];
#endif
NSMutableArray<SDWebImageFrame *> *frames = [NSMutableArray array];
do {
@autoreleasepool {
UIImage *image;
if (iter.blend_method == WEBP_MUX_BLEND) {
image = [self sd_blendWebpImageWithCanvas:canvas iterator:iter];
} else {
image = [self sd_nonblendWebpImageWithCanvas:canvas iterator:iter];
}
UIImage *image = [self sd_drawnWebpImageWithCanvas:canvas iterator:iter];
if (!image) {
continue;
}
[images addObject:image];
#if SD_MAC
break;
#else
int duration = iter.duration;
if (duration <= 10) {
// WebP standard says 0 duration is used for canvas updating but not showing image, but actually Chrome and other implementations set it to 100ms if duration is lower or equal than 10ms
// Some animated WebP images also created without duration, we should keep compatibility
duration = 100;
}
totalDuration += duration;
size_t count = images.count;
durations[count - 1] = duration;
#endif
SDWebImageFrame *frame = [SDWebImageFrame frameWithImage:image duration:duration / 1000.f];
[frames addObject:frame];
}
} while (WebPDemuxNextFrame(&iter));
@ -161,17 +139,10 @@
WebPDemuxDelete(demuxer);
CGContextRelease(canvas);
UIImage *finalImage = nil;
#if SD_UIKIT || SD_WATCH
NSArray<UIImage *> *animatedImages = [self sd_animatedImagesWithImages:images durations:durations totalDuration:totalDuration];
finalImage = [UIImage animatedImageWithImages:animatedImages duration:totalDuration / 1000.0];
if (finalImage) {
finalImage.sd_imageLoopCount = loopCount;
}
#elif SD_MAC
finalImage = images.firstObject;
#endif
return finalImage;
UIImage *animatedImage = [SDWebImageCoderHelper animatedImageWithFrames:frames];
animatedImage.sd_imageLoopCount = loopCount;
return animatedImage;
}
- (UIImage *)incrementallyDecodedImageWithData:(NSData *)data finished:(BOOL)finished {
@ -259,7 +230,7 @@
return image;
}
- (nullable UIImage *)sd_blendWebpImageWithCanvas:(CGContextRef)canvas iterator:(WebPIterator)iter {
- (nullable UIImage *)sd_drawnWebpImageWithCanvas:(CGContextRef)canvas iterator:(WebPIterator)iter {
UIImage *image = [self sd_rawWebpImageWithData:iter.fragment];
if (!image) {
return nil;
@ -271,39 +242,12 @@
CGFloat tmpX = iter.x_offset;
CGFloat tmpY = size.height - iter.height - iter.y_offset;
CGRect imageRect = CGRectMake(tmpX, tmpY, iter.width, iter.height);
BOOL shouldBlend = iter.blend_method == WEBP_MUX_BLEND;
CGContextDrawImage(canvas, imageRect, image.CGImage);
CGImageRef newImageRef = CGBitmapContextCreateImage(canvas);
#if SD_UIKIT || SD_WATCH
image = [UIImage imageWithCGImage:newImageRef];
#elif SD_MAC
image = [[UIImage alloc] initWithCGImage:newImageRef size:NSZeroSize];
#endif
CGImageRelease(newImageRef);
if (iter.dispose_method == WEBP_MUX_DISPOSE_BACKGROUND) {
// If not blend, cover the target image rect. (firstly clear then draw)
if (!shouldBlend) {
CGContextClearRect(canvas, imageRect);
}
return image;
}
- (nullable UIImage *)sd_nonblendWebpImageWithCanvas:(CGContextRef)canvas iterator:(WebPIterator)iter {
UIImage *image = [self sd_rawWebpImageWithData:iter.fragment];
if (!image) {
return nil;
}
size_t canvasWidth = CGBitmapContextGetWidth(canvas);
size_t canvasHeight = CGBitmapContextGetHeight(canvas);
CGSize size = CGSizeMake(canvasWidth, canvasHeight);
CGFloat tmpX = iter.x_offset;
CGFloat tmpY = size.height - iter.height - iter.y_offset;
CGRect imageRect = CGRectMake(tmpX, tmpY, iter.width, iter.height);
CGContextClearRect(canvas, imageRect);
CGContextDrawImage(canvas, imageRect, image.CGImage);
CGImageRef newImageRef = CGBitmapContextCreateImage(canvas);
@ -379,23 +323,21 @@
}
NSData *data;
#if SD_UIKIT || SD_WATCH
if (!image.images) {
#endif
NSArray<SDWebImageFrame *> *frames = [SDWebImageCoderHelper framesFromAnimatedImage:image];
if (frames.count == 0) {
// for static single webp image
data = [self sd_encodedWebpDataWithImage:image];
#if SD_UIKIT || SD_WATCH
} else {
// for animated webp image
int durations[image.images.count];
NSArray<UIImage *> *images = [self sd_imagesFromAnimatedImages:image.images totalDuration:image.duration durations:durations];
WebPMux *mux = WebPMuxNew();
if (!mux) {
return nil;
}
for (NSUInteger i = 0; i < images.count; i++) {
NSData *webpData = [self sd_encodedWebpDataWithImage:images[i]];
int duration = durations[i];
for (size_t i = 0; i < frames.count; i++) {
SDWebImageFrame *currentFrame = frames[i];
NSData *webpData = [self sd_encodedWebpDataWithImage:currentFrame.image];
int duration = currentFrame.duration * 1000;
WebPMuxFrameInfo frame = { .bitstream.bytes = webpData.bytes,
.bitstream.size = webpData.length,
.duration = duration,
@ -427,7 +369,6 @@
data = [NSData dataWithBytes:outputData.bytes length:outputData.size];
WebPDataClear(&outputData);
}
#endif
return data;
}
@ -471,99 +412,10 @@
return webpData;
}
- (NSArray<UIImage *> *)sd_animatedImagesWithImages:(NSArray<UIImage *> *)images durations:(int const * const)durations totalDuration:(NSTimeInterval)totalDuration
{
// [UIImage animatedImageWithImages:duration:] only use the average duration for per frame
// divide the total duration to implement per frame duration for animated WebP
NSUInteger count = images.count;
if (!count) {
return nil;
}
if (count == 1) {
return images;
}
int const gcd = gcdArray(count, durations);
NSMutableArray<UIImage *> *animatedImages = [NSMutableArray arrayWithCapacity:count];
[images enumerateObjectsUsingBlock:^(UIImage * _Nonnull image, NSUInteger idx, BOOL * _Nonnull stop) {
int duration = durations[idx];
int repeatCount;
if (gcd) {
repeatCount = duration / gcd;
} else {
repeatCount = 1;
}
for (int i = 0; i < repeatCount; ++i) {
[animatedImages addObject:image];
}
}];
return animatedImages;
}
- (NSArray<UIImage *> *)sd_imagesFromAnimatedImages:(NSArray<UIImage *> *)animatedImages totalDuration:(NSTimeInterval)totalDuration durations:(int * const)durations {
// This is the reversed procedure to sd_animatedImagesWithImages:durations:totalDuration
// To avoid precision loss, convert from s to ms during this method
NSUInteger count = animatedImages.count;
if (!count) {
return nil;
}
if (count == 1) {
durations[0] = totalDuration * 1000; // s -> ms
}
int const duration = totalDuration * 1000 / count;
__block NSUInteger index = 0;
__block int repeatCount = 1;
__block UIImage *previousImage = animatedImages.firstObject;
NSMutableArray<UIImage *> *images = [NSMutableArray array];
[animatedImages enumerateObjectsUsingBlock:^(UIImage * _Nonnull image, NSUInteger idx, BOOL * _Nonnull stop) {
// ignore first
if (idx == 0) {
return;
}
if ([image isEqual:previousImage]) {
repeatCount++;
} else {
[images addObject:previousImage];
durations[index] = duration * repeatCount;
repeatCount = 1;
index++;
}
previousImage = image;
// last one
if (idx == count - 1) {
[images addObject:previousImage];
durations[index] = duration * repeatCount;
}
}];
return images;
}
static void FreeImageData(void *info, const void *data, size_t size) {
free((void *)data);
}
static int gcdArray(size_t const count, int const * const values) {
int result = values[0];
for (size_t i = 1; i < count; ++i) {
result = gcd(values[i], result);
}
return result;
}
static int gcd(int a,int b) {
int c;
while (a != 0) {
c = a;
a = b % a;
b = c;
}
return b;
}
@end
#endif

View File

@ -11,12 +11,15 @@
@interface UIImage (MultiFormat)
/**
* UIKit:
* For static image format, this value is always 0.
* For animated image format, 0 means infinite looping.
* Note that because of the limitations of categories this property can get out of sync
* if you create another instance with CGImage or other methods.
* Note that because of the limitations of categories this property can get out of sync if you create another instance with CGImage or other methods.
* AppKit:
* NSImage currently only support animated via GIF imageRep unlike UIImage.
* The getter of this property will get the loop count from GIF imageRep
* The setter of this property will set the loop count from GIF imageRep
*/
@property (nonatomic, assign) NSUInteger sd_imageLoopCount;

View File

@ -13,6 +13,31 @@
@implementation UIImage (MultiFormat)
#if SD_MAC
- (NSUInteger)sd_imageLoopCount {
NSUInteger imageLoopCount = 0;
for (NSImageRep *rep in self.representations) {
if ([rep isKindOfClass:[NSBitmapImageRep class]]) {
NSBitmapImageRep *bitmapRep = (NSBitmapImageRep *)rep;
imageLoopCount = [[bitmapRep valueForProperty:NSImageLoopCount] unsignedIntegerValue];
break;
}
}
return imageLoopCount;
}
- (void)setSd_imageLoopCount:(NSUInteger)sd_imageLoopCount {
for (NSImageRep *rep in self.representations) {
if ([rep isKindOfClass:[NSBitmapImageRep class]]) {
NSBitmapImageRep *bitmapRep = (NSBitmapImageRep *)rep;
[bitmapRep setProperty:NSImageLoopCount withValue:@(sd_imageLoopCount)];
break;
}
}
}
#else
- (NSUInteger)sd_imageLoopCount {
NSUInteger imageLoopCount = 0;
NSNumber *value = objc_getAssociatedObject(self, @selector(sd_imageLoopCount));
@ -26,6 +51,7 @@
NSNumber *value = @(sd_imageLoopCount);
objc_setAssociatedObject(self, @selector(sd_imageLoopCount), value, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
#endif
+ (nullable UIImage *)sd_imageWithData:(nullable NSData *)data {
return [[SDWebImageCodersManager sharedInstance] decodedImageWithData:data];

View File

@ -44,6 +44,8 @@ FOUNDATION_EXPORT const unsigned char WebImageVersionString[];
#import <SDWebImage/SDWebImageWebPCoder.h>
#import <SDWebImage/SDWebImageGIFCoder.h>
#import <SDWebImage/SDWebImageImageIOCoder.h>
#import <SDWebImage/SDWebImageFrame.h>
#import <SDWebImage/SDWebImageCoderHelper.h>
#import <SDWebImage/UIImage+WebP.h>
#import <SDWebImage/UIImage+GIF.h>
#import <SDWebImage/UIImage+ForceDecode.h>