From 19f45a3cf4c0a3045db648e2c2f79e96e2142093 Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Wed, 1 Nov 2017 02:34:06 +0800 Subject: [PATCH] Feature refactor built-in coders and support animated webp on macOS (#2082) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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 --- Examples/SDWebImage OSX Demo/ViewController.m | 1 + SDWebImage.xcodeproj/project.pbxproj | 56 ++++ SDWebImage/NSImage+WebCache.m | 6 +- SDWebImage/SDWebImageCoderHelper.h | 52 ++++ SDWebImage/SDWebImageCoderHelper.m | 255 ++++++++++++++++++ SDWebImage/SDWebImageFrame.h | 34 +++ SDWebImage/SDWebImageFrame.m | 28 ++ SDWebImage/SDWebImageGIFCoder.m | 73 ++--- SDWebImage/SDWebImageImageIOCoder.m | 77 +----- SDWebImage/SDWebImageWebPCoder.m | 188 ++----------- SDWebImage/UIImage+MultiFormat.h | 9 +- SDWebImage/UIImage+MultiFormat.m | 26 ++ WebImage/SDWebImage.h | 2 + 13 files changed, 512 insertions(+), 295 deletions(-) create mode 100644 SDWebImage/SDWebImageCoderHelper.h create mode 100644 SDWebImage/SDWebImageCoderHelper.m create mode 100644 SDWebImage/SDWebImageFrame.h create mode 100644 SDWebImage/SDWebImageFrame.m diff --git a/Examples/SDWebImage OSX Demo/ViewController.m b/Examples/SDWebImage OSX Demo/ViewController.m index 15509519..2d7e8774 100644 --- a/Examples/SDWebImage OSX Demo/ViewController.m +++ b/Examples/SDWebImage OSX Demo/ViewController.m @@ -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"]]; diff --git a/SDWebImage.xcodeproj/project.pbxproj b/SDWebImage.xcodeproj/project.pbxproj index 9682a27c..fac78404 100644 --- a/SDWebImage.xcodeproj/project.pbxproj +++ b/SDWebImage.xcodeproj/project.pbxproj @@ -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 = ""; }; 323F8B3C1F38EF770092B609 /* muxinternal.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = muxinternal.c; sourceTree = ""; }; 323F8B3D1F38EF770092B609 /* muxread.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = muxread.c; sourceTree = ""; }; + 3290FA021FA478AF0047D20C /* SDWebImageFrame.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDWebImageFrame.h; sourceTree = ""; }; + 3290FA031FA478AF0047D20C /* SDWebImageFrame.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDWebImageFrame.m; sourceTree = ""; }; + 32CF1C051FA496B000004BD1 /* SDWebImageCoderHelper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDWebImageCoderHelper.h; sourceTree = ""; }; + 32CF1C061FA496B000004BD1 /* SDWebImageCoderHelper.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDWebImageCoderHelper.m; sourceTree = ""; }; 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 = ""; }; @@ -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 = ""; @@ -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 */, diff --git a/SDWebImage/NSImage+WebCache.m b/SDWebImage/NSImage+WebCache.m index c5a3b8f5..140ed6ce 100644 --- a/SDWebImage/NSImage+WebCache.m +++ b/SDWebImage/NSImage+WebCache.m @@ -28,10 +28,8 @@ if ([rep isKindOfClass:[NSBitmapImageRep class]]) { NSBitmapImageRep *bitmapRep = (NSBitmapImageRep *)rep; NSUInteger frameCount = [[bitmapRep valueForProperty:NSImageFrameCount] unsignedIntegerValue]; - if (frameCount > 1) { - isGIF = YES; - break; - } + isGIF = frameCount > 1 ? YES : NO; + break; } } return isGIF; diff --git a/SDWebImage/SDWebImageCoderHelper.h b/SDWebImage/SDWebImageCoderHelper.h new file mode 100644 index 00000000..cdafd88d --- /dev/null +++ b/SDWebImage/SDWebImageCoderHelper.h @@ -0,0 +1,52 @@ +/* + * 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 +#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 * _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 * _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 diff --git a/SDWebImage/SDWebImageCoderHelper.m b/SDWebImage/SDWebImageCoderHelper.m new file mode 100644 index 00000000..b2b651a2 --- /dev/null +++ b/SDWebImage/SDWebImageCoderHelper.m @@ -0,0 +1,255 @@ +/* + * 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 "SDWebImageCoderHelper.h" +#import "SDWebImageFrame.h" +#import "UIImage+MultiFormat.h" +#import "NSImage+WebCache.h" +#import + +@implementation SDWebImageCoderHelper + ++ (UIImage *)animatedImageWithFrames:(NSArray *)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 *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 *)framesFromAnimatedImage:(UIImage *)animatedImage { + if (!animatedImage) { + return nil; + } + + NSMutableArray *frames = [NSMutableArray array]; + NSUInteger frameCount = 0; + +#if SD_UIKIT || SD_WATCH + NSArray *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 diff --git a/SDWebImage/SDWebImageFrame.h b/SDWebImage/SDWebImageFrame.h new file mode 100644 index 00000000..d8ba1812 --- /dev/null +++ b/SDWebImage/SDWebImageFrame.h @@ -0,0 +1,34 @@ +/* + * 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 +#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 diff --git a/SDWebImage/SDWebImageFrame.m b/SDWebImage/SDWebImageFrame.m new file mode 100644 index 00000000..b0aefe54 --- /dev/null +++ b/SDWebImage/SDWebImageFrame.m @@ -0,0 +1,28 @@ +/* + * 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 "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 diff --git a/SDWebImage/SDWebImageGIFCoder.m b/SDWebImage/SDWebImageGIFCoder.m index 7ad61595..005f2b89 100644 --- a/SDWebImage/SDWebImageGIFCoder.m +++ b/SDWebImage/SDWebImageGIFCoder.m @@ -11,6 +11,7 @@ #import #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 *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 *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,20 +160,13 @@ 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 - NSDictionary *frameProperties = @{(__bridge_transfer NSString *)kCGImagePropertyGIFDictionary : @{(__bridge_transfer NSString *)kCGImagePropertyGIFUnclampedDelayTime : @(frameDuration)}}; - CGImageDestinationAddImage(imageDestination, frameImageRef, (__bridge CFDictionaryRef)frameProperties); - } + + 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. diff --git a/SDWebImage/SDWebImageImageIOCoder.m b/SDWebImage/SDWebImageImageIOCoder.m index 7d565648..4e549ebe 100644 --- a/SDWebImage/SDWebImageImageIOCoder.m +++ b/SDWebImage/SDWebImageImageIOCoder.m @@ -7,6 +7,7 @@ */ #import "SDWebImageImageIOCoder.h" +#import "SDWebImageCoderHelper.h" #import "NSImage+WebCache.h" #import #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 diff --git a/SDWebImage/SDWebImageWebPCoder.m b/SDWebImage/SDWebImageWebPCoder.m index 3bcc0524..7dbe1afa 100644 --- a/SDWebImage/SDWebImageWebPCoder.m +++ b/SDWebImage/SDWebImageWebPCoder.m @@ -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 #if __has_include() && __has_include() && __has_include() && __has_include() #import #import @@ -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 *images = [NSMutableArray array]; -#if SD_UIKIT || SD_WATCH - NSTimeInterval totalDuration = 0; - int durations[frameCount]; -#endif + NSMutableArray *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 *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 *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 *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 *)sd_animatedImagesWithImages:(NSArray *)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 *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 *)sd_imagesFromAnimatedImages:(NSArray *)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 *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 diff --git a/SDWebImage/UIImage+MultiFormat.h b/SDWebImage/UIImage+MultiFormat.h index bde5ac02..c0792d1b 100644 --- a/SDWebImage/UIImage+MultiFormat.h +++ b/SDWebImage/UIImage+MultiFormat.h @@ -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; diff --git a/SDWebImage/UIImage+MultiFormat.m b/SDWebImage/UIImage+MultiFormat.m index 771553f8..664e0969 100644 --- a/SDWebImage/UIImage+MultiFormat.m +++ b/SDWebImage/UIImage+MultiFormat.m @@ -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]; diff --git a/WebImage/SDWebImage.h b/WebImage/SDWebImage.h index df1d6b70..df3d176d 100644 --- a/WebImage/SDWebImage.h +++ b/WebImage/SDWebImage.h @@ -44,6 +44,8 @@ FOUNDATION_EXPORT const unsigned char WebImageVersionString[]; #import #import #import +#import +#import #import #import #import