From b4ea87f6c5d5238befa0f07a5e32ddf492ff47ed Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Sun, 11 Feb 2018 23:36:58 +0800 Subject: [PATCH 01/13] Add image transformer protocol and class. Add UIImage+Transformer category including common image geometry, tinting, blur effect processor. --- SDWebImage.xcodeproj/project.pbxproj | 56 +++ SDWebImage/SDWebImageCompat.h | 3 + SDWebImage/SDWebImageTransformer.h | 139 ++++++++ SDWebImage/SDWebImageTransformer.m | 309 ++++++++++++++++ SDWebImage/UIImage+Transform.h | 126 +++++++ SDWebImage/UIImage+Transform.m | 505 +++++++++++++++++++++++++++ 6 files changed, 1138 insertions(+) create mode 100644 SDWebImage/SDWebImageTransformer.h create mode 100644 SDWebImage/SDWebImageTransformer.m create mode 100644 SDWebImage/UIImage+Transform.h create mode 100644 SDWebImage/UIImage+Transform.m diff --git a/SDWebImage.xcodeproj/project.pbxproj b/SDWebImage.xcodeproj/project.pbxproj index 21cf1e9d..1ff4efb0 100644 --- a/SDWebImage.xcodeproj/project.pbxproj +++ b/SDWebImage.xcodeproj/project.pbxproj @@ -388,6 +388,30 @@ 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 */; }; + 32F7C06F2030114C00873181 /* SDWebImageTransformer.h in Headers */ = {isa = PBXBuildFile; fileRef = 32F7C06D2030114C00873181 /* SDWebImageTransformer.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 32F7C0702030114C00873181 /* SDWebImageTransformer.h in Headers */ = {isa = PBXBuildFile; fileRef = 32F7C06D2030114C00873181 /* SDWebImageTransformer.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 32F7C0712030114C00873181 /* SDWebImageTransformer.h in Headers */ = {isa = PBXBuildFile; fileRef = 32F7C06D2030114C00873181 /* SDWebImageTransformer.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 32F7C0722030114C00873181 /* SDWebImageTransformer.h in Headers */ = {isa = PBXBuildFile; fileRef = 32F7C06D2030114C00873181 /* SDWebImageTransformer.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 32F7C0732030114C00873181 /* SDWebImageTransformer.h in Headers */ = {isa = PBXBuildFile; fileRef = 32F7C06D2030114C00873181 /* SDWebImageTransformer.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 32F7C0742030114C00873181 /* SDWebImageTransformer.h in Headers */ = {isa = PBXBuildFile; fileRef = 32F7C06D2030114C00873181 /* SDWebImageTransformer.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 32F7C0752030114C00873181 /* SDWebImageTransformer.m in Sources */ = {isa = PBXBuildFile; fileRef = 32F7C06E2030114C00873181 /* SDWebImageTransformer.m */; }; + 32F7C0762030114C00873181 /* SDWebImageTransformer.m in Sources */ = {isa = PBXBuildFile; fileRef = 32F7C06E2030114C00873181 /* SDWebImageTransformer.m */; }; + 32F7C0772030114C00873181 /* SDWebImageTransformer.m in Sources */ = {isa = PBXBuildFile; fileRef = 32F7C06E2030114C00873181 /* SDWebImageTransformer.m */; }; + 32F7C0782030114C00873181 /* SDWebImageTransformer.m in Sources */ = {isa = PBXBuildFile; fileRef = 32F7C06E2030114C00873181 /* SDWebImageTransformer.m */; }; + 32F7C0792030114C00873181 /* SDWebImageTransformer.m in Sources */ = {isa = PBXBuildFile; fileRef = 32F7C06E2030114C00873181 /* SDWebImageTransformer.m */; }; + 32F7C07A2030114C00873181 /* SDWebImageTransformer.m in Sources */ = {isa = PBXBuildFile; fileRef = 32F7C06E2030114C00873181 /* SDWebImageTransformer.m */; }; + 32F7C07E2030719600873181 /* UIImage+Transform.m in Sources */ = {isa = PBXBuildFile; fileRef = 32F7C07C2030719600873181 /* UIImage+Transform.m */; }; + 32F7C07F2030719600873181 /* UIImage+Transform.m in Sources */ = {isa = PBXBuildFile; fileRef = 32F7C07C2030719600873181 /* UIImage+Transform.m */; }; + 32F7C0802030719600873181 /* UIImage+Transform.m in Sources */ = {isa = PBXBuildFile; fileRef = 32F7C07C2030719600873181 /* UIImage+Transform.m */; }; + 32F7C0812030719600873181 /* UIImage+Transform.m in Sources */ = {isa = PBXBuildFile; fileRef = 32F7C07C2030719600873181 /* UIImage+Transform.m */; }; + 32F7C0822030719600873181 /* UIImage+Transform.m in Sources */ = {isa = PBXBuildFile; fileRef = 32F7C07C2030719600873181 /* UIImage+Transform.m */; }; + 32F7C0832030719600873181 /* UIImage+Transform.m in Sources */ = {isa = PBXBuildFile; fileRef = 32F7C07C2030719600873181 /* UIImage+Transform.m */; }; + 32F7C0842030719600873181 /* UIImage+Transform.h in Headers */ = {isa = PBXBuildFile; fileRef = 32F7C07D2030719600873181 /* UIImage+Transform.h */; }; + 32F7C0852030719600873181 /* UIImage+Transform.h in Headers */ = {isa = PBXBuildFile; fileRef = 32F7C07D2030719600873181 /* UIImage+Transform.h */; }; + 32F7C0862030719600873181 /* UIImage+Transform.h in Headers */ = {isa = PBXBuildFile; fileRef = 32F7C07D2030719600873181 /* UIImage+Transform.h */; }; + 32F7C0872030719600873181 /* UIImage+Transform.h in Headers */ = {isa = PBXBuildFile; fileRef = 32F7C07D2030719600873181 /* UIImage+Transform.h */; }; + 32F7C0882030719600873181 /* UIImage+Transform.h in Headers */ = {isa = PBXBuildFile; fileRef = 32F7C07D2030719600873181 /* UIImage+Transform.h */; }; + 32F7C0892030719600873181 /* UIImage+Transform.h in Headers */ = {isa = PBXBuildFile; fileRef = 32F7C07D2030719600873181 /* UIImage+Transform.h */; }; 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 */; }; @@ -1391,6 +1415,10 @@ 32C0FDE02013426C001B8F2D /* SDWebImageIndicator.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDWebImageIndicator.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 = ""; }; + 32F7C06D2030114C00873181 /* SDWebImageTransformer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDWebImageTransformer.h; sourceTree = ""; }; + 32F7C06E2030114C00873181 /* SDWebImageTransformer.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDWebImageTransformer.m; sourceTree = ""; }; + 32F7C07C2030719600873181 /* UIImage+Transform.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIImage+Transform.m"; sourceTree = ""; }; + 32F7C07D2030719600873181 /* UIImage+Transform.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIImage+Transform.h"; 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 = ""; }; @@ -1835,6 +1863,8 @@ 53EDFB921762547C00698166 /* UIImage+WebP.m */, 321E60BC1F38E91700405457 /* UIImage+ForceDecode.h */, 321E60BD1F38E91700405457 /* UIImage+ForceDecode.m */, + 32F7C07D2030719600873181 /* UIImage+Transform.h */, + 32F7C07C2030719600873181 /* UIImage+Transform.m */, 4397D2F41D0DE2DF00BB2784 /* NSImage+Additions.h */, 4397D2F51D0DE2DF00BB2784 /* NSImage+Additions.m */, AB615301192DA24600A2D8E9 /* UIView+WebCacheOperation.h */, @@ -1878,6 +1908,8 @@ 325312C7200F09910046BF1E /* SDWebImageTransition.m */, 32C0FDDF2013426C001B8F2D /* SDWebImageIndicator.h */, 32C0FDE02013426C001B8F2D /* SDWebImageIndicator.m */, + 32F7C06D2030114C00873181 /* SDWebImageTransformer.h */, + 32F7C06E2030114C00873181 /* SDWebImageTransformer.m */, ); name = Utils; sourceTree = ""; @@ -2125,11 +2157,13 @@ 80377C5F1F2F666300F89830 /* utils.h in Headers */, 80377C5B1F2F666300F89830 /* rescaler_utils.h in Headers */, 323F8BF91F38EF770092B609 /* animi.h in Headers */, + 32F7C0872030719600873181 /* UIImage+Transform.h in Headers */, 80377C4F1F2F666300F89830 /* filters_utils.h in Headers */, 80377C4C1F2F666300F89830 /* color_cache_utils.h in Headers */, 32C0FDE42013426C001B8F2D /* SDWebImageIndicator.h in Headers */, 321E60C11F38E91700405457 /* UIImage+ForceDecode.h in Headers */, 80377DBE1F2F66A700F89830 /* dsp.h in Headers */, + 32F7C0722030114C00873181 /* SDWebImageTransformer.h in Headers */, 80377EB81F2F66D400F89830 /* alphai_dec.h in Headers */, 00733A6D1BC4880E00A5A117 /* UIImage+GIF.h in Headers */, 80377C551F2F666300F89830 /* quant_levels_dec_utils.h in Headers */, @@ -2152,8 +2186,10 @@ 321E60BF1F38E91700405457 /* UIImage+ForceDecode.h in Headers */, 80377EA61F2F66D400F89830 /* webpi_dec.h in Headers */, 807A12291F89636300EC2A9B /* SDWebImageCodersManager.h in Headers */, + 32F7C0852030719600873181 /* UIImage+Transform.h in Headers */, 80377C141F2F666300F89830 /* bit_reader_utils.h in Headers */, 323F8C0F1F38EF770092B609 /* muxi.h in Headers */, + 32F7C0702030114C00873181 /* SDWebImageTransformer.h in Headers */, 80377C2B1F2F666300F89830 /* utils.h in Headers */, 4314D1621D0E0E3B004B36C9 /* mux_types.h in Headers */, 4314D1631D0E0E3B004B36C9 /* demux.h in Headers */, @@ -2255,6 +2291,7 @@ 80377E211F2F66A800F89830 /* neon.h in Headers */, 80377C711F2F666400F89830 /* quant_levels_utils.h in Headers */, 323F8B541F38EF770092B609 /* backward_references_enc.h in Headers */, + 32F7C0882030719600873181 /* UIImage+Transform.h in Headers */, 43A62A1F1D0E0A800089D7DD /* mux.h in Headers */, 431BB6E91D06D2C1006A3455 /* SDWebImageDownloaderOperation.h in Headers */, 80377ED41F2F66D500F89830 /* vp8li_dec.h in Headers */, @@ -2289,6 +2326,7 @@ 323F8BFA1F38EF770092B609 /* animi.h in Headers */, 431BB6F91D06D2C1006A3455 /* UIImage+GIF.h in Headers */, 321E60B41F38E90100405457 /* SDWebImageWebPCoder.h in Headers */, + 32F7C0732030114C00873181 /* SDWebImageTransformer.h in Headers */, 431BB6FA1D06D2C1006A3455 /* SDWebImageDownloader.h in Headers */, 80377DF51F2F66A800F89830 /* common_sse2.h in Headers */, 323F8BDC1F38EF770092B609 /* vp8i_enc.h in Headers */, @@ -2305,6 +2343,7 @@ 80377ED81F2F66D500F89830 /* alphai_dec.h in Headers */, 321E60A71F38E8F600405457 /* SDWebImageGIFCoder.h in Headers */, 324DF4B9200A14DC008A84CC /* SDWebImageDefine.h in Headers */, + 32F7C0892030719600873181 /* UIImage+Transform.h in Headers */, 80377EDA1F2F66D500F89830 /* common_dec.h in Headers */, 80377EE61F2F66D500F89830 /* webpi_dec.h in Headers */, 4397D2BA1D0DDD8C00BB2784 /* demux.h in Headers */, @@ -2340,6 +2379,7 @@ 32CF1C0C1FA496B000004BD1 /* SDWebImageCoderHelper.h in Headers */, 43A918691D8308FE00B3925F /* SDImageCacheConfig.h in Headers */, 4397D2D81D0DDD8C00BB2784 /* UIButton+WebCache.h in Headers */, + 32F7C0742030114C00873181 /* SDWebImageTransformer.h in Headers */, 80377E641F2F66A800F89830 /* mips_macro.h in Headers */, 323F8BDD1F38EF770092B609 /* vp8i_enc.h in Headers */, 323F8B671F38EF770092B609 /* cost_enc.h in Headers */, @@ -2452,11 +2492,13 @@ 4A2CAE311AB4BB7500B6BC39 /* UIImage+WebP.h in Headers */, 323F8BF81F38EF770092B609 /* animi.h in Headers */, 80377C351F2F666300F89830 /* filters_utils.h in Headers */, + 32F7C0862030719600873181 /* UIImage+Transform.h in Headers */, 80377C321F2F666300F89830 /* color_cache_utils.h in Headers */, 321E60C01F38E91700405457 /* UIImage+ForceDecode.h in Headers */, 32C0FDE32013426C001B8F2D /* SDWebImageIndicator.h in Headers */, 80377D791F2F66A700F89830 /* dsp.h in Headers */, 80377EA81F2F66D400F89830 /* alphai_dec.h in Headers */, + 32F7C0712030114C00873181 /* SDWebImageTransformer.h in Headers */, 4A2CAE2D1AB4BB7500B6BC39 /* UIImage+GIF.h in Headers */, 80377C3B1F2F666300F89830 /* quant_levels_dec_utils.h in Headers */, 80377EB11F2F66D400F89830 /* vp8_dec.h in Headers */, @@ -2473,6 +2515,7 @@ 431738BE1CDFC2660008FEB9 /* demux.h in Headers */, 80377BFC1F2F665300F89830 /* bit_writer_utils.h in Headers */, 32CF1C071FA496B000004BD1 /* SDWebImageCoderHelper.h in Headers */, + 32F7C0842030719600873181 /* UIImage+Transform.h in Headers */, 431738BF1CDFC2660008FEB9 /* encode.h in Headers */, 53761316155AD0D5005750A4 /* SDImageCache.h in Headers */, 323F8C0E1F38EF770092B609 /* muxi.h in Headers */, @@ -2507,6 +2550,7 @@ 80377C0F1F2F665300F89830 /* thread_utils.h in Headers */, 321E60BE1F38E91700405457 /* UIImage+ForceDecode.h in Headers */, 5376131E155AD0D5005750A4 /* SDWebImagePrefetcher.h in Headers */, + 32F7C06F2030114C00873181 /* SDWebImageTransformer.h in Headers */, 324DF4B4200A14DC008A84CC /* SDWebImageDefine.h in Headers */, 80377CE11F2F66A100F89830 /* common_sse2.h in Headers */, 80377C0B1F2F665300F89830 /* random_utils.h in Headers */, @@ -2796,6 +2840,7 @@ 80377DAE1F2F66A700F89830 /* argb_sse2.c in Sources */, 323F8B7D1F38EF770092B609 /* frame_enc.c in Sources */, 80377EBB1F2F66D500F89830 /* frame_dec.c in Sources */, + 32F7C0782030114C00873181 /* SDWebImageTransformer.m in Sources */, 80377DAF1F2F66A700F89830 /* argb.c in Sources */, 323F8B6B1F38EF770092B609 /* delta_palettization_enc.c in Sources */, 00733A5B1BC4880000A5A117 /* NSData+ImageContentType.m in Sources */, @@ -2828,6 +2873,7 @@ 80377DE31F2F66A700F89830 /* upsampling_mips_dsp_r2.c in Sources */, 80377EBD1F2F66D500F89830 /* io_dec.c in Sources */, 80377EBC1F2F66D500F89830 /* idec_dec.c in Sources */, + 32F7C0812030719600873181 /* UIImage+Transform.m in Sources */, 323F8B991F38EF770092B609 /* near_lossless_enc.c in Sources */, 80377DE81F2F66A700F89830 /* yuv_mips_dsp_r2.c in Sources */, 80377EC31F2F66D500F89830 /* vp8l_dec.c in Sources */, @@ -3016,6 +3062,7 @@ 3237F9EC20161AE000A88143 /* NSImage+Additions.m in Sources */, 4314D1411D0E0E3B004B36C9 /* SDWebImageDownloaderOperation.m in Sources */, 80377D561F2F66A700F89830 /* rescaler_neon.c in Sources */, + 32F7C0762030114C00873181 /* SDWebImageTransformer.m in Sources */, 80377D551F2F66A700F89830 /* rescaler_msa.c in Sources */, 80377D5E1F2F66A700F89830 /* yuv_mips_dsp_r2.c in Sources */, 80377D611F2F66A700F89830 /* yuv.c in Sources */, @@ -3056,6 +3103,7 @@ 4314D1551D0E0E3B004B36C9 /* UIImageView+HighlightedWebCache.m in Sources */, 80377E991F2F66D400F89830 /* buffer_dec.c in Sources */, 80377C201F2F666300F89830 /* quant_levels_dec_utils.c in Sources */, + 32F7C07F2030719600873181 /* UIImage+Transform.m in Sources */, 80377D471F2F66A700F89830 /* lossless_enc_sse2.c in Sources */, 80377C1E1F2F666300F89830 /* huffman_utils.c in Sources */, ); @@ -3164,6 +3212,7 @@ 3237F9EA20161AE000A88143 /* NSImage+Additions.m in Sources */, 431BB6B61D06D2C1006A3455 /* UIImage+WebP.m in Sources */, 80377E251F2F66A800F89830 /* rescaler_neon.c in Sources */, + 32F7C0792030114C00873181 /* SDWebImageTransformer.m in Sources */, 80377E241F2F66A800F89830 /* rescaler_msa.c in Sources */, 80377E2D1F2F66A800F89830 /* yuv_mips_dsp_r2.c in Sources */, 431BB6B91D06D2C1006A3455 /* UIButton+WebCache.m in Sources */, @@ -3204,6 +3253,7 @@ 80377C6E1F2F666400F89830 /* quant_levels_dec_utils.c in Sources */, 80377EC91F2F66D500F89830 /* buffer_dec.c in Sources */, 80377C6C1F2F666400F89830 /* huffman_utils.c in Sources */, + 32F7C0822030719600873181 /* UIImage+Transform.m in Sources */, 80377E161F2F66A800F89830 /* lossless_enc_sse2.c in Sources */, 431BB6C71D06D2C1006A3455 /* UIImageView+HighlightedWebCache.m in Sources */, ); @@ -3348,6 +3398,7 @@ 32C0FDEC2013426C001B8F2D /* SDWebImageIndicator.m in Sources */, 80377E5E1F2F66A800F89830 /* lossless_mips_dsp_r2.c in Sources */, 80377E3D1F2F66A800F89830 /* cost_sse2.c in Sources */, + 32F7C0832030719600873181 /* UIImage+Transform.m in Sources */, 321E60BB1F38E90100405457 /* SDWebImageWebPCoder.m in Sources */, 80377E3C1F2F66A800F89830 /* cost_mips32.c in Sources */, 80377E421F2F66A800F89830 /* dec_mips32.c in Sources */, @@ -3355,6 +3406,7 @@ 323F8B851F38EF770092B609 /* histogram_enc.c in Sources */, 80377EE51F2F66D500F89830 /* webp_dec.c in Sources */, 4397D2B01D0DDD8C00BB2784 /* SDImageCache.m in Sources */, + 32F7C07A2030114C00873181 /* SDWebImageTransformer.m in Sources */, 80377E4F1F2F66A800F89830 /* enc_sse41.c in Sources */, 80377E701F2F66A800F89830 /* upsampling_sse2.c in Sources */, ); @@ -3395,6 +3447,7 @@ 80377D6A1F2F66A700F89830 /* argb.c in Sources */, 323F8B7C1F38EF770092B609 /* frame_enc.c in Sources */, 80377EAB1F2F66D400F89830 /* frame_dec.c in Sources */, + 32F7C0772030114C00873181 /* SDWebImageTransformer.m in Sources */, 43C8929D1D9D6DD90022038D /* anim_decode.c in Sources */, 323F8B6A1F38EF770092B609 /* delta_palettization_enc.c in Sources */, 43CE75D41CFE98E0006C64D0 /* FLAnimatedImageView+WebCache.m in Sources */, @@ -3427,6 +3480,7 @@ 4A2CAE1E1AB4BB6800B6BC39 /* SDWebImageDownloaderOperation.m in Sources */, 80377EAD1F2F66D400F89830 /* io_dec.c in Sources */, 80377EAC1F2F66D400F89830 /* idec_dec.c in Sources */, + 32F7C0802030719600873181 /* UIImage+Transform.m in Sources */, 323F8B981F38EF770092B609 /* near_lossless_enc.c in Sources */, 80377D6F1F2F66A700F89830 /* cost.c in Sources */, 80377EB31F2F66D400F89830 /* vp8l_dec.c in Sources */, @@ -3547,6 +3601,7 @@ 323F8B7A1F38EF770092B609 /* frame_enc.c in Sources */, 80377E8B1F2F66D000F89830 /* frame_dec.c in Sources */, 43C8929A1D9D6DD70022038D /* anim_decode.c in Sources */, + 32F7C0752030114C00873181 /* SDWebImageTransformer.m in Sources */, 323F8B681F38EF770092B609 /* delta_palettization_enc.c in Sources */, 43CE75D31CFE98E0006C64D0 /* FLAnimatedImageView+WebCache.m in Sources */, 323F8B5C1F38EF770092B609 /* cost_enc.c in Sources */, @@ -3579,6 +3634,7 @@ 80377E8D1F2F66D000F89830 /* io_dec.c in Sources */, 80377E8C1F2F66D000F89830 /* idec_dec.c in Sources */, 323F8B961F38EF770092B609 /* near_lossless_enc.c in Sources */, + 32F7C07E2030719600873181 /* UIImage+Transform.m in Sources */, 80377CE51F2F66A100F89830 /* cost.c in Sources */, 80377E931F2F66D000F89830 /* vp8l_dec.c in Sources */, 321E609A1F38E8ED00405457 /* SDWebImageImageIOCoder.m in Sources */, diff --git a/SDWebImage/SDWebImageCompat.h b/SDWebImage/SDWebImageCompat.h index adbc64d2..87142799 100644 --- a/SDWebImage/SDWebImageCompat.h +++ b/SDWebImage/SDWebImageCompat.h @@ -59,6 +59,9 @@ #ifndef UIView #define UIView NSView #endif + #ifndef UIColor + #define UIColor NSColor + #endif #else #if __IPHONE_OS_VERSION_MIN_REQUIRED != 20000 && __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_5_0 #error SDWebImage doesn't support Deployment Target version < 5.0 diff --git a/SDWebImage/SDWebImageTransformer.h b/SDWebImage/SDWebImageTransformer.h new file mode 100644 index 00000000..d54bff24 --- /dev/null +++ b/SDWebImage/SDWebImageTransformer.h @@ -0,0 +1,139 @@ +/* + * This file is part of the SDWebImage package. + * (c) Olivier Poitrey + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +#import "SDWebImageCompat.h" +#import "UIImage+Transform.h" + +/** + A transformer protocol to transform the image load from cache or from download. + For cache, it store the transformed image to memory cache if the memory cache missed (Default). + For download, it store the transformed image to disk cache and memory cache if the download finished (Default). + + @note The transform process is called from a global queue in order to not to block the main queue. + */ +@protocol SDWebImageTransformer + +@required +/** + For each transformer, it must contains its cache key to used to store the image cache or query from the cache. This key will be appened after the original cache key generated by URL or from user. + + @return The cache key to appended after the original cache key. Should not be nil. + */ +- (nonnull NSString *)transformerKey; + +/** + Transform the image to another image. + + @param image The image to be transformed + @param key The cache key associated to the image + @return The transformed image, or nil if transform failed + */ +- (nullable UIImage *)transformedImageWithImage:(nonnull UIImage *)image forKey:(nonnull NSString *)key; + +@end + +#pragma mark - Pipeline + +// Pipeline transformer. Which you can bind multiple transformers together to let the image to be transformed one by one and generate the final image. +@interface SDWebImagePipelineTransformer : NSObject + +@property (nonatomic, copy, readonly, nonnull) NSArray> *transformers; + +- (nonnull instancetype)initWithTransformers:(nonnull NSArray> *)transformers; + +- (void)addTransformer:(nonnull id)transformer; +- (void)removeTransformer:(nonnull id)transformer; + +@end + +// There are some build-in transformer based on the `UIImage+Transformer` category to provide the common image geometry, image blending and image effect process. Those transform are useful for static image only but you can create your own to support animated image as well. +#pragma mark - Image Geometry + +// Image round corner transformer +@interface SDWebImageRoundCornerTransformer: NSObject + +@property (nonatomic, assign, readonly) CGFloat cornerRadius; +@property (nonatomic, assign, readonly) SDRectCorner corners; +@property (nonatomic, assign, readonly) CGFloat borderWidth; +@property (nonatomic, strong, readonly, nullable) UIColor *borderColor; + +- (nonnull instancetype)initWithRadius:(CGFloat)cornerRadius corners:(SDRectCorner)corners borderWidth:(CGFloat)borderWidth borderColor:(nullable UIColor *)borderColor; + +@end + +// Image resizing transformer +@interface SDWebImageResizingTransformer : NSObject + +@property (nonatomic, assign, readonly) CGSize size; +@property (nonatomic, assign, readonly) SDImageScaleMode scaleMode; + +- (nonnull instancetype)initWithSize:(CGSize)size scaleMode:(SDImageScaleMode)scaleMode; + +@end + +// Image cropping transformer +@interface SDWebImageCroppingTransformer : NSObject + +@property (nonatomic, assign, readonly) CGRect rect; + +- (nonnull instancetype)initWithRect:(CGRect)rect; + +@end + +// Image flipping transformer +@interface SDWebImageFlippingTransformer : NSObject + +@property (nonatomic, assign, readonly) BOOL horizontal; +@property (nonatomic, assign, readonly) BOOL vertical; + +- (nonnull instancetype)initWithHorizontal:(BOOL)horizontal vertical:(BOOL)vertical; + +@end + +// Image rotation transformer +@interface SDWebImageRotationTransformer : NSObject + +@property (nonatomic, assign, readonly) CGFloat angle; +@property (nonatomic, assign, readonly) BOOL fitSize; + +- (nonnull instancetype)initWithAngle:(CGFloat)angle fitSize:(BOOL)fitSize; + +@end + +#pragma mark - Image Blending + +// Image tint color transformer +@interface SDWebImageTintTransformer : NSObject + +@property (nonatomic, strong, readonly, nonnull) UIColor *tintColor; + +- (nonnull instancetype)initWithColor:(nonnull UIColor *)tintColor; + +@end + +#pragma mark - Image Effect + +// Image blur effect transformer +@interface SDWebImageBlurTransformer : NSObject + +@property (nonatomic, assign, readonly) CGFloat blurRadius; + +- (nonnull instancetype)initWithRadius:(CGFloat)blurRadius; + +@end + +#if SD_UIKIT || SD_MAC +// Core Image filter transformer +@interface SDWebImageFilterTransformer: NSObject + +@property (nonatomic, strong, readonly, nonnull) CIFilter *filter; + +- (nonnull instancetype)initWithFilter:(nonnull CIFilter *)filter; + +@end +#endif diff --git a/SDWebImage/SDWebImageTransformer.m b/SDWebImage/SDWebImageTransformer.m new file mode 100644 index 00000000..0c68e9be --- /dev/null +++ b/SDWebImage/SDWebImageTransformer.m @@ -0,0 +1,309 @@ +/* + * 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 "SDWebImageTransformer.h" +#if SD_UIKIT || SD_MAC +#import +#endif + +@interface UIColor (Additions) + +@property (nonatomic, copy, readonly, nonnull) NSString *sd_hexString; + +@end + +@implementation UIColor (Additions) + +- (NSString *)sd_hexString { + CGFloat red, green, blue, alpha; +#if SD_UIKIT + if (![self getRed:&red green:&green blue:&blue alpha:&alpha]) { + [self getWhite:&red alpha:&alpha]; + green = red; + blue = red; + } +#else + @try { + [self getRed:&red green:&green blue:&blue alpha:&alpha]; + } + @catch (NSException *exception) { + [self getWhite:&red alpha:&alpha]; + green = red; + blue = red; + } +#endif + + red = roundf(red * 255.f); + green = roundf(green * 255.f); + blue = roundf(blue * 255.f); + alpha = roundf(alpha * 255.f); + + uint hex = ((uint)alpha << 24) | ((uint)red << 16) | ((uint)green << 8) | ((uint)blue); + + return [NSString stringWithFormat:@"0x%08x", hex]; +} + +@end + +@interface SDWebImagePipelineTransformer () + +@property (nonatomic, copy, readwrite, nonnull) NSArray> *transformers; +@property (nonatomic, copy, readwrite) NSString *transformerKey; + +@end + +@implementation SDWebImagePipelineTransformer + +- (instancetype)initWithTransformers:(NSArray> *)transformers { + self = [super init]; + if (self) { + _transformers = [transformers copy]; + _transformerKey = [[self class] cacheKeyForTransformers:transformers]; + } + return self; +} + ++ (NSString *)cacheKeyForTransformers:(NSArray> *)transformers { + if (transformers.count == 0) { + return @""; + } + NSMutableArray *cacheKeys = [NSMutableArray arrayWithCapacity:transformers.count]; + [transformers enumerateObjectsUsingBlock:^(id _Nonnull transformer, NSUInteger idx, BOOL * _Nonnull stop) { + NSString *cacheKey = transformer.transformerKey; + [cacheKeys addObject:cacheKey]; + }]; + + return [cacheKeys componentsJoinedByString:@"@"]; +} + +- (UIImage *)transformedImageWithImage:(UIImage *)image forKey:(NSString *)key { + if (!image) { + return nil; + } + UIImage *transformedImage = image; + for (id transformer in self.transformers) { + transformedImage = [transformer transformedImageWithImage:transformedImage forKey:key]; + } + return transformedImage; +} + +- (void)addTransformer:(id)transformer { + if (!transformer) { + return; + } + self.transformers = [self.transformers arrayByAddingObject:transformer]; +} + +- (void)removeTransformer:(id)transformer { + if (!transformer) { + return; + } + NSMutableArray> *transformers = [self.transformers mutableCopy]; + [transformers removeObject:transformer]; + self.transformers = [transformers copy]; +} + +@end + +@implementation SDWebImageRoundCornerTransformer + +- (instancetype)initWithRadius:(CGFloat)cornerRadius corners:(SDRectCorner)corners borderWidth:(CGFloat)borderWidth borderColor:(nullable UIColor *)borderColor { + self = [super init]; + if (self) { + _cornerRadius = cornerRadius; + _corners = corners; + _borderWidth = borderWidth; + _borderColor = borderColor; + } + return self; +} + +- (NSString *)transformerKey { + return [NSString stringWithFormat:@"SDWebImageRoundCornerTransformer(%f,%lu,%f,%@)", self.cornerRadius, (unsigned long)self.corners, self.borderWidth, self.borderColor.sd_hexString]; +} + +- (UIImage *)transformedImageWithImage:(UIImage *)image forKey:(NSString *)key { + if (!image) { + return nil; + } + return [image sd_roundedCornerImageWithRadius:self.cornerRadius corners:self.corners borderWidth:self.borderWidth borderColor:self.borderColor]; +} + +@end + +@implementation SDWebImageResizingTransformer + +- (instancetype)initWithSize:(CGSize)size scaleMode:(SDImageScaleMode)scaleMode { + self = [super init]; + if (self) { + _size = size; + _scaleMode = scaleMode; + } + return self; +} + +- (NSString *)transformerKey { + CGSize size = self.size; + return [NSString stringWithFormat:@"SDWebImageResizingTransformer({%f,%f},%lu)", size.width, size.height, (unsigned long)self.scaleMode]; +} + +- (UIImage *)transformedImageWithImage:(UIImage *)image forKey:(NSString *)key { + if (!image) { + return nil; + } + return [image sd_resizedImageWithSize:self.size scaleMode:self.scaleMode]; +} + +@end + +@implementation SDWebImageCroppingTransformer + +- (instancetype)initWithRect:(CGRect)rect { + self = [super init]; + if (self) { + _rect = rect; + } + return self; +} + +- (NSString *)transformerKey { + CGRect rect = self.rect; + return [NSString stringWithFormat:@"SDWebImageCroppingTransformer({%f,%f,%f,%f})", rect.origin.x, rect.origin.y, rect.size.width, rect.size.height]; +} + +- (UIImage *)transformedImageWithImage:(UIImage *)image forKey:(NSString *)key { + if (!image) { + return nil; + } + return [image sd_croppedImageWithRect:self.rect]; +} + +@end + +@implementation SDWebImageFlippingTransformer + +- (instancetype)initWithHorizontal:(BOOL)horizontal vertical:(BOOL)vertical { + self = [super init]; + if (self) { + _horizontal = horizontal; + _vertical = vertical; + } + return self; +} + +- (NSString *)transformerKey { + return [NSString stringWithFormat:@"SDWebImageFlippingTransformer(%d,%d)", self.horizontal, self.vertical]; +} + +- (UIImage *)transformedImageWithImage:(UIImage *)image forKey:(NSString *)key { + if (!image) { + return nil; + } + return [image sd_flippedImageWithHorizontal:self.horizontal vertical:self.vertical]; +} + +@end + +@implementation SDWebImageRotationTransformer + +- (instancetype)initWithAngle:(CGFloat)angle fitSize:(BOOL)fitSize { + self = [super init]; + if (self) { + _angle = angle; + _fitSize = fitSize; + } + return self; +} + +- (NSString *)transformerKey { + return [NSString stringWithFormat:@"SDWebImageRotationTransformer(%f,%d)", self.angle, self.fitSize]; +} + +- (UIImage *)transformedImageWithImage:(UIImage *)image forKey:(NSString *)key { + if (!image) { + return nil; + } + return [image sd_rotatedImageWithAngle:self.angle fitSize:self.fitSize]; +} + +@end + +#pragma mark - Image Blending + +@implementation SDWebImageTintTransformer + +- (instancetype)initWithColor:(UIColor *)tintColor { + self = [super init]; + if (self) { + _tintColor = tintColor; + } + return self; +} + +- (NSString *)transformerKey { + return [NSString stringWithFormat:@"SDWebImageTintTransformer(%@)", self.tintColor.sd_hexString]; +} + +- (UIImage *)transformedImageWithImage:(UIImage *)image forKey:(NSString *)key { + if (!image) { + return nil; + } + return [image sd_tintedImageWithColor:self.tintColor]; +} + +@end + +#pragma mark - Image Effect + +@implementation SDWebImageBlurTransformer + +- (instancetype)initWithRadius:(CGFloat)blurRadius { + self = [super init]; + if (self) { + _blurRadius = blurRadius; + } + return self; +} + +- (NSString *)transformerKey { + return [NSString stringWithFormat:@"SDWebImageBlurTransformer(%f)", self.blurRadius]; +} + +- (UIImage *)transformedImageWithImage:(UIImage *)image forKey:(NSString *)key { + if (!image) { + return nil; + } + return [image sd_blurredImageWithRadius:self.blurRadius]; +} + +@end + +#if SD_UIKIT || SD_MAC +@implementation SDWebImageFilterTransformer + +- (instancetype)initWithFilter:(CIFilter *)filter { + self = [super init]; + if (self) { + _filter = filter; + } + return self; +} + +- (NSString *)transformerKey { + return [NSString stringWithFormat:@"SDWebImageFilterTransformer(%@)", self.filter.name]; +} + +- (UIImage *)transformedImageWithImage:(UIImage *)image forKey:(NSString *)key { + if (!image) { + return nil; + } + return [image sd_filteredImageWithFilter:self.filter]; +} + +@end +#endif diff --git a/SDWebImage/UIImage+Transform.h b/SDWebImage/UIImage+Transform.h new file mode 100644 index 00000000..73b0c5c5 --- /dev/null +++ b/SDWebImage/UIImage+Transform.h @@ -0,0 +1,126 @@ +/* + * This file is part of the SDWebImage package. + * (c) Olivier Poitrey + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +#import "SDWebImageCompat.h" + +typedef NS_ENUM(NSUInteger, SDImageScaleMode) { + SDImageScaleModeFill = 0, + SDImageScaleModeAspectFit = 1, + SDImageScaleModeAspectFill = 2 +}; + +#if SD_UIKIT || SD_WATCH +typedef UIRectCorner SDRectCorner; +#else +typedef NS_OPTIONS(NSUInteger, SDRectCorner) { + SDRectCornerTopLeft = 1 << 0, + SDRectCornerTopRight = 1 << 1, + SDRectCornerBottomLeft = 1 << 2, + SDRectCornerBottomRight = 1 << 3, + SDRectCornerAllCorners = ~0UL +}; +#endif + +/** + Provide some commen method for `UIImage`. + Image process is based on Core Graphics and vImage. + */ +@interface UIImage (Transform) + +#pragma mark - Image Geometry + +/** + Returns a new image which is scaled from this image. + The image content will be changed with the scale mode. + + @param size The new size to be scaled, values should be positive. + @param contentMode The scale mode for image content. + @return The new image with the given size. + */ +- (nullable UIImage *)sd_resizedImageWithSize:(CGSize)size scaleMode:(SDImageScaleMode)scaleMode; + +/** + Returns a new image which is cropped from this image. + + @param cropRect Image's inner rect. + @return The new image with the cropping rect. + */ +- (nullable UIImage *)sd_croppedImageWithRect:(CGRect)rect; + +/** + Rounds a new image with a given corner radius and corners. + + @param cornerRadius The radius of each corner oval. Values larger than half the + rectangle's width or height are clamped appropriately to + half the width or height. + @param corners A bitmask value that identifies the corners that you want + rounded. You can use this parameter to round only a subset + of the corners of the rectangle. + @param borderWidth The inset border line width. Values larger than half the rectangle's + width or height are clamped appropriately to half the width + or height. + @param borderColor The border stroke color. nil means clear color. + @return The new image with the round corner. + */ +- (nullable UIImage *)sd_roundedCornerImageWithRadius:(CGFloat)cornerRadius + corners:(SDRectCorner)corners + borderWidth:(CGFloat)borderWidth + borderColor:(nullable UIColor *)borderColor; + +/** + Returns a new rotated image (relative to the center). + + @param radians Rotated radians in counterclockwise.⟲ + @param fitSize YES: new image's size is extend to fit all content. + NO: image's size will not change, content may be clipped. + @return The new image with the rotation. + */ +- (nullable UIImage *)sd_rotatedImageWithAngle:(CGFloat)angle fitSize:(BOOL)fitSize; + +/** + Returns a new horizontally(vertically) flipped image. + + @param horizontal YES to flip the image horizontally. ⇋ + @param vertical YES to flip the image vertically. ⥯ + @return The new image with the flipping. + */ +- (nullable UIImage *)sd_flippedImageWithHorizontal:(BOOL)horizontal vertical:(BOOL)vertical; + +#pragma mark - Image Blending + +/** + Return a tinted image in alpha channel with the given color. + + @param tintColor The color. + @return The new image with the tint color. + */ +- (nullable UIImage *)sd_tintedImageWithColor:(nonnull UIColor *)tintColor; + +#pragma mark - Image Effect + +/** + Return a new image applied a blur effect. + + @param blurRadius The radius of the blur in points, 0 means no blur effect. + + @return The new image with blur effect, or nil if an error occurs (e.g. no enough memory). + */ +- (nullable UIImage *)sd_blurredImageWithRadius:(CGFloat)blurRadius; + +#if SD_UIKIT || SD_MAC +/** + Return a new image applied a CIFilter. + + @param filter The CIFilter to be applied to the image. + @return The new image with the CIFilter, or nil if an error occurs (e.g. no + enough memory). + */ +- (nullable UIImage *)sd_filteredImageWithFilter:(nonnull CIFilter *)filter; +#endif + +@end diff --git a/SDWebImage/UIImage+Transform.m b/SDWebImage/UIImage+Transform.m new file mode 100644 index 00000000..79f16094 --- /dev/null +++ b/SDWebImage/UIImage+Transform.m @@ -0,0 +1,505 @@ +/* + * 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 "UIImage+Transform.h" +#import "NSImage+Additions.h" +#import +#if SD_UIKIT || SD_MAC +#import +#endif + +#ifndef SD_SWAP // swap two value +#define SD_SWAP(_a_, _b_) do { __typeof__(_a_) _tmp_ = (_a_); (_a_) = (_b_); (_b_) = _tmp_; } while (0) +#endif + +#if SD_MAC +static CGContextRef SDCGContextCreateARGBBitmapContext(CGSize size, BOOL opaque, CGFloat scale) { + size_t width = ceil(size.width * scale); + size_t height = ceil(size.height * scale); + if (width < 1 || height < 1) return NULL; + + //pre-multiplied ARGB, 8-bits per component + CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB(); + CGImageAlphaInfo alphaInfo = (opaque ? kCGImageAlphaNoneSkipFirst : kCGImageAlphaPremultipliedFirst); + CGContextRef context = CGBitmapContextCreate(NULL, width, height, 8, 0, space, kCGBitmapByteOrderDefault | alphaInfo); + CGColorSpaceRelease(space); + return context; +} +#endif + +static void SDGraphicsBeginImageContextWithOptions(CGSize size, BOOL opaque, CGFloat scale) { +#if SD_UIKIT || SD_WATCH + UIGraphicsBeginImageContextWithOptions(size, opaque, scale); + CGContextRef context = UIGraphicsGetCurrentContext(); + CGContextTranslateCTM(context, 0, -size.height); + CGContextScaleCTM(context, scale, -scale); +#else + CGContextRef context = SDCGContextCreateARGBBitmapContext(size, opaque, scale); + if (!context) { + return; + } + NSGraphicsContext *graphicsContext = [NSGraphicsContext graphicsContextWithCGContext:context flipped:NO]; + CGContextRelease(context); + [NSGraphicsContext saveGraphicsState]; + NSGraphicsContext.currentContext = graphicsContext; +#endif +} + +static CGContextRef SDGraphicsGetCurrentContext(void) { +#if SD_UIKIT || SD_WATCH + return UIGraphicsGetCurrentContext(); +#else + return NSGraphicsContext.currentContext.CGContext; +#endif +} + +static void SDGraphicsEndImageContext(void) { +#if SD_UIKIT || SD_WATCH + UIGraphicsEndImageContext(); +#else + [NSGraphicsContext restoreGraphicsState]; +#endif +} + +static UIImage * SDGraphicsGetImageFromCurrentImageContext(void) { +#if SD_UIKIT || SD_WATCH + return UIGraphicsGetImageFromCurrentImageContext(); +#else + CGContextRef context = SDGraphicsGetCurrentContext(); + if (!context) { + return nil; + } + CGImageRef imageRef = CGBitmapContextCreateImage(context); + if (!imageRef) { + return nil; + } + NSImage *image = [[NSImage alloc] initWithCGImage:imageRef size:NSZeroSize]; + CGImageRelease(imageRef); + return image; +#endif +} + +static SDRectCorner SDRectCornerConvertCounterClockwise(SDRectCorner corners) { +#if SD_UIKIT || SD_WATCH + if (corners != UIRectCornerAllCorners) { + UIRectCorner tmp = 0; + if (corners & UIRectCornerTopLeft) tmp |= UIRectCornerBottomLeft; + if (corners & UIRectCornerTopRight) tmp |= UIRectCornerBottomRight; + if (corners & UIRectCornerBottomLeft) tmp |= UIRectCornerTopLeft; + if (corners & UIRectCornerBottomRight) tmp |= UIRectCornerTopRight; + corners = tmp; + } +#else + if (corners != SDRectCornerAllCorners) { + SDRectCorner tmp = 0; + if (corners & SDRectCornerTopLeft) tmp |= SDRectCornerBottomLeft; + if (corners & SDRectCornerTopRight) tmp |= SDRectCornerBottomRight; + if (corners & SDRectCornerBottomLeft) tmp |= SDRectCornerTopLeft; + if (corners & SDRectCornerBottomRight) tmp |= SDRectCornerTopRight; + corners = tmp; + } +#endif + return corners; +} + +static CGRect SDCGRectFitWithScaleMode(CGRect rect, CGSize size, SDImageScaleMode scaleMode) { + rect = CGRectStandardize(rect); + size.width = size.width < 0 ? -size.width : size.width; + size.height = size.height < 0 ? -size.height : size.height; + CGPoint center = CGPointMake(CGRectGetMidX(rect), CGRectGetMidY(rect)); + switch (scaleMode) { + case SDImageScaleModeAspectFit: + case SDImageScaleModeAspectFill: { + if (rect.size.width < 0.01 || rect.size.height < 0.01 || + size.width < 0.01 || size.height < 0.01) { + rect.origin = center; + rect.size = CGSizeZero; + } else { + CGFloat scale; + if (scaleMode == SDImageScaleModeAspectFit) { + if (size.width / size.height < rect.size.width / rect.size.height) { + scale = rect.size.height / size.height; + } else { + scale = rect.size.width / size.width; + } + } else { + if (size.width / size.height < rect.size.width / rect.size.height) { + scale = rect.size.width / size.width; + } else { + scale = rect.size.height / size.height; + } + } + size.width *= scale; + size.height *= scale; + rect.size = size; + rect.origin = CGPointMake(center.x - size.width * 0.5, center.y - size.height * 0.5); + } + } break; + case SDImageScaleModeFill: + default: { + rect = rect; + } + } + return rect; +} + +#if SD_MAC +@interface NSBezierPath (Compatibility) + ++ (instancetype)bezierPathWithRoundedRect:(NSRect)rect byRoundingCorners:(SDRectCorner)corners cornerRadius:(CGFloat)cornerRadius; + +@end + +@implementation NSBezierPath (Compatibility) + ++ (instancetype)bezierPathWithRoundedRect:(NSRect)rect byRoundingCorners:(SDRectCorner)corners cornerRadius:(CGFloat)cornerRadius { + NSBezierPath *path = [NSBezierPath bezierPath]; + + CGFloat maxCorner = MIN(NSWidth(rect), NSHeight(rect)) / 2; + + CGFloat topLeftRadius = MIN(maxCorner, (corners & SDRectCornerTopLeft) ? cornerRadius : 0); + CGFloat topRightRadius = MIN(maxCorner, (corners & SDRectCornerTopRight) ? cornerRadius : 0); + CGFloat bottomLeftRadius = MIN(maxCorner, (corners & SDRectCornerBottomLeft) ? cornerRadius : 0); + CGFloat bottomRightRadius = MIN(maxCorner, (corners & SDRectCornerBottomRight) ? cornerRadius : 0); + + NSPoint topLeft = NSMakePoint(NSMinX(rect), NSMaxY(rect)); + NSPoint topRight = NSMakePoint(NSMaxX(rect), NSMaxY(rect)); + NSPoint bottomLeft = NSMakePoint(NSMinX(rect), NSMinY(rect)); + NSPoint bottomRight = NSMakePoint(NSMaxX(rect), NSMinY(rect)); + + [path moveToPoint:NSMakePoint(NSMidX(rect), NSMaxY(rect))]; + [path appendBezierPathWithArcFromPoint:topLeft toPoint:bottomLeft radius:topLeftRadius]; + [path appendBezierPathWithArcFromPoint:bottomLeft toPoint:bottomRight radius:bottomLeftRadius]; + [path appendBezierPathWithArcFromPoint:bottomRight toPoint:topRight radius:bottomRightRadius]; + [path appendBezierPathWithArcFromPoint:topRight toPoint:topLeft radius:topRightRadius]; + [path closePath]; + + return path; +} + +@end + +#endif + +@implementation UIImage (Transform) + +- (void)sd_drawInRect:(CGRect)rect withScaleMode:(SDImageScaleMode)scaleMode clipsToBounds:(BOOL)clips { + CGRect drawRect = SDCGRectFitWithScaleMode(rect, self.size, scaleMode); + if (drawRect.size.width == 0 || drawRect.size.height == 0) return; + if (clips) { + CGContextRef context = SDGraphicsGetCurrentContext(); + if (context) { + CGContextSaveGState(context); + CGContextAddRect(context, rect); + CGContextClip(context); + [self drawInRect:drawRect]; + CGContextRestoreGState(context); + } + } else { + [self drawInRect:drawRect]; + } +} + +- (UIImage *)sd_resizedImageWithSize:(CGSize)size scaleMode:(SDImageScaleMode)scaleMode { + if (size.width <= 0 || size.height <= 0) return nil; + SDGraphicsBeginImageContextWithOptions(size, NO, self.scale); + [self sd_drawInRect:CGRectMake(0, 0, size.width, size.height) withScaleMode:scaleMode clipsToBounds:NO]; + UIImage *image = SDGraphicsGetImageFromCurrentImageContext(); + SDGraphicsEndImageContext(); + return image; +} + +- (UIImage *)sd_croppedImageWithRect:(CGRect)rect { + if (!self.CGImage) return nil; + rect.origin.x *= self.scale; + rect.origin.y *= self.scale; + rect.size.width *= self.scale; + rect.size.height *= self.scale; + if (rect.size.width <= 0 || rect.size.height <= 0) return nil; + CGImageRef imageRef = CGImageCreateWithImageInRect(self.CGImage, rect); + if (!imageRef) { + return nil; + } +#if SD_UIKIT || SD_WATCH + UIImage *image = [UIImage imageWithCGImage:imageRef scale:self.scale orientation:self.imageOrientation]; +#else + UIImage *image = [[UIImage alloc] initWithCGImage:imageRef scale:self.scale]; +#endif + CGImageRelease(imageRef); + return image; +} + +- (UIImage *)sd_roundedCornerImageWithRadius:(CGFloat)cornerRadius corners:(SDRectCorner)corners borderWidth:(CGFloat)borderWidth borderColor:(UIColor *)borderColor { + if (!self.CGImage) return nil; + corners = SDRectCornerConvertCounterClockwise(corners); + SDGraphicsBeginImageContextWithOptions(self.size, NO, self.scale); + CGContextRef context = SDGraphicsGetCurrentContext(); + CGRect rect = CGRectMake(0, 0, self.size.width, self.size.height); + + CGFloat minSize = MIN(self.size.width, self.size.height); + if (borderWidth < minSize / 2) { +#if SD_UIKIT || SD_WATCH + UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectInset(rect, borderWidth, borderWidth) byRoundingCorners:corners cornerRadii:CGSizeMake(cornerRadius, cornerRadius)]; +#else + NSBezierPath *path = [NSBezierPath bezierPathWithRoundedRect:CGRectInset(rect, borderWidth, borderWidth) byRoundingCorners:corners cornerRadius:cornerRadius]; +#endif + [path closePath]; + + CGContextSaveGState(context); + [path addClip]; + CGContextDrawImage(context, rect, self.CGImage); + CGContextRestoreGState(context); + } + + if (borderColor && borderWidth < minSize / 2 && borderWidth > 0) { + CGFloat strokeInset = (floor(borderWidth * self.scale) + 0.5) / self.scale; + CGRect strokeRect = CGRectInset(rect, strokeInset, strokeInset); + CGFloat strokeRadius = cornerRadius > self.scale / 2 ? cornerRadius - self.scale / 2 : 0; +#if SD_UIKIT || SD_WATCH + UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:strokeRect byRoundingCorners:corners cornerRadii:CGSizeMake(strokeRadius, strokeRadius)]; +#else + NSBezierPath *path = [NSBezierPath bezierPathWithRoundedRect:strokeRect byRoundingCorners:corners cornerRadius:strokeRadius]; +#endif + [path closePath]; + + path.lineWidth = borderWidth; + [borderColor setStroke]; + [path stroke]; + } + + UIImage *image = SDGraphicsGetImageFromCurrentImageContext(); + SDGraphicsEndImageContext(); + return image; +} + +- (UIImage *)sd_rotatedImageWithAngle:(CGFloat)angle fitSize:(BOOL)fitSize { + if (!self.CGImage) return nil; + size_t width = (size_t)CGImageGetWidth(self.CGImage); + size_t height = (size_t)CGImageGetHeight(self.CGImage); + CGRect newRect = CGRectApplyAffineTransform(CGRectMake(0., 0., width, height), + fitSize ? CGAffineTransformMakeRotation(angle) : CGAffineTransformIdentity); + + CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); + CGContextRef context = CGBitmapContextCreate(NULL, + (size_t)newRect.size.width, + (size_t)newRect.size.height, + 8, + (size_t)newRect.size.width * 4, + colorSpace, + kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedFirst); + CGColorSpaceRelease(colorSpace); + if (!context) return nil; + + CGContextSetShouldAntialias(context, true); + CGContextSetAllowsAntialiasing(context, true); + CGContextSetInterpolationQuality(context, kCGInterpolationHigh); + + CGContextTranslateCTM(context, +(newRect.size.width * 0.5), +(newRect.size.height * 0.5)); + CGContextRotateCTM(context, angle); + + CGContextDrawImage(context, CGRectMake(-(width * 0.5), -(height * 0.5), width, height), self.CGImage); + CGImageRef imgRef = CGBitmapContextCreateImage(context); +#if SD_UIKIT || SD_WATCH + UIImage *img = [UIImage imageWithCGImage:imgRef scale:self.scale orientation:self.imageOrientation]; +#else + UIImage *img = [[UIImage alloc] initWithCGImage:imgRef scale:self.scale]; +#endif + CGImageRelease(imgRef); + CGContextRelease(context); + return img; +} + +- (UIImage *)sd_flippedImageWithHorizontal:(BOOL)horizontal vertical:(BOOL)vertical { + if (!self.CGImage) return nil; + size_t width = (size_t)CGImageGetWidth(self.CGImage); + size_t height = (size_t)CGImageGetHeight(self.CGImage); + size_t bytesPerRow = width * 4; + CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); + CGContextRef context = CGBitmapContextCreate(NULL, width, height, 8, bytesPerRow, colorSpace, kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedFirst); + CGColorSpaceRelease(colorSpace); + if (!context) return nil; + + CGContextDrawImage(context, CGRectMake(0, 0, width, height), self.CGImage); + UInt8 *data = (UInt8 *)CGBitmapContextGetData(context); + if (!data) { + CGContextRelease(context); + return nil; + } + vImage_Buffer src = { data, height, width, bytesPerRow }; + vImage_Buffer dest = { data, height, width, bytesPerRow }; + if (vertical) { + vImageVerticalReflect_ARGB8888(&src, &dest, kvImageBackgroundColorFill); + } + if (horizontal) { + vImageHorizontalReflect_ARGB8888(&src, &dest, kvImageBackgroundColorFill); + } + CGImageRef imgRef = CGBitmapContextCreateImage(context); + CGContextRelease(context); +#if SD_UIKIT || SD_WATCH + UIImage *img = [UIImage imageWithCGImage:imgRef scale:self.scale orientation:self.imageOrientation]; +#else + UIImage *img = [[UIImage alloc] initWithCGImage:imgRef scale:self.scale]; +#endif + CGImageRelease(imgRef); + return img; +} + +#pragma mark - Image Blending + +- (UIImage *)sd_tintedImageWithColor:(UIColor *)tintColor { + if (!self.CGImage) return nil; + if (!tintColor.CGColor) return nil; + + BOOL hasTint = CGColorGetAlpha(tintColor.CGColor) > __FLT_EPSILON__; + if (!hasTint) { +#if SD_UIKIT || SD_WATCH + return [UIImage imageWithCGImage:self.CGImage scale:self.scale orientation:self.imageOrientation]; +#else + return [[UIImage alloc] initWithCGImage:self.CGImage scale:self.scale]; +#endif + } + + CGSize size = self.size; + CGRect rect = { CGPointZero, size }; + CGFloat scale = self.scale; + + // blend mode, see https://en.wikipedia.org/wiki/Alpha_compositing + CGBlendMode blendMode = kCGBlendModeSourceAtop; + + SDGraphicsBeginImageContextWithOptions(size, NO, scale); + CGContextRef context = SDGraphicsGetCurrentContext(); + CGContextDrawImage(context, rect, self.CGImage); + CGContextSetBlendMode(context, blendMode); + CGContextSetFillColorWithColor(context, tintColor.CGColor); + CGContextFillRect(context, rect); + UIImage *image = SDGraphicsGetImageFromCurrentImageContext(); + SDGraphicsEndImageContext(); + + return image; +} + +#pragma mark - Image Effect + +- (UIImage *)sd_blurredImageWithRadius:(CGFloat)blurRadius { + if (self.size.width < 1 || self.size.height < 1) { + return nil; + } + if (!self.CGImage) { + return nil; + } + + BOOL hasBlur = blurRadius > __FLT_EPSILON__; + if (!hasBlur) { + return self; + } + + CGFloat scale = self.scale; + CGImageRef imageRef = self.CGImage; + + vImage_Buffer effect = {}, scratch = {}; + vImage_Buffer *input = NULL, *output = NULL; + + vImage_CGImageFormat format = { + .bitsPerComponent = 8, + .bitsPerPixel = 32, + .colorSpace = NULL, + .bitmapInfo = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little, //requests a BGRA buffer. + .version = 0, + .decode = NULL, + .renderingIntent = kCGRenderingIntentDefault + }; + + vImage_Error err; + err = vImageBuffer_InitWithCGImage(&effect, &format, NULL, imageRef, kvImagePrintDiagnosticsToConsole); + if (err != kvImageNoError) { + NSLog(@"UIImage+Transform error: vImageBuffer_InitWithCGImage returned error code %zi for inputImage: %@", err, self); + return nil; + } + err = vImageBuffer_Init(&scratch, effect.height, effect.width, format.bitsPerPixel, kvImageNoFlags); + if (err != kvImageNoError) { + NSLog(@"UIImage+Transform error: vImageBuffer_Init returned error code %zi for inputImage: %@", err, self); + return nil; + } + + input = &effect; + output = &scratch; + + if (hasBlur) { + // A description of how to compute the box kernel width from the Gaussian + // radius (aka standard deviation) appears in the SVG spec: + // http://www.w3.org/TR/SVG/filters.html#feGaussianBlurElement + // + // For larger values of 's' (s >= 2.0), an approximation can be used: Three + // successive box-blurs build a piece-wise quadratic convolution kernel, which + // approximates the Gaussian kernel to within roughly 3%. + // + // let d = floor(s * 3*sqrt(2*pi)/4 + 0.5) + // + // ... if d is odd, use three box-blurs of size 'd', centered on the output pixel. + // + CGFloat inputRadius = blurRadius * scale; + if (inputRadius - 2.0 < __FLT_EPSILON__) inputRadius = 2.0; + uint32_t radius = floor((inputRadius * 3.0 * sqrt(2 * M_PI) / 4 + 0.5) / 2); + radius |= 1; // force radius to be odd so that the three box-blur methodology works. + int iterations; + if (blurRadius * scale < 0.5) iterations = 1; + else if (blurRadius * scale < 1.5) iterations = 2; + else iterations = 3; + NSInteger tempSize = vImageBoxConvolve_ARGB8888(input, output, NULL, 0, 0, radius, radius, NULL, kvImageGetTempBufferSize | kvImageEdgeExtend); + void *temp = malloc(tempSize); + for (int i = 0; i < iterations; i++) { + vImageBoxConvolve_ARGB8888(input, output, temp, 0, 0, radius, radius, NULL, kvImageEdgeExtend); + SD_SWAP(input, output); + } + free(temp); + } + + CGImageRef effectCGImage = NULL; + effectCGImage = vImageCreateCGImageFromBuffer(input, &format, NULL, NULL, kvImageNoAllocate, NULL); + if (effectCGImage == NULL) { + effectCGImage = vImageCreateCGImageFromBuffer(input, &format, NULL, NULL, kvImageNoFlags, NULL); + free(input->data); + } + free(output->data); +#if SD_UIKIT || SD_WATCH + UIImage *outputImage = [UIImage imageWithCGImage:effectCGImage scale:self.scale orientation:self.imageOrientation]; +#else + UIImage *outputImage = [[UIImage alloc] initWithCGImage:effectCGImage scale:self.scale]; +#endif + CGImageRelease(effectCGImage); + + return outputImage; +} + +#if SD_UIKIT || SD_MAC +- (UIImage *)sd_filteredImageWithFilter:(CIFilter *)filter { + if (!self.CGImage) return nil; + + CIContext *context = [CIContext context]; + CIImage *inputImage = [CIImage imageWithCGImage:self.CGImage]; + if (!inputImage) return nil; + + [filter setValue:inputImage forKey:kCIInputImageKey]; + CIImage *outputImage = filter.outputImage; + if (!outputImage) return nil; + + CGImageRef imageRef = [context createCGImage:outputImage fromRect:outputImage.extent]; + if (!imageRef) return nil; + +#if SD_UIKIT + UIImage *image = [UIImage imageWithCGImage:imageRef scale:self.scale orientation:self.imageOrientation]; +#else + UIImage *image = [[UIImage alloc] initWithCGImage:imageRef scale:self.scale]; +#endif + CGImageRelease(imageRef); + + return image; +} +#endif + +@end From 464d725368baee42ef249e507fa18340595adfe0 Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Tue, 13 Feb 2018 18:30:25 +0800 Subject: [PATCH 02/13] Adopt the transformer to cache & manager. Use a new context option SDWebImageContextCustomTransformer to bind the transformer Drop old way of delegate method for transformer. Add two new delegate methods to allow advanced use case after we remove that. --- SDWebImage.xcodeproj/project.pbxproj | 12 ++++++------ SDWebImage/SDImageCache.h | 7 ++++++- SDWebImage/SDImageCache.m | 12 ++++++++++-- SDWebImage/SDWebImageDefine.h | 5 +++++ SDWebImage/SDWebImageDefine.m | 1 + SDWebImage/SDWebImageManager.h | 19 +++---------------- SDWebImage/SDWebImageManager.m | 12 ++++++++---- SDWebImage/SDWebImageTransformer.h | 3 +++ SDWebImage/SDWebImageTransformer.m | 4 +++- SDWebImage/UIImage+Transform.h | 6 +++--- SDWebImage/UIImage+Transform.m | 4 ++-- WebImage/SDWebImage.h | 2 ++ 12 files changed, 52 insertions(+), 35 deletions(-) diff --git a/SDWebImage.xcodeproj/project.pbxproj b/SDWebImage.xcodeproj/project.pbxproj index 1ff4efb0..7d7389da 100644 --- a/SDWebImage.xcodeproj/project.pbxproj +++ b/SDWebImage.xcodeproj/project.pbxproj @@ -406,12 +406,12 @@ 32F7C0812030719600873181 /* UIImage+Transform.m in Sources */ = {isa = PBXBuildFile; fileRef = 32F7C07C2030719600873181 /* UIImage+Transform.m */; }; 32F7C0822030719600873181 /* UIImage+Transform.m in Sources */ = {isa = PBXBuildFile; fileRef = 32F7C07C2030719600873181 /* UIImage+Transform.m */; }; 32F7C0832030719600873181 /* UIImage+Transform.m in Sources */ = {isa = PBXBuildFile; fileRef = 32F7C07C2030719600873181 /* UIImage+Transform.m */; }; - 32F7C0842030719600873181 /* UIImage+Transform.h in Headers */ = {isa = PBXBuildFile; fileRef = 32F7C07D2030719600873181 /* UIImage+Transform.h */; }; - 32F7C0852030719600873181 /* UIImage+Transform.h in Headers */ = {isa = PBXBuildFile; fileRef = 32F7C07D2030719600873181 /* UIImage+Transform.h */; }; - 32F7C0862030719600873181 /* UIImage+Transform.h in Headers */ = {isa = PBXBuildFile; fileRef = 32F7C07D2030719600873181 /* UIImage+Transform.h */; }; - 32F7C0872030719600873181 /* UIImage+Transform.h in Headers */ = {isa = PBXBuildFile; fileRef = 32F7C07D2030719600873181 /* UIImage+Transform.h */; }; - 32F7C0882030719600873181 /* UIImage+Transform.h in Headers */ = {isa = PBXBuildFile; fileRef = 32F7C07D2030719600873181 /* UIImage+Transform.h */; }; - 32F7C0892030719600873181 /* UIImage+Transform.h in Headers */ = {isa = PBXBuildFile; fileRef = 32F7C07D2030719600873181 /* UIImage+Transform.h */; }; + 32F7C0842030719600873181 /* UIImage+Transform.h in Headers */ = {isa = PBXBuildFile; fileRef = 32F7C07D2030719600873181 /* UIImage+Transform.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 32F7C0852030719600873181 /* UIImage+Transform.h in Headers */ = {isa = PBXBuildFile; fileRef = 32F7C07D2030719600873181 /* UIImage+Transform.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 32F7C0862030719600873181 /* UIImage+Transform.h in Headers */ = {isa = PBXBuildFile; fileRef = 32F7C07D2030719600873181 /* UIImage+Transform.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 32F7C0872030719600873181 /* UIImage+Transform.h in Headers */ = {isa = PBXBuildFile; fileRef = 32F7C07D2030719600873181 /* UIImage+Transform.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 32F7C0882030719600873181 /* UIImage+Transform.h in Headers */ = {isa = PBXBuildFile; fileRef = 32F7C07D2030719600873181 /* UIImage+Transform.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 32F7C0892030719600873181 /* UIImage+Transform.h in Headers */ = {isa = PBXBuildFile; fileRef = 32F7C07D2030719600873181 /* UIImage+Transform.h */; settings = {ATTRIBUTES = (Public, ); }; }; 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 */; }; diff --git a/SDWebImage/SDImageCache.h b/SDWebImage/SDImageCache.h index 4d51c03b..07dfee44 100644 --- a/SDWebImage/SDImageCache.h +++ b/SDWebImage/SDImageCache.h @@ -34,7 +34,12 @@ typedef NS_OPTIONS(NSUInteger, SDImageCacheOptions) { /** * By default, we query the memory cache synchronously, disk cache asynchronously. This mask can force to query disk cache synchronously. */ - SDImageCacheQueryDiskSync = 1 << 1 + SDImageCacheQueryDiskSync = 1 << 1, + /** + * We usually don't apply transform on animated images as most transformers could not manage animated images. + * Use this flag to transform them anyway. + */ + SDImageCacheTransformAnimatedImage = 1 << 2, }; typedef void(^SDCacheQueryCompletedBlock)(UIImage * _Nullable image, NSData * _Nullable data, SDImageCacheType cacheType); diff --git a/SDWebImage/SDImageCache.m b/SDWebImage/SDImageCache.m index 68f5f459..a16b4600 100644 --- a/SDWebImage/SDImageCache.m +++ b/SDWebImage/SDImageCache.m @@ -10,6 +10,7 @@ #import #import "NSImage+Additions.h" #import "SDWebImageCodersManager.h" +#import "SDWebImageTransformer.h" #define LOCK(lock) dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER); #define UNLOCK(lock) dispatch_semaphore_signal(lock); @@ -532,11 +533,18 @@ FOUNDATION_STATIC_INLINE NSUInteger SDCacheCostForImage(UIImage *image) { diskImage = image; cacheType = SDImageCacheTypeMemory; } else if (diskData) { + NSString *cacheKey = key; + if ([context valueForKey:SDWebImageContextCustomTransformer]) { + // grab the transformed disk image if transformer provided + id transformer = [context valueForKey:SDWebImageContextCustomTransformer]; + NSString *transformerKey = [transformer transformerKey]; + cacheKey = [[key stringByAppendingString:SDWebImageTransformerKeySeparator] stringByAppendingString:transformerKey]; + } // decode image data only if in-memory cache missed - diskImage = [self diskImageForKey:key data:diskData]; + diskImage = [self diskImageForKey:cacheKey data:diskData]; if (diskImage && self.config.shouldCacheImagesInMemory) { NSUInteger cost = SDCacheCostForImage(diskImage); - [self.memCache setObject:diskImage forKey:key cost:cost]; + [self.memCache setObject:diskImage forKey:cacheKey cost:cost]; } } diff --git a/SDWebImage/SDWebImageDefine.h b/SDWebImage/SDWebImageDefine.h index d99d22d4..b3f5e726 100644 --- a/SDWebImage/SDWebImageDefine.h +++ b/SDWebImage/SDWebImageDefine.h @@ -20,3 +20,8 @@ FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextSetIma A SDWebImageManager instance to control the image download and cache process using in UIImageView+WebCache category and likes. If not provided, use the shared manager (SDWebImageManager) */ FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextCustomManager; + +/** + A id instance which conforms SDWebImageTransformer protocol. It's used for image transform after the image load finished and store the transformed image to cache. (id) + */ +FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextCustomTransformer; diff --git a/SDWebImage/SDWebImageDefine.m b/SDWebImage/SDWebImageDefine.m index 36adffae..ae88e07f 100644 --- a/SDWebImage/SDWebImageDefine.m +++ b/SDWebImage/SDWebImageDefine.m @@ -10,3 +10,4 @@ SDWebImageContextOption const SDWebImageContextSetImageGroup = @"setImageGroup"; SDWebImageContextOption const SDWebImageContextCustomManager = @"customManager"; +SDWebImageContextOption const SDWebImageContextCustomTransformer = @"customTransformer"; diff --git a/SDWebImage/SDWebImageManager.h b/SDWebImage/SDWebImageManager.h index 222392d0..baf66ddb 100644 --- a/SDWebImage/SDWebImageManager.h +++ b/SDWebImage/SDWebImageManager.h @@ -76,8 +76,7 @@ typedef NS_OPTIONS(NSUInteger, SDWebImageOptions) { SDWebImageDelayPlaceholder = 1 << 9, /** - * We usually don't call transformDownloadedImage delegate method on animated images, - * as most transformation code would mangle it. + * We usually don't apply transform on animated images as most transformers could not manage animated images. * Use this flag to transform them anyway. */ SDWebImageTransformAnimatedImage = 1 << 10, @@ -115,7 +114,7 @@ typedef NS_OPTIONS(NSUInteger, SDWebImageOptions) { /** * By default, when you use `SDWebImageTransition` to do some view transition after the image load finished, this transition is only applied for image download from the network. This mask can force to apply view transition for memory and disk cache as well. */ - SDWebImageForceTransition = 1 << 16 + SDWebImageForceTransition = 1 << 16, }; typedef void(^SDExternalCompletionBlock)(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL); @@ -141,7 +140,7 @@ typedef NSData * _Nullable(^SDWebImageCacheSerializerBlock)(UIImage * _Nonnull i * * @return Return NO to prevent the downloading of the image on cache misses. If not implemented, YES is implied. */ -- (BOOL)imageManager:(nonnull SDWebImageManager *)imageManager shouldDownloadImageForURL:(nullable NSURL *)imageURL; +- (BOOL)imageManager:(nonnull SDWebImageManager *)imageManager shouldDownloadImageForURL:(nonnull NSURL *)imageURL; /** * Controls the complicated logic to mark as failed URLs when download error occur. @@ -153,18 +152,6 @@ typedef NSData * _Nullable(^SDWebImageCacheSerializerBlock)(UIImage * _Nonnull i */ - (BOOL)imageManager:(nonnull SDWebImageManager *)imageManager shouldBlockFailedURL:(nonnull NSURL *)imageURL withError:(nonnull NSError *)error; -/** - * Allows to transform the image immediately after it has been downloaded and just before to cache it on disk and memory. - * NOTE: This method is called from a global queue in order to not to block the main thread. - * - * @param imageManager The current `SDWebImageManager` - * @param image The image to transform - * @param imageURL The url of the image to transform - * - * @return The transformed image object. - */ -- (nullable UIImage *)imageManager:(nonnull SDWebImageManager *)imageManager transformDownloadedImage:(nullable UIImage *)image withURL:(nullable NSURL *)imageURL; - @end /** diff --git a/SDWebImage/SDWebImageManager.m b/SDWebImage/SDWebImageManager.m index ff5c4017..5e9ae917 100644 --- a/SDWebImage/SDWebImageManager.m +++ b/SDWebImage/SDWebImageManager.m @@ -9,6 +9,7 @@ #import "SDWebImageManager.h" #import "NSImage+Additions.h" #import +#import "SDWebImageTransformer.h" @interface SDWebImageCombinedOperation : NSObject @@ -153,6 +154,7 @@ SDImageCacheOptions cacheOptions = 0; if (options & SDWebImageQueryDataWhenInMemory) cacheOptions |= SDImageCacheQueryDataWhenInMemory; if (options & SDWebImageQueryDiskSync) cacheOptions |= SDImageCacheQueryDiskSync; + if (options & SDWebImageTransformAnimatedImage) cacheOptions |= SDImageCacheTransformAnimatedImage; __weak SDWebImageCombinedOperation *weakOperation = operation; operation.cacheOperation = [self.imageCache queryCacheOperationForKey:key options:cacheOptions done:^(UIImage *cachedImage, NSData *cachedData, SDImageCacheType cacheType) { @@ -238,11 +240,13 @@ if (options & SDWebImageRefreshCached && cachedImage && !downloadedImage) { // Image refresh hit the NSURLCache cache, do not call the completion block - } else if (downloadedImage && (!downloadedImage.images || (options & SDWebImageTransformAnimatedImage)) && [self.delegate respondsToSelector:@selector(imageManager:transformDownloadedImage:withURL:)]) { + } else if (downloadedImage && (!downloadedImage.images || (options & SDWebImageTransformAnimatedImage)) && [context valueForKey:SDWebImageContextCustomTransformer]) { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ - UIImage *transformedImage = [self.delegate imageManager:self transformDownloadedImage:downloadedImage withURL:url]; - + id transformer = [context valueForKey:SDWebImageContextCustomTransformer]; + UIImage *transformedImage = [transformer transformedImageWithImage:downloadedImage forKey:key]; if (transformedImage && finished) { + NSString *transformerKey = [transformer transformerKey]; + NSString *cacheKey = [[key stringByAppendingString:SDWebImageTransformerKeySeparator] stringByAppendingString:transformerKey]; BOOL imageWasTransformed = ![transformedImage isEqual:downloadedImage]; NSData *cacheData; // pass nil if the image was transformed, so we can recalculate the data from the image @@ -251,7 +255,7 @@ } else { cacheData = (imageWasTransformed ? nil : downloadedData); } - [self.imageCache storeImage:transformedImage imageData:cacheData forKey:key toDisk:cacheOnDisk completion:nil]; + [self.imageCache storeImage:transformedImage imageData:cacheData forKey:cacheKey toDisk:cacheOnDisk completion:nil]; } [self callCompletionBlockForOperation:strongSubOperation completion:completedBlock image:transformedImage data:downloadedData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url]; diff --git a/SDWebImage/SDWebImageTransformer.h b/SDWebImage/SDWebImageTransformer.h index d54bff24..8080e47f 100644 --- a/SDWebImage/SDWebImageTransformer.h +++ b/SDWebImage/SDWebImageTransformer.h @@ -39,6 +39,9 @@ #pragma mark - Pipeline +// Separator for different transformerKey, for example, `image.png` |> flip(YES,NO) |> rotate(pi/4,YES) => 'image-SDWebImageFlippingTransformer(1,0)-SDWebImageRotationTransformer(0.78539816339,1).png' +FOUNDATION_EXPORT NSString * _Nonnull const SDWebImageTransformerKeySeparator; + // Pipeline transformer. Which you can bind multiple transformers together to let the image to be transformed one by one and generate the final image. @interface SDWebImagePipelineTransformer : NSObject diff --git a/SDWebImage/SDWebImageTransformer.m b/SDWebImage/SDWebImageTransformer.m index 0c68e9be..13da7fed 100644 --- a/SDWebImage/SDWebImageTransformer.m +++ b/SDWebImage/SDWebImageTransformer.m @@ -50,6 +50,8 @@ @end +NSString * const SDWebImageTransformerKeySeparator = @"-"; + @interface SDWebImagePipelineTransformer () @property (nonatomic, copy, readwrite, nonnull) NSArray> *transformers; @@ -78,7 +80,7 @@ [cacheKeys addObject:cacheKey]; }]; - return [cacheKeys componentsJoinedByString:@"@"]; + return [cacheKeys componentsJoinedByString:SDWebImageTransformerKeySeparator]; } - (UIImage *)transformedImageWithImage:(UIImage *)image forKey:(NSString *)key { diff --git a/SDWebImage/UIImage+Transform.h b/SDWebImage/UIImage+Transform.h index 73b0c5c5..c042977d 100644 --- a/SDWebImage/UIImage+Transform.h +++ b/SDWebImage/UIImage+Transform.h @@ -39,7 +39,7 @@ typedef NS_OPTIONS(NSUInteger, SDRectCorner) { The image content will be changed with the scale mode. @param size The new size to be scaled, values should be positive. - @param contentMode The scale mode for image content. + @param scaleMode The scale mode for image content. @return The new image with the given size. */ - (nullable UIImage *)sd_resizedImageWithSize:(CGSize)size scaleMode:(SDImageScaleMode)scaleMode; @@ -47,7 +47,7 @@ typedef NS_OPTIONS(NSUInteger, SDRectCorner) { /** Returns a new image which is cropped from this image. - @param cropRect Image's inner rect. + @param rect Image's inner rect. @return The new image with the cropping rect. */ - (nullable UIImage *)sd_croppedImageWithRect:(CGRect)rect; @@ -75,7 +75,7 @@ typedef NS_OPTIONS(NSUInteger, SDRectCorner) { /** Returns a new rotated image (relative to the center). - @param radians Rotated radians in counterclockwise.⟲ + @param angle Rotated radians in counterclockwise.⟲ @param fitSize YES: new image's size is extend to fit all content. NO: image's size will not change, content may be clipped. @return The new image with the rotation. diff --git a/SDWebImage/UIImage+Transform.m b/SDWebImage/UIImage+Transform.m index 79f16094..ecacfd09 100644 --- a/SDWebImage/UIImage+Transform.m +++ b/SDWebImage/UIImage+Transform.m @@ -149,13 +149,13 @@ static CGRect SDCGRectFitWithScaleMode(CGRect rect, CGSize size, SDImageScaleMod } #if SD_MAC -@interface NSBezierPath (Compatibility) +@interface NSBezierPath (Additions) + (instancetype)bezierPathWithRoundedRect:(NSRect)rect byRoundingCorners:(SDRectCorner)corners cornerRadius:(CGFloat)cornerRadius; @end -@implementation NSBezierPath (Compatibility) +@implementation NSBezierPath (Additions) + (instancetype)bezierPathWithRoundedRect:(NSRect)rect byRoundingCorners:(SDRectCorner)corners cornerRadius:(CGFloat)cornerRadius { NSBezierPath *path = [NSBezierPath bezierPath]; diff --git a/WebImage/SDWebImage.h b/WebImage/SDWebImage.h index e6e74cf8..c425cb04 100644 --- a/WebImage/SDWebImage.h +++ b/WebImage/SDWebImage.h @@ -37,6 +37,8 @@ FOUNDATION_EXPORT const unsigned char WebImageVersionString[]; #import #import #import +#import +#import #if SD_MAC || SD_UIKIT #import From 44d266af7c26d191be337f183439d129b08047df Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Sun, 18 Feb 2018 01:22:07 +0800 Subject: [PATCH 03/13] Add transformer property in manager level to allow a central control of image transform(optional) --- SDWebImage/SDWebImageDefine.h | 2 +- SDWebImage/SDWebImageManager.h | 8 ++++++++ SDWebImage/SDWebImageManager.m | 17 +++++++++++++---- SDWebImage/SDWebImageTransformer.h | 5 ++--- 4 files changed, 24 insertions(+), 8 deletions(-) diff --git a/SDWebImage/SDWebImageDefine.h b/SDWebImage/SDWebImageDefine.h index b3f5e726..4437d578 100644 --- a/SDWebImage/SDWebImageDefine.h +++ b/SDWebImage/SDWebImageDefine.h @@ -22,6 +22,6 @@ FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextSetIma FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextCustomManager; /** - A id instance which conforms SDWebImageTransformer protocol. It's used for image transform after the image load finished and store the transformed image to cache. (id) + A id instance which conforms SDWebImageTransformer protocol. It's used for image transform after the image load finished and store the transformed image to cache. If you provide one, it will ignore the `transformer` in manager and use provided one instead. (id) */ FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextCustomTransformer; diff --git a/SDWebImage/SDWebImageManager.h b/SDWebImage/SDWebImageManager.h index baf66ddb..d28ad8f7 100644 --- a/SDWebImage/SDWebImageManager.h +++ b/SDWebImage/SDWebImageManager.h @@ -10,6 +10,7 @@ #import "SDWebImageOperation.h" #import "SDWebImageDownloader.h" #import "SDImageCache.h" +#import "SDWebImageTransformer.h" typedef NS_OPTIONS(NSUInteger, SDWebImageOptions) { /** @@ -183,6 +184,13 @@ SDWebImageManager *manager = [SDWebImageManager sharedManager]; @property (strong, nonatomic, readonly, nullable) SDImageCache *imageCache; @property (strong, nonatomic, readonly, nullable) SDWebImageDownloader *imageDownloader; +/** + The image transformer for manager. It's used for image transform after the image load finished and store the transformed image to cache, see `SDWebImageTransformer`. + Defaults to nil, which means no transform is applied. + @note This will affect all the load requests for this manager if you provide. However, you can pass `SDWebImageContextCustomTransformer` in context arg to explicitly use that transformer instead. + */ +@property (strong, nonatomic, nullable) id transformer; + /** * The cache filter is a block used each time SDWebImageManager need to convert an URL into a cache key. This can * be used to remove dynamic part of an image URL. diff --git a/SDWebImage/SDWebImageManager.m b/SDWebImage/SDWebImageManager.m index 5e9ae917..c30a612d 100644 --- a/SDWebImage/SDWebImageManager.m +++ b/SDWebImage/SDWebImageManager.m @@ -8,8 +8,6 @@ #import "SDWebImageManager.h" #import "NSImage+Additions.h" -#import -#import "SDWebImageTransformer.h" @interface SDWebImageCombinedOperation : NSObject @@ -156,6 +154,18 @@ if (options & SDWebImageQueryDiskSync) cacheOptions |= SDImageCacheQueryDiskSync; if (options & SDWebImageTransformAnimatedImage) cacheOptions |= SDImageCacheTransformAnimatedImage; + // Image transformer + id transformer; + if ([context valueForKey:SDWebImageContextCustomTransformer]) { + transformer = [context valueForKey:SDWebImageContextCustomTransformer]; + } else if (self.transformer) { + // Transformer from manager + transformer = self.transformer; + NSMutableDictionary *mutableContext = [NSMutableDictionary dictionaryWithDictionary:context]; + [mutableContext setValue:transformer forKey:SDWebImageContextCustomTransformer]; + context = [mutableContext copy]; + } + __weak SDWebImageCombinedOperation *weakOperation = operation; operation.cacheOperation = [self.imageCache queryCacheOperationForKey:key options:cacheOptions done:^(UIImage *cachedImage, NSData *cachedData, SDImageCacheType cacheType) { __strong __typeof(weakOperation) strongOperation = weakOperation; @@ -240,9 +250,8 @@ if (options & SDWebImageRefreshCached && cachedImage && !downloadedImage) { // Image refresh hit the NSURLCache cache, do not call the completion block - } else if (downloadedImage && (!downloadedImage.images || (options & SDWebImageTransformAnimatedImage)) && [context valueForKey:SDWebImageContextCustomTransformer]) { + } else if (downloadedImage && (!downloadedImage.images || (options & SDWebImageTransformAnimatedImage)) && transformer) { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ - id transformer = [context valueForKey:SDWebImageContextCustomTransformer]; UIImage *transformedImage = [transformer transformedImageWithImage:downloadedImage forKey:key]; if (transformedImage && finished) { NSString *transformerKey = [transformer transformerKey]; diff --git a/SDWebImage/SDWebImageTransformer.h b/SDWebImage/SDWebImageTransformer.h index 8080e47f..a060e6a3 100644 --- a/SDWebImage/SDWebImageTransformer.h +++ b/SDWebImage/SDWebImageTransformer.h @@ -11,8 +11,7 @@ /** A transformer protocol to transform the image load from cache or from download. - For cache, it store the transformed image to memory cache if the memory cache missed (Default). - For download, it store the transformed image to disk cache and memory cache if the download finished (Default). + You can provide transformer to cache and manager (Through the `transformer` property or context option `SDWebImageContextCustomTransformer`). @note The transform process is called from a global queue in order to not to block the main queue. */ @@ -42,7 +41,7 @@ // Separator for different transformerKey, for example, `image.png` |> flip(YES,NO) |> rotate(pi/4,YES) => 'image-SDWebImageFlippingTransformer(1,0)-SDWebImageRotationTransformer(0.78539816339,1).png' FOUNDATION_EXPORT NSString * _Nonnull const SDWebImageTransformerKeySeparator; -// Pipeline transformer. Which you can bind multiple transformers together to let the image to be transformed one by one and generate the final image. +// Pipeline transformer. Which you can bind multiple transformers together to let the image to be transformed one by one in order and generate the final image. @interface SDWebImagePipelineTransformer : NSObject @property (nonatomic, copy, readonly, nonnull) NSArray> *transformers; From 8742e21fab0666afa8a084d12bce095be252bda3 Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Mon, 19 Feb 2018 20:10:14 +0800 Subject: [PATCH 04/13] Do not expose the separator because of extensibility, use a function instead --- SDWebImage/SDImageCache.m | 2 +- SDWebImage/SDWebImageManager.m | 2 +- SDWebImage/SDWebImageTransformer.h | 12 +++++++++--- SDWebImage/SDWebImageTransformer.m | 12 ++++++++++-- SDWebImage/UIImage+Transform.m | 8 ++++---- 5 files changed, 25 insertions(+), 11 deletions(-) diff --git a/SDWebImage/SDImageCache.m b/SDWebImage/SDImageCache.m index a16b4600..d790aef8 100644 --- a/SDWebImage/SDImageCache.m +++ b/SDWebImage/SDImageCache.m @@ -538,7 +538,7 @@ FOUNDATION_STATIC_INLINE NSUInteger SDCacheCostForImage(UIImage *image) { // grab the transformed disk image if transformer provided id transformer = [context valueForKey:SDWebImageContextCustomTransformer]; NSString *transformerKey = [transformer transformerKey]; - cacheKey = [[key stringByAppendingString:SDWebImageTransformerKeySeparator] stringByAppendingString:transformerKey]; + cacheKey = SDTransformedKeyForKey(key, transformerKey); } // decode image data only if in-memory cache missed diskImage = [self diskImageForKey:cacheKey data:diskData]; diff --git a/SDWebImage/SDWebImageManager.m b/SDWebImage/SDWebImageManager.m index c30a612d..f92ed841 100644 --- a/SDWebImage/SDWebImageManager.m +++ b/SDWebImage/SDWebImageManager.m @@ -255,7 +255,7 @@ UIImage *transformedImage = [transformer transformedImageWithImage:downloadedImage forKey:key]; if (transformedImage && finished) { NSString *transformerKey = [transformer transformerKey]; - NSString *cacheKey = [[key stringByAppendingString:SDWebImageTransformerKeySeparator] stringByAppendingString:transformerKey]; + NSString *cacheKey = SDTransformedKeyForKey(key, transformerKey); BOOL imageWasTransformed = ![transformedImage isEqual:downloadedImage]; NSData *cacheData; // pass nil if the image was transformed, so we can recalculate the data from the image diff --git a/SDWebImage/SDWebImageTransformer.h b/SDWebImage/SDWebImageTransformer.h index a060e6a3..624bbe4d 100644 --- a/SDWebImage/SDWebImageTransformer.h +++ b/SDWebImage/SDWebImageTransformer.h @@ -9,6 +9,15 @@ #import "SDWebImageCompat.h" #import "UIImage+Transform.h" +/** + Return the transformed cache key which applied with specify transformerKey. + + @param key The original cache key + @param transformerKey The transformer key from the transformer + @return The transformed cache key + */ +FOUNDATION_EXPORT NSString * _Nullable SDTransformedKeyForKey(NSString * _Nullable key, NSString * _Nonnull transformerKey); + /** A transformer protocol to transform the image load from cache or from download. You can provide transformer to cache and manager (Through the `transformer` property or context option `SDWebImageContextCustomTransformer`). @@ -38,9 +47,6 @@ #pragma mark - Pipeline -// Separator for different transformerKey, for example, `image.png` |> flip(YES,NO) |> rotate(pi/4,YES) => 'image-SDWebImageFlippingTransformer(1,0)-SDWebImageRotationTransformer(0.78539816339,1).png' -FOUNDATION_EXPORT NSString * _Nonnull const SDWebImageTransformerKeySeparator; - // Pipeline transformer. Which you can bind multiple transformers together to let the image to be transformed one by one in order and generate the final image. @interface SDWebImagePipelineTransformer : NSObject diff --git a/SDWebImage/SDWebImageTransformer.m b/SDWebImage/SDWebImageTransformer.m index 13da7fed..b7d63015 100644 --- a/SDWebImage/SDWebImageTransformer.m +++ b/SDWebImage/SDWebImageTransformer.m @@ -11,6 +11,16 @@ #import #endif +// Separator for different transformerKey, for example, `image.png` |> flip(YES,NO) |> rotate(pi/4,YES) => 'image-SDWebImageFlippingTransformer(1,0)-SDWebImageRotationTransformer(0.78539816339,1).png' +static NSString * const SDWebImageTransformerKeySeparator = @"-"; + +NSString * _Nullable SDTransformedKeyForKey(NSString * _Nullable key, NSString * _Nonnull transformerKey) { + if (!key || !transformerKey) { + return nil; + } + return [[key stringByAppendingString:SDWebImageTransformerKeySeparator] stringByAppendingString:transformerKey]; +} + @interface UIColor (Additions) @property (nonatomic, copy, readonly, nonnull) NSString *sd_hexString; @@ -50,8 +60,6 @@ @end -NSString * const SDWebImageTransformerKeySeparator = @"-"; - @interface SDWebImagePipelineTransformer () @property (nonatomic, copy, readwrite, nonnull) NSArray> *transformers; diff --git a/SDWebImage/UIImage+Transform.m b/SDWebImage/UIImage+Transform.m index ecacfd09..543ed2b0 100644 --- a/SDWebImage/UIImage+Transform.m +++ b/SDWebImage/UIImage+Transform.m @@ -151,13 +151,13 @@ static CGRect SDCGRectFitWithScaleMode(CGRect rect, CGSize size, SDImageScaleMod #if SD_MAC @interface NSBezierPath (Additions) -+ (instancetype)bezierPathWithRoundedRect:(NSRect)rect byRoundingCorners:(SDRectCorner)corners cornerRadius:(CGFloat)cornerRadius; ++ (instancetype)sd_bezierPathWithRoundedRect:(NSRect)rect byRoundingCorners:(SDRectCorner)corners cornerRadius:(CGFloat)cornerRadius; @end @implementation NSBezierPath (Additions) -+ (instancetype)bezierPathWithRoundedRect:(NSRect)rect byRoundingCorners:(SDRectCorner)corners cornerRadius:(CGFloat)cornerRadius { ++ (instancetype)sd_bezierPathWithRoundedRect:(NSRect)rect byRoundingCorners:(SDRectCorner)corners cornerRadius:(CGFloat)cornerRadius { NSBezierPath *path = [NSBezierPath bezierPath]; CGFloat maxCorner = MIN(NSWidth(rect), NSHeight(rect)) / 2; @@ -246,7 +246,7 @@ static CGRect SDCGRectFitWithScaleMode(CGRect rect, CGSize size, SDImageScaleMod #if SD_UIKIT || SD_WATCH UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectInset(rect, borderWidth, borderWidth) byRoundingCorners:corners cornerRadii:CGSizeMake(cornerRadius, cornerRadius)]; #else - NSBezierPath *path = [NSBezierPath bezierPathWithRoundedRect:CGRectInset(rect, borderWidth, borderWidth) byRoundingCorners:corners cornerRadius:cornerRadius]; + NSBezierPath *path = [NSBezierPath sd_bezierPathWithRoundedRect:CGRectInset(rect, borderWidth, borderWidth) byRoundingCorners:corners cornerRadius:cornerRadius]; #endif [path closePath]; @@ -263,7 +263,7 @@ static CGRect SDCGRectFitWithScaleMode(CGRect rect, CGSize size, SDImageScaleMod #if SD_UIKIT || SD_WATCH UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:strokeRect byRoundingCorners:corners cornerRadii:CGSizeMake(strokeRadius, strokeRadius)]; #else - NSBezierPath *path = [NSBezierPath bezierPathWithRoundedRect:strokeRect byRoundingCorners:corners cornerRadius:strokeRadius]; + NSBezierPath *path = [NSBezierPath sd_bezierPathWithRoundedRect:strokeRect byRoundingCorners:corners cornerRadius:strokeRadius]; #endif [path closePath]; From 01402c03692c0161ee0fdd92594b996c91b3ace3 Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Mon, 19 Feb 2018 23:56:08 +0800 Subject: [PATCH 05/13] Add one `colorAtPoint` to help user get the pixel color. Expose the category for `UIColor` and `NSBezierPath` because it can be used in common cases --- SDWebImage/SDWebImageTransformer.m | 39 ------- SDWebImage/UIImage+Transform.h | 30 +++++ SDWebImage/UIImage+Transform.m | 175 ++++++++++++++++++++++++++++- 3 files changed, 199 insertions(+), 45 deletions(-) diff --git a/SDWebImage/SDWebImageTransformer.m b/SDWebImage/SDWebImageTransformer.m index b7d63015..86db61a2 100644 --- a/SDWebImage/SDWebImageTransformer.m +++ b/SDWebImage/SDWebImageTransformer.m @@ -21,45 +21,6 @@ NSString * _Nullable SDTransformedKeyForKey(NSString * _Nullable key, NSString * return [[key stringByAppendingString:SDWebImageTransformerKeySeparator] stringByAppendingString:transformerKey]; } -@interface UIColor (Additions) - -@property (nonatomic, copy, readonly, nonnull) NSString *sd_hexString; - -@end - -@implementation UIColor (Additions) - -- (NSString *)sd_hexString { - CGFloat red, green, blue, alpha; -#if SD_UIKIT - if (![self getRed:&red green:&green blue:&blue alpha:&alpha]) { - [self getWhite:&red alpha:&alpha]; - green = red; - blue = red; - } -#else - @try { - [self getRed:&red green:&green blue:&blue alpha:&alpha]; - } - @catch (NSException *exception) { - [self getWhite:&red alpha:&alpha]; - green = red; - blue = red; - } -#endif - - red = roundf(red * 255.f); - green = roundf(green * 255.f); - blue = roundf(blue * 255.f); - alpha = roundf(alpha * 255.f); - - uint hex = ((uint)alpha << 24) | ((uint)red << 16) | ((uint)green << 8) | ((uint)blue); - - return [NSString stringWithFormat:@"0x%08x", hex]; -} - -@end - @interface SDWebImagePipelineTransformer () @property (nonatomic, copy, readwrite, nonnull) NSArray> *transformers; diff --git a/SDWebImage/UIImage+Transform.h b/SDWebImage/UIImage+Transform.h index c042977d..6165c53d 100644 --- a/SDWebImage/UIImage+Transform.h +++ b/SDWebImage/UIImage+Transform.h @@ -26,6 +26,28 @@ typedef NS_OPTIONS(NSUInteger, SDRectCorner) { }; #endif +#pragma mark - Useful category + +@interface UIColor (Additions) + +/** + Convenience way to get hex string from color. The output should always be 32-bit hex string like `0x00000000` + */ +@property (nonatomic, copy, readonly, nonnull) NSString *sd_hexString; + +@end + +#if SD_MAC +@interface NSBezierPath (Additions) + +/** + Convenience way to create a bezier path with the specify rouunding corners on macOS. Same as the one on `UIBezierPath`. + */ ++ (instancetype)sd_bezierPathWithRoundedRect:(NSRect)rect byRoundingCorners:(SDRectCorner)corners cornerRadius:(CGFloat)cornerRadius; + +@end +#endif + /** Provide some commen method for `UIImage`. Image process is based on Core Graphics and vImage. @@ -101,6 +123,14 @@ typedef NS_OPTIONS(NSUInteger, SDRectCorner) { */ - (nullable UIImage *)sd_tintedImageWithColor:(nonnull UIColor *)tintColor; +/** + Return the color at specify pixel. The postion is from the top-left to the bottom-right. And the color is always be RGBA format. + + @param point The position of pixel + @return The color for specify pixel, or nil if any error occur + */ +- (nullable UIColor *)sd_colorAtPoint:(CGPoint)point; + #pragma mark - Image Effect /** diff --git a/SDWebImage/UIImage+Transform.m b/SDWebImage/UIImage+Transform.m index 543ed2b0..430fe79b 100644 --- a/SDWebImage/UIImage+Transform.m +++ b/SDWebImage/UIImage+Transform.m @@ -35,9 +35,6 @@ static CGContextRef SDCGContextCreateARGBBitmapContext(CGSize size, BOOL opaque, static void SDGraphicsBeginImageContextWithOptions(CGSize size, BOOL opaque, CGFloat scale) { #if SD_UIKIT || SD_WATCH UIGraphicsBeginImageContextWithOptions(size, opaque, scale); - CGContextRef context = UIGraphicsGetCurrentContext(); - CGContextTranslateCTM(context, 0, -size.height); - CGContextScaleCTM(context, scale, -scale); #else CGContextRef context = SDCGContextCreateARGBBitmapContext(size, opaque, scale); if (!context) { @@ -148,13 +145,41 @@ static CGRect SDCGRectFitWithScaleMode(CGRect rect, CGSize size, SDImageScaleMod return rect; } -#if SD_MAC -@interface NSBezierPath (Additions) +@implementation UIColor (Additions) -+ (instancetype)sd_bezierPathWithRoundedRect:(NSRect)rect byRoundingCorners:(SDRectCorner)corners cornerRadius:(CGFloat)cornerRadius; +- (NSString *)sd_hexString { + CGFloat red, green, blue, alpha; +#if SD_UIKIT + if (![self getRed:&red green:&green blue:&blue alpha:&alpha]) { + [self getWhite:&red alpha:&alpha]; + green = red; + blue = red; + } +#else + @try { + [self getRed:&red green:&green blue:&blue alpha:&alpha]; + } + @catch (NSException *exception) { + [self getWhite:&red alpha:&alpha]; + green = red; + blue = red; + } +#endif + + red = roundf(red * 255.f); + green = roundf(green * 255.f); + blue = roundf(blue * 255.f); + alpha = roundf(alpha * 255.f); + + uint hex = ((uint)alpha << 24) | ((uint)red << 16) | ((uint)green << 8) | ((uint)blue); + + return [NSString stringWithFormat:@"0x%08x", hex]; +} @end +#if SD_MAC + @implementation NSBezierPath (Additions) + (instancetype)sd_bezierPathWithRoundedRect:(NSRect)rect byRoundingCorners:(SDRectCorner)corners cornerRadius:(CGFloat)cornerRadius { @@ -383,8 +408,146 @@ static CGRect SDCGRectFitWithScaleMode(CGRect rect, CGSize size, SDImageScaleMod return image; } +- (UIColor *)sd_colorAtPoint:(CGPoint)point { + if (!self) { + return nil; + } + CGImageRef imageRef = self.CGImage; + if (!imageRef) { + return nil; + } + + // Check point + CGFloat width = CGImageGetWidth(imageRef); + CGFloat height = CGImageGetHeight(imageRef); + if (point.x < 0 || point.y < 0 || point.x > width || point.y > height) { + return nil; + } + + // Get pixels + CGDataProviderRef provider = CGImageGetDataProvider(imageRef); + if (!provider) { + return nil; + } + CFDataRef data = CGDataProviderCopyData(provider); + if (!data) { + return nil; + } + + // Get pixel at point + size_t bytesPerRow = CGImageGetBytesPerRow(imageRef); // Actually should be ARGB8888, equal to width * 4(alpha) or 3(non-alpha) + size_t components = CGImageGetBitsPerPixel(imageRef) / CGImageGetBitsPerComponent(imageRef); + + CFRange range = CFRangeMake(bytesPerRow * point.y + components * point.x, 4); + if (CFDataGetLength(data) < range.location + range.length) { + return nil; + } + UInt8 pixel[4] = {0}; + CFDataGetBytes(data, range, pixel); + + // Convert to color + CGImageAlphaInfo alphaInfo = CGImageGetAlphaInfo(imageRef); + CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(imageRef); + CGFloat r = 0, g = 0, b = 0, a = 0; + + BOOL byteOrderNormal = NO; + switch (bitmapInfo & kCGBitmapByteOrderMask) { + case kCGBitmapByteOrderDefault: { + byteOrderNormal = YES; + } break; + case kCGBitmapByteOrder32Little: { + } break; + case kCGBitmapByteOrder32Big: { + byteOrderNormal = YES; + } break; + default: break; + } + switch (alphaInfo) { + case kCGImageAlphaPremultipliedFirst: { + if (byteOrderNormal) { + // ARGB8888 + a = pixel[0] / 255.0; + r = pixel[1] / 255.0; + g = pixel[2] / 255.0; + b = pixel[3] / 255.0; + } else { + // BGRA8888 + b = pixel[0] / 255.0; + g = pixel[1] / 255.0; + r = pixel[2] / 255.0; + a = pixel[3] / 255.0; + } + } + break; + case kCGImageAlphaPremultipliedLast: { + if (byteOrderNormal) { + // RGBA8888 + r = pixel[0] / 255.0; + g = pixel[1] / 255.0; + b = pixel[2] / 255.0; + a = pixel[3] / 255.0; + } else { + // ABGR8888 + a = pixel[0] / 255.0; + b = pixel[1] / 255.0; + g = pixel[2] / 255.0; + r = pixel[3] / 255.0; + } + } + break; + case kCGImageAlphaNone: { + if (byteOrderNormal) { + // RGB + r = pixel[0] / 255.0; + g = pixel[1] / 255.0; + b = pixel[2] / 255.0; + } else { + // BGR + b = pixel[0] / 255.0; + g = pixel[1] / 255.0; + r = pixel[2] / 255.0; + } + } + break; + case kCGImageAlphaNoneSkipLast: { + if (byteOrderNormal) { + // RGBX + r = pixel[0] / 255.0; + g = pixel[1] / 255.0; + b = pixel[2] / 255.0; + } else { + // XBGR + b = pixel[1] / 255.0; + g = pixel[2] / 255.0; + r = pixel[3] / 255.0; + } + } + break; + case kCGImageAlphaNoneSkipFirst: { + if (byteOrderNormal) { + // XRGB + r = pixel[1] / 255.0; + g = pixel[2] / 255.0; + b = pixel[3] / 255.0; + } else { + // BGRX + b = pixel[0] / 255.0; + g = pixel[1] / 255.0; + r = pixel[2] / 255.0; + } + } + break; + // iOS does not supports non-premultiplied alpha, so no these cases :) + default: + break; + } + + return [UIColor colorWithRed:r green:g blue:b alpha:a]; +} + #pragma mark - Image Effect +// We use vImage to do box convolve for performance. However, you can just use `CIFilter.CIBoxBlur`. For other blur effect, use any filter in `CICategoryBlur` - (UIImage *)sd_blurredImageWithRadius:(CGFloat)blurRadius { if (self.size.width < 1 || self.size.height < 1) { return nil; From d0df01bec1e00d6325805f94906a9868eb4675dc Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Mon, 19 Feb 2018 23:57:23 +0800 Subject: [PATCH 06/13] Complete the 8 tests for all image transform methods, well done --- Tests/Tests/SDCategoriesTests.m | 98 +++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) diff --git a/Tests/Tests/SDCategoriesTests.m b/Tests/Tests/SDCategoriesTests.m index b49378ed..5b814de4 100644 --- a/Tests/Tests/SDCategoriesTests.m +++ b/Tests/Tests/SDCategoriesTests.m @@ -15,9 +15,12 @@ #import #import #import +#import @interface SDCategoriesTests : SDTestCase +@property (nonatomic, strong) UIImage *testImage; + @end @implementation SDCategoriesTests @@ -65,8 +68,98 @@ expect(image).notTo.beNil(); } +// UIImage+Transform test is hard to write because it's more about visual effect. Current it's tied to the `TestImage.png`, please keep that image or write new test with new image +- (void)test05UIImageTransformResize { + CGSize size = CGSizeMake(200, 100); + UIImage *resizedImage = [self.testImage sd_resizedImageWithSize:size scaleMode:SDImageScaleModeFill]; + expect(CGSizeEqualToSize(resizedImage.size, size)).beTruthy(); +} + +- (void)test06UIImageTransformCrop { + CGRect rect = CGRectMake(50, 50, 200, 200); + UIImage *croppedImage = [self.testImage sd_croppedImageWithRect:rect]; + expect(CGSizeEqualToSize(croppedImage.size, CGSizeMake(200, 200))).beTruthy(); + UIColor *startColor = [croppedImage sd_colorAtPoint:CGPointZero]; + expect([startColor.sd_hexString isEqualToString:@"0x00000000"]).beTruthy(); +} + +- (void)test07UIImageTransformRoundedCorner { + CGFloat radius = 50; + UIRectCorner corners = UIRectCornerAllCorners; + CGFloat borderWidth = 1; + UIColor *borderCoder = [UIColor blackColor]; + UIImage *roundedCornerImage = [self.testImage sd_roundedCornerImageWithRadius:radius corners:corners borderWidth:borderWidth borderColor:borderCoder]; + expect(CGSizeEqualToSize(roundedCornerImage.size, CGSizeMake(300, 300))).beTruthy(); + UIColor *startColor = [roundedCornerImage sd_colorAtPoint:CGPointZero]; + expect([startColor.sd_hexString isEqualToString:@"0x00000000"]).beTruthy(); + // Check the left center pixel, should be border :) + UIColor *checkBorderColor = [roundedCornerImage sd_colorAtPoint:CGPointMake(1, 150)]; + expect([checkBorderColor.sd_hexString isEqualToString:borderCoder.sd_hexString]).beTruthy(); +} + +- (void)test08UIImageTransformRotate { + CGFloat angle = M_PI_4; + UIImage *rotatedImage = [self.testImage sd_rotatedImageWithAngle:angle fitSize:NO]; + // Not fit size and no change + expect(CGSizeEqualToSize(rotatedImage.size, self.testImage.size)).beTruthy(); + // Fit size, may change size + rotatedImage = [self.testImage sd_rotatedImageWithAngle:angle fitSize:YES]; + CGSize rotatedSize = CGSizeMake(floor(300 * 1.414), floor(300 * 1.414)); // 45º, square length * sqrt(2) + expect(CGSizeEqualToSize(rotatedImage.size, rotatedSize)).beTruthy(); + rotatedImage = [self.testImage sd_rotatedImageWithAngle:angle fitSize:NO]; +} + +- (void)test09UIImageTransformFlip { + BOOL horizontal = YES; + BOOL vertical = YES; + UIImage *flippedImage = [self.testImage sd_flippedImageWithHorizontal:horizontal vertical:vertical]; + expect(CGSizeEqualToSize(flippedImage.size, self.testImage.size)).beTruthy(); +} + +- (void)test10UIImageTransformTint { + UIColor *tintColor = [UIColor blackColor]; + UIImage *tintedImage = [self.testImage sd_tintedImageWithColor:tintColor]; + expect(CGSizeEqualToSize(tintedImage.size, self.testImage.size)).beTruthy(); + // Check center color, should keep clear + UIColor *centerColor = [tintedImage sd_colorAtPoint:CGPointMake(150, 150)]; + expect([centerColor.sd_hexString isEqualToString:@"0x00000000"]); + // Check left color, should be tinted + UIColor *leftColor = [tintedImage sd_colorAtPoint:CGPointMake(80, 150)]; + expect([leftColor.sd_hexString isEqualToString:tintColor.sd_hexString]); +} + +- (void)test11UIImageTransformBlur { + CGFloat radius = 50; + UIImage *blurredImage = [self.testImage sd_blurredImageWithRadius:radius]; + expect(CGSizeEqualToSize(blurredImage.size, self.testImage.size)).beTruthy(); + // Check left color, should be blurred + UIColor *leftColor = [blurredImage sd_colorAtPoint:CGPointMake(80, 150)]; + // Hard-code from the output + UIColor *expectedColor = [UIColor colorWithRed:0.431373 green:0.101961 blue:0.0901961 alpha:0.729412]; + expect([leftColor.sd_hexString isEqualToString:expectedColor.sd_hexString]); +} + +- (void)test12UIImageTransformFilter { + // Invert color filter + CIFilter *filter = [CIFilter filterWithName:@"CIColorInvert"]; + UIImage *filteredImage = [self.testImage sd_filteredImageWithFilter:filter]; + expect(CGSizeEqualToSize(filteredImage.size, self.testImage.size)).beTruthy(); + // Check left color, should be inverted + UIColor *leftColor = [filteredImage sd_colorAtPoint:CGPointMake(80, 150)]; + // Hard-code from the output + UIColor *expectedColor = [UIColor colorWithRed:0.85098 green:0.992157 blue:0.992157 alpha:1]; + expect([leftColor.sd_hexString isEqualToString:expectedColor.sd_hexString]); +} + #pragma mark - Helper +- (UIImage *)testImage { + if (!_testImage) { + _testImage = [UIImage imageWithContentsOfFile:[self testPNGPath]]; + } + return _testImage; +} + - (NSString *)testJPEGPath { NSBundle *testBundle = [NSBundle bundleForClass:[self class]]; return [testBundle pathForResource:@"TestImage" ofType:@"jpg"]; @@ -82,4 +175,9 @@ return [testBundle pathForResource:@"TestImageStatic" ofType:@"webp"]; } +- (NSString *)testPNGPath { + NSBundle *testBundle = [NSBundle bundleForClass:[self class]]; + return [testBundle pathForResource:@"TestImage" ofType:@"png"]; +} + @end From aea81791ec6dee08f6c21c561aab7f15682a1259 Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Tue, 20 Feb 2018 00:04:31 +0800 Subject: [PATCH 07/13] Fix the test on macOS. Fix the issue when using rounded corner --- SDWebImage/UIImage+Transform.h | 2 +- SDWebImage/UIImage+Transform.m | 32 +++++--------------------------- Tests/Tests/SDCategoriesTests.m | 9 +++++++-- 3 files changed, 13 insertions(+), 30 deletions(-) diff --git a/SDWebImage/UIImage+Transform.h b/SDWebImage/UIImage+Transform.h index 6165c53d..44d1e807 100644 --- a/SDWebImage/UIImage+Transform.h +++ b/SDWebImage/UIImage+Transform.h @@ -43,7 +43,7 @@ typedef NS_OPTIONS(NSUInteger, SDRectCorner) { /** Convenience way to create a bezier path with the specify rouunding corners on macOS. Same as the one on `UIBezierPath`. */ -+ (instancetype)sd_bezierPathWithRoundedRect:(NSRect)rect byRoundingCorners:(SDRectCorner)corners cornerRadius:(CGFloat)cornerRadius; ++ (nonnull instancetype)sd_bezierPathWithRoundedRect:(NSRect)rect byRoundingCorners:(SDRectCorner)corners cornerRadius:(CGFloat)cornerRadius; @end #endif diff --git a/SDWebImage/UIImage+Transform.m b/SDWebImage/UIImage+Transform.m index 430fe79b..076a23c9 100644 --- a/SDWebImage/UIImage+Transform.m +++ b/SDWebImage/UIImage+Transform.m @@ -81,29 +81,6 @@ static UIImage * SDGraphicsGetImageFromCurrentImageContext(void) { #endif } -static SDRectCorner SDRectCornerConvertCounterClockwise(SDRectCorner corners) { -#if SD_UIKIT || SD_WATCH - if (corners != UIRectCornerAllCorners) { - UIRectCorner tmp = 0; - if (corners & UIRectCornerTopLeft) tmp |= UIRectCornerBottomLeft; - if (corners & UIRectCornerTopRight) tmp |= UIRectCornerBottomRight; - if (corners & UIRectCornerBottomLeft) tmp |= UIRectCornerTopLeft; - if (corners & UIRectCornerBottomRight) tmp |= UIRectCornerTopRight; - corners = tmp; - } -#else - if (corners != SDRectCornerAllCorners) { - SDRectCorner tmp = 0; - if (corners & SDRectCornerTopLeft) tmp |= SDRectCornerBottomLeft; - if (corners & SDRectCornerTopRight) tmp |= SDRectCornerBottomRight; - if (corners & SDRectCornerBottomLeft) tmp |= SDRectCornerTopLeft; - if (corners & SDRectCornerBottomRight) tmp |= SDRectCornerTopRight; - corners = tmp; - } -#endif - return corners; -} - static CGRect SDCGRectFitWithScaleMode(CGRect rect, CGSize size, SDImageScaleMode scaleMode) { rect = CGRectStandardize(rect); size.width = size.width < 0 ? -size.width : size.width; @@ -261,7 +238,6 @@ static CGRect SDCGRectFitWithScaleMode(CGRect rect, CGSize size, SDImageScaleMod - (UIImage *)sd_roundedCornerImageWithRadius:(CGFloat)cornerRadius corners:(SDRectCorner)corners borderWidth:(CGFloat)borderWidth borderColor:(UIColor *)borderColor { if (!self.CGImage) return nil; - corners = SDRectCornerConvertCounterClockwise(corners); SDGraphicsBeginImageContextWithOptions(self.size, NO, self.scale); CGContextRef context = SDGraphicsGetCurrentContext(); CGRect rect = CGRectMake(0, 0, self.size.width, self.size.height); @@ -463,7 +439,8 @@ static CGRect SDCGRectFitWithScaleMode(CGRect rect, CGSize size, SDImageScaleMod default: break; } switch (alphaInfo) { - case kCGImageAlphaPremultipliedFirst: { + case kCGImageAlphaPremultipliedFirst: + case kCGImageAlphaFirst: { if (byteOrderNormal) { // ARGB8888 a = pixel[0] / 255.0; @@ -479,7 +456,8 @@ static CGRect SDCGRectFitWithScaleMode(CGRect rect, CGSize size, SDImageScaleMod } } break; - case kCGImageAlphaPremultipliedLast: { + case kCGImageAlphaPremultipliedLast: + case kCGImageAlphaLast: { if (byteOrderNormal) { // RGBA8888 r = pixel[0] / 255.0; @@ -537,7 +515,7 @@ static CGRect SDCGRectFitWithScaleMode(CGRect rect, CGSize size, SDImageScaleMod } } break; - // iOS does not supports non-premultiplied alpha, so no these cases :) + case kCGImageAlphaOnly: default: break; } diff --git a/Tests/Tests/SDCategoriesTests.m b/Tests/Tests/SDCategoriesTests.m index 5b814de4..486f9a36 100644 --- a/Tests/Tests/SDCategoriesTests.m +++ b/Tests/Tests/SDCategoriesTests.m @@ -16,6 +16,7 @@ #import #import #import +#import @interface SDCategoriesTests : SDTestCase @@ -85,7 +86,11 @@ - (void)test07UIImageTransformRoundedCorner { CGFloat radius = 50; - UIRectCorner corners = UIRectCornerAllCorners; +#if SD_UIKIT + SDRectCorner corners = UIRectCornerAllCorners; +#else + SDRectCorner corners = SDRectCornerAllCorners; +#endif CGFloat borderWidth = 1; UIColor *borderCoder = [UIColor blackColor]; UIImage *roundedCornerImage = [self.testImage sd_roundedCornerImageWithRadius:radius corners:corners borderWidth:borderWidth borderColor:borderCoder]; @@ -155,7 +160,7 @@ - (UIImage *)testImage { if (!_testImage) { - _testImage = [UIImage imageWithContentsOfFile:[self testPNGPath]]; + _testImage = [[UIImage alloc] initWithContentsOfFile:[self testPNGPath]]; } return _testImage; } From 3e3ec8d513eaeae51336c18ed679c02ffe919c62 Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Tue, 20 Feb 2018 01:37:30 +0800 Subject: [PATCH 08/13] Change the hex color from 0x00000000 format to #00000000 --- SDWebImage/UIImage+Transform.h | 2 +- SDWebImage/UIImage+Transform.m | 2 +- Tests/Tests/SDCategoriesTests.m | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/SDWebImage/UIImage+Transform.h b/SDWebImage/UIImage+Transform.h index 44d1e807..87a8a969 100644 --- a/SDWebImage/UIImage+Transform.h +++ b/SDWebImage/UIImage+Transform.h @@ -31,7 +31,7 @@ typedef NS_OPTIONS(NSUInteger, SDRectCorner) { @interface UIColor (Additions) /** - Convenience way to get hex string from color. The output should always be 32-bit hex string like `0x00000000` + Convenience way to get hex string from color. The output should always be 32-bit hex string like `#00000000`. */ @property (nonatomic, copy, readonly, nonnull) NSString *sd_hexString; diff --git a/SDWebImage/UIImage+Transform.m b/SDWebImage/UIImage+Transform.m index 076a23c9..5bcb6c83 100644 --- a/SDWebImage/UIImage+Transform.m +++ b/SDWebImage/UIImage+Transform.m @@ -150,7 +150,7 @@ static CGRect SDCGRectFitWithScaleMode(CGRect rect, CGSize size, SDImageScaleMod uint hex = ((uint)alpha << 24) | ((uint)red << 16) | ((uint)green << 8) | ((uint)blue); - return [NSString stringWithFormat:@"0x%08x", hex]; + return [NSString stringWithFormat:@"#%08x", hex]; } @end diff --git a/Tests/Tests/SDCategoriesTests.m b/Tests/Tests/SDCategoriesTests.m index 486f9a36..a788173a 100644 --- a/Tests/Tests/SDCategoriesTests.m +++ b/Tests/Tests/SDCategoriesTests.m @@ -81,7 +81,7 @@ UIImage *croppedImage = [self.testImage sd_croppedImageWithRect:rect]; expect(CGSizeEqualToSize(croppedImage.size, CGSizeMake(200, 200))).beTruthy(); UIColor *startColor = [croppedImage sd_colorAtPoint:CGPointZero]; - expect([startColor.sd_hexString isEqualToString:@"0x00000000"]).beTruthy(); + expect([startColor.sd_hexString isEqualToString:[UIColor clearColor].sd_hexString]).beTruthy(); } - (void)test07UIImageTransformRoundedCorner { @@ -96,7 +96,7 @@ UIImage *roundedCornerImage = [self.testImage sd_roundedCornerImageWithRadius:radius corners:corners borderWidth:borderWidth borderColor:borderCoder]; expect(CGSizeEqualToSize(roundedCornerImage.size, CGSizeMake(300, 300))).beTruthy(); UIColor *startColor = [roundedCornerImage sd_colorAtPoint:CGPointZero]; - expect([startColor.sd_hexString isEqualToString:@"0x00000000"]).beTruthy(); + expect([startColor.sd_hexString isEqualToString:[UIColor clearColor].sd_hexString]).beTruthy(); // Check the left center pixel, should be border :) UIColor *checkBorderColor = [roundedCornerImage sd_colorAtPoint:CGPointMake(1, 150)]; expect([checkBorderColor.sd_hexString isEqualToString:borderCoder.sd_hexString]).beTruthy(); @@ -127,7 +127,7 @@ expect(CGSizeEqualToSize(tintedImage.size, self.testImage.size)).beTruthy(); // Check center color, should keep clear UIColor *centerColor = [tintedImage sd_colorAtPoint:CGPointMake(150, 150)]; - expect([centerColor.sd_hexString isEqualToString:@"0x00000000"]); + expect([centerColor.sd_hexString isEqualToString:[UIColor clearColor].sd_hexString]); // Check left color, should be tinted UIColor *leftColor = [tintedImage sd_colorAtPoint:CGPointMake(80, 150)]; expect([leftColor.sd_hexString isEqualToString:tintColor.sd_hexString]); From 57408d83132e358bcdcd9731b86099947aa2c383 Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Wed, 21 Feb 2018 20:25:30 +0800 Subject: [PATCH 09/13] Fix the potential leak of CFDataRef --- SDWebImage/UIImage+Transform.h | 3 ++- SDWebImage/UIImage+Transform.m | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/SDWebImage/UIImage+Transform.h b/SDWebImage/UIImage+Transform.h index 87a8a969..cbdbc212 100644 --- a/SDWebImage/UIImage+Transform.h +++ b/SDWebImage/UIImage+Transform.h @@ -124,7 +124,8 @@ typedef NS_OPTIONS(NSUInteger, SDRectCorner) { - (nullable UIImage *)sd_tintedImageWithColor:(nonnull UIColor *)tintColor; /** - Return the color at specify pixel. The postion is from the top-left to the bottom-right. And the color is always be RGBA format. + Return the color at specify pixel. The point is from the top-left to the bottom-right and 0-based. The returned the color is always be RGBA format. + @note The point's x/y should not be smaller than 0, or greater than or equal to width/height. @param point The position of pixel @return The color for specify pixel, or nil if any error occur diff --git a/SDWebImage/UIImage+Transform.m b/SDWebImage/UIImage+Transform.m index 5bcb6c83..7e66680a 100644 --- a/SDWebImage/UIImage+Transform.m +++ b/SDWebImage/UIImage+Transform.m @@ -396,7 +396,7 @@ static CGRect SDCGRectFitWithScaleMode(CGRect rect, CGSize size, SDImageScaleMod // Check point CGFloat width = CGImageGetWidth(imageRef); CGFloat height = CGImageGetHeight(imageRef); - if (point.x < 0 || point.y < 0 || point.x > width || point.y > height) { + if (point.x < 0 || point.y < 0 || point.x >= width || point.y >= height) { return nil; } @@ -411,15 +411,17 @@ static CGRect SDCGRectFitWithScaleMode(CGRect rect, CGSize size, SDImageScaleMod } // Get pixel at point - size_t bytesPerRow = CGImageGetBytesPerRow(imageRef); // Actually should be ARGB8888, equal to width * 4(alpha) or 3(non-alpha) + size_t bytesPerRow = CGImageGetBytesPerRow(imageRef); size_t components = CGImageGetBitsPerPixel(imageRef) / CGImageGetBitsPerComponent(imageRef); CFRange range = CFRangeMake(bytesPerRow * point.y + components * point.x, 4); if (CFDataGetLength(data) < range.location + range.length) { + CFRelease(data); return nil; } UInt8 pixel[4] = {0}; CFDataGetBytes(data, range, pixel); + CFRelease(data); // Convert to color CGImageAlphaInfo alphaInfo = CGImageGetAlphaInfo(imageRef); From a2076d362e2a3bda694f86c29a59199ee044b85e Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Wed, 21 Feb 2018 20:41:05 +0800 Subject: [PATCH 10/13] Add sd_colorsWithRect method and test, treat RGB color with default alpha 1.0 --- SDWebImage/UIImage+Transform.h | 19 ++- SDWebImage/UIImage+Transform.m | 263 ++++++++++++++++++++------------ Tests/Tests/SDCategoriesTests.m | 8 + 3 files changed, 188 insertions(+), 102 deletions(-) diff --git a/SDWebImage/UIImage+Transform.h b/SDWebImage/UIImage+Transform.h index cbdbc212..f02a73d6 100644 --- a/SDWebImage/UIImage+Transform.h +++ b/SDWebImage/UIImage+Transform.h @@ -31,7 +31,7 @@ typedef NS_OPTIONS(NSUInteger, SDRectCorner) { @interface UIColor (Additions) /** - Convenience way to get hex string from color. The output should always be 32-bit hex string like `#00000000`. + Convenience way to get hex string from color. The output should always be 32-bit RGBA hex string like `#00000000`. */ @property (nonatomic, copy, readonly, nonnull) NSString *sd_hexString; @@ -116,22 +116,33 @@ typedef NS_OPTIONS(NSUInteger, SDRectCorner) { #pragma mark - Image Blending /** - Return a tinted image in alpha channel with the given color. + Return a tinted image with the given color. This actually use alpha blending of current image and the tint color. - @param tintColor The color. + @param tintColor The tint color. @return The new image with the tint color. */ - (nullable UIImage *)sd_tintedImageWithColor:(nonnull UIColor *)tintColor; /** - Return the color at specify pixel. The point is from the top-left to the bottom-right and 0-based. The returned the color is always be RGBA format. + Return the pixel color at specify position. The point is from the top-left to the bottom-right and 0-based. The returned the color is always be RGBA format. The image must be CG-based. @note The point's x/y should not be smaller than 0, or greater than or equal to width/height. + @note The overhead of object creation means this method is best suited for infrequent color sampling. For heavy image processing, grab the raw bitmap data and process yourself. @param point The position of pixel @return The color for specify pixel, or nil if any error occur */ - (nullable UIColor *)sd_colorAtPoint:(CGPoint)point; +/** + Return the pixel color array with specify rectangle. The rect is from the top-left to the bottom-right and 0-based. The returned the color is always be RGBA format. The image must be CG-based. + @note The rect's width/height should not be smaller than or equal to 0. The minX/minY should not be smaller than 0. The maxX/maxY should not be greater than width/height. Attention this limit is different from point(point: (0,0) like rect: (0, 0, 1, 1)) + @note The overhead of object creation means this method is best suited for infrequent color sampling. For heavy image processing, grab the raw bitmap data and process yourself. + + @param rect The rectangle of pixels + @return The color array for specify pixels, or nil if any error occur + */ +- (nullable NSArray *)sd_colorsWithRect:(CGRect)rect; + #pragma mark - Image Effect /** diff --git a/SDWebImage/UIImage+Transform.m b/SDWebImage/UIImage+Transform.m index 7e66680a..4d06a67a 100644 --- a/SDWebImage/UIImage+Transform.m +++ b/SDWebImage/UIImage+Transform.m @@ -81,7 +81,7 @@ static UIImage * SDGraphicsGetImageFromCurrentImageContext(void) { #endif } -static CGRect SDCGRectFitWithScaleMode(CGRect rect, CGSize size, SDImageScaleMode scaleMode) { +static inline CGRect SDCGRectFitWithScaleMode(CGRect rect, CGSize size, SDImageScaleMode scaleMode) { rect = CGRectStandardize(rect); size.width = size.width < 0 ? -size.width : size.width; size.height = size.height < 0 ? -size.height : size.height; @@ -122,6 +122,109 @@ static CGRect SDCGRectFitWithScaleMode(CGRect rect, CGSize size, SDImageScaleMod return rect; } +static inline UIColor * SDGetColorFromPixel(Pixel_8888 pixel, CGBitmapInfo bitmapInfo) { + // Get alpha info, byteOrder info + CGImageAlphaInfo alphaInfo = bitmapInfo & kCGBitmapAlphaInfoMask; + CGImageByteOrderInfo byteOrderInfo = bitmapInfo & kCGBitmapByteOrderMask; + CGFloat r = 0, g = 0, b = 0, a = 255.0; + + BOOL byteOrderNormal = NO; + switch (byteOrderInfo) { + case kCGBitmapByteOrderDefault: { + byteOrderNormal = YES; + } break; + case kCGBitmapByteOrder32Little: { + } break; + case kCGBitmapByteOrder32Big: { + byteOrderNormal = YES; + } break; + default: break; + } + switch (alphaInfo) { + case kCGImageAlphaPremultipliedFirst: + case kCGImageAlphaFirst: { + if (byteOrderNormal) { + // ARGB8888 + a = pixel[0] / 255.0; + r = pixel[1] / 255.0; + g = pixel[2] / 255.0; + b = pixel[3] / 255.0; + } else { + // BGRA8888 + b = pixel[0] / 255.0; + g = pixel[1] / 255.0; + r = pixel[2] / 255.0; + a = pixel[3] / 255.0; + } + } + break; + case kCGImageAlphaPremultipliedLast: + case kCGImageAlphaLast: { + if (byteOrderNormal) { + // RGBA8888 + r = pixel[0] / 255.0; + g = pixel[1] / 255.0; + b = pixel[2] / 255.0; + a = pixel[3] / 255.0; + } else { + // ABGR8888 + a = pixel[0] / 255.0; + b = pixel[1] / 255.0; + g = pixel[2] / 255.0; + r = pixel[3] / 255.0; + } + } + break; + case kCGImageAlphaNone: { + if (byteOrderNormal) { + // RGB + r = pixel[0] / 255.0; + g = pixel[1] / 255.0; + b = pixel[2] / 255.0; + } else { + // BGR + b = pixel[0] / 255.0; + g = pixel[1] / 255.0; + r = pixel[2] / 255.0; + } + } + break; + case kCGImageAlphaNoneSkipLast: { + if (byteOrderNormal) { + // RGBX + r = pixel[0] / 255.0; + g = pixel[1] / 255.0; + b = pixel[2] / 255.0; + } else { + // XBGR + b = pixel[1] / 255.0; + g = pixel[2] / 255.0; + r = pixel[3] / 255.0; + } + } + break; + case kCGImageAlphaNoneSkipFirst: { + if (byteOrderNormal) { + // XRGB + r = pixel[1] / 255.0; + g = pixel[2] / 255.0; + b = pixel[3] / 255.0; + } else { + // BGRX + b = pixel[0] / 255.0; + g = pixel[1] / 255.0; + r = pixel[2] / 255.0; + } + } + break; + case kCGImageAlphaOnly: + default: + break; + } + + return [UIColor colorWithRed:r green:g blue:b alpha:a]; +} + @implementation UIColor (Additions) - (NSString *)sd_hexString { @@ -419,115 +522,79 @@ static CGRect SDCGRectFitWithScaleMode(CGRect rect, CGSize size, SDImageScaleMod CFRelease(data); return nil; } - UInt8 pixel[4] = {0}; + Pixel_8888 pixel = {0}; CFDataGetBytes(data, range, pixel); CFRelease(data); // Convert to color - CGImageAlphaInfo alphaInfo = CGImageGetAlphaInfo(imageRef); CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(imageRef); - CGFloat r = 0, g = 0, b = 0, a = 0; - - BOOL byteOrderNormal = NO; - switch (bitmapInfo & kCGBitmapByteOrderMask) { - case kCGBitmapByteOrderDefault: { - byteOrderNormal = YES; - } break; - case kCGBitmapByteOrder32Little: { - } break; - case kCGBitmapByteOrder32Big: { - byteOrderNormal = YES; - } break; - default: break; + return SDGetColorFromPixel(pixel, bitmapInfo); +} + +- (NSArray *)sd_colorsWithRect:(CGRect)rect { + if (!self) { + return nil; } - switch (alphaInfo) { - case kCGImageAlphaPremultipliedFirst: - case kCGImageAlphaFirst: { - if (byteOrderNormal) { - // ARGB8888 - a = pixel[0] / 255.0; - r = pixel[1] / 255.0; - g = pixel[2] / 255.0; - b = pixel[3] / 255.0; - } else { - // BGRA8888 - b = pixel[0] / 255.0; - g = pixel[1] / 255.0; - r = pixel[2] / 255.0; - a = pixel[3] / 255.0; - } - } - break; - case kCGImageAlphaPremultipliedLast: - case kCGImageAlphaLast: { - if (byteOrderNormal) { - // RGBA8888 - r = pixel[0] / 255.0; - g = pixel[1] / 255.0; - b = pixel[2] / 255.0; - a = pixel[3] / 255.0; - } else { - // ABGR8888 - a = pixel[0] / 255.0; - b = pixel[1] / 255.0; - g = pixel[2] / 255.0; - r = pixel[3] / 255.0; - } - } - break; - case kCGImageAlphaNone: { - if (byteOrderNormal) { - // RGB - r = pixel[0] / 255.0; - g = pixel[1] / 255.0; - b = pixel[2] / 255.0; - } else { - // BGR - b = pixel[0] / 255.0; - g = pixel[1] / 255.0; - r = pixel[2] / 255.0; - } - } - break; - case kCGImageAlphaNoneSkipLast: { - if (byteOrderNormal) { - // RGBX - r = pixel[0] / 255.0; - g = pixel[1] / 255.0; - b = pixel[2] / 255.0; - } else { - // XBGR - b = pixel[1] / 255.0; - g = pixel[2] / 255.0; - r = pixel[3] / 255.0; - } - } - break; - case kCGImageAlphaNoneSkipFirst: { - if (byteOrderNormal) { - // XRGB - r = pixel[1] / 255.0; - g = pixel[2] / 255.0; - b = pixel[3] / 255.0; - } else { - // BGRX - b = pixel[0] / 255.0; - g = pixel[1] / 255.0; - r = pixel[2] / 255.0; - } - } - break; - case kCGImageAlphaOnly: - default: - break; + CGImageRef imageRef = self.CGImage; + if (!imageRef) { + return nil; } - return [UIColor colorWithRed:r green:g blue:b alpha:a]; + // Check rect + CGFloat width = CGImageGetWidth(imageRef); + CGFloat height = CGImageGetHeight(imageRef); + if (CGRectGetWidth(rect) <= 0 || CGRectGetHeight(rect) <= 0 || CGRectGetMinX(rect) < 0 || CGRectGetMinY(rect) < 0 || CGRectGetMaxX(rect) > width || CGRectGetMaxY(rect) > height) { + return nil; + } + + // Get pixels + CGDataProviderRef provider = CGImageGetDataProvider(imageRef); + if (!provider) { + return nil; + } + CFDataRef data = CGDataProviderCopyData(provider); + if (!data) { + return nil; + } + + // Get pixels with rect + size_t bytesPerRow = CGImageGetBytesPerRow(imageRef); + size_t components = CGImageGetBitsPerPixel(imageRef) / CGImageGetBitsPerComponent(imageRef); + + size_t start = bytesPerRow * CGRectGetMinY(rect) + components * CGRectGetMinX(rect); + size_t end = bytesPerRow * (CGRectGetMaxY(rect) - 1) + components * CGRectGetMaxX(rect); + if (CFDataGetLength(data) < (CFIndex)end) { + CFRelease(data); + return nil; + } + + const UInt8 *pixels = CFDataGetBytePtr(data); + size_t row = CGRectGetMinY(rect); + size_t col = CGRectGetMaxX(rect); + + // Convert to color + CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(imageRef); + NSMutableArray *colors = [NSMutableArray arrayWithCapacity:CGRectGetWidth(rect) * CGRectGetHeight(rect)]; + for (size_t index = start; index < end; index += 4) { + if (index >= row * bytesPerRow + col * components) { + // Index beyond the end of current row, go next row + row++; + index = row * bytesPerRow + CGRectGetMinX(rect) * components; + index -= 4; + continue; + } + Pixel_8888 pixel = {pixels[index], pixels[index+1], pixels[index+2], pixels[index+3]}; + UIColor *color = SDGetColorFromPixel(pixel, bitmapInfo); + [colors addObject:color]; + } + CFRelease(data); + + return [colors copy]; } #pragma mark - Image Effect -// We use vImage to do box convolve for performance. However, you can just use `CIFilter.CIBoxBlur`. For other blur effect, use any filter in `CICategoryBlur` +// We use vImage to do box convolve for performance and support for watchOS. However, you can just use `CIFilter.CIBoxBlur`. For other blur effect, use any filter in `CICategoryBlur` - (UIImage *)sd_blurredImageWithRadius:(CGFloat)blurRadius { if (self.size.width < 1 || self.size.height < 1) { return nil; diff --git a/Tests/Tests/SDCategoriesTests.m b/Tests/Tests/SDCategoriesTests.m index a788173a..04247172 100644 --- a/Tests/Tests/SDCategoriesTests.m +++ b/Tests/Tests/SDCategoriesTests.m @@ -119,6 +119,14 @@ BOOL vertical = YES; UIImage *flippedImage = [self.testImage sd_flippedImageWithHorizontal:horizontal vertical:vertical]; expect(CGSizeEqualToSize(flippedImage.size, self.testImage.size)).beTruthy(); + // Test pixel colors method here + UIColor *checkColor = [flippedImage sd_colorAtPoint:CGPointMake(75, 75)]; + expect(checkColor); + NSArray *checkColors = [flippedImage sd_colorsWithRect:CGRectMake(75, 75, 10, 10)]; // Rect are all same color + expect(checkColors.count).to.equal(10 * 10); + for (UIColor *color in checkColors) { + expect([color isEqual:checkColor]).to.beTruthy(); + } } - (void)test10UIImageTransformTint { From 67285ee722785229d115f0afd0becc2ced86f2d3 Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Sat, 17 Mar 2018 20:56:20 +0800 Subject: [PATCH 11/13] Add the test for transformer property --- SDWebImage/UIImage+Transform.h | 2 +- .../project.pbxproj | 8 +++++++ Tests/Tests/SDWebImageManagerTests.m | 20 +++++++++++++++++ Tests/Tests/SDWebImageTestTransformer.h | 16 ++++++++++++++ Tests/Tests/SDWebImageTestTransformer.m | 22 +++++++++++++++++++ 5 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 Tests/Tests/SDWebImageTestTransformer.h create mode 100644 Tests/Tests/SDWebImageTestTransformer.m diff --git a/SDWebImage/UIImage+Transform.h b/SDWebImage/UIImage+Transform.h index f02a73d6..c5d64549 100644 --- a/SDWebImage/UIImage+Transform.h +++ b/SDWebImage/UIImage+Transform.h @@ -41,7 +41,7 @@ typedef NS_OPTIONS(NSUInteger, SDRectCorner) { @interface NSBezierPath (Additions) /** - Convenience way to create a bezier path with the specify rouunding corners on macOS. Same as the one on `UIBezierPath`. + Convenience way to create a bezier path with the specify rounding corners on macOS. Same as the one on `UIBezierPath`. */ + (nonnull instancetype)sd_bezierPathWithRoundedRect:(NSRect)rect byRoundingCorners:(SDRectCorner)corners cornerRadius:(CGFloat)cornerRadius; diff --git a/Tests/SDWebImage Tests.xcodeproj/project.pbxproj b/Tests/SDWebImage Tests.xcodeproj/project.pbxproj index b181c4a8..790c3e7e 100644 --- a/Tests/SDWebImage Tests.xcodeproj/project.pbxproj +++ b/Tests/SDWebImage Tests.xcodeproj/project.pbxproj @@ -12,6 +12,8 @@ 2D7AF0601F329763000083C2 /* SDTestCase.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D7AF05F1F329763000083C2 /* SDTestCase.m */; }; 321259EC1F39E3240096FE0E /* TestImageStatic.webp in Resources */ = {isa = PBXBuildFile; fileRef = 321259EB1F39E3240096FE0E /* TestImageStatic.webp */; }; 321259EE1F39E4110096FE0E /* TestImageAnimated.webp in Resources */ = {isa = PBXBuildFile; fileRef = 321259ED1F39E4110096FE0E /* TestImageAnimated.webp */; }; + 3264FF2F205D42CB00F6BD48 /* SDWebImageTestTransformer.m in Sources */ = {isa = PBXBuildFile; fileRef = 3264FF2E205D42CB00F6BD48 /* SDWebImageTestTransformer.m */; }; + 3264FF30205D42CB00F6BD48 /* SDWebImageTestTransformer.m in Sources */ = {isa = PBXBuildFile; fileRef = 3264FF2E205D42CB00F6BD48 /* SDWebImageTestTransformer.m */; }; 32B99E8B203AF8690017FD66 /* SDCategoriesTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 32B99E8A203AF8690017FD66 /* SDCategoriesTests.m */; }; 32B99E9B203B2EDD0017FD66 /* SDTestCase.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D7AF05F1F329763000083C2 /* SDTestCase.m */; }; 32B99E9C203B2EE40017FD66 /* SDCategoriesTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 32B99E8A203AF8690017FD66 /* SDCategoriesTests.m */; }; @@ -56,6 +58,8 @@ 2D7AF05F1F329763000083C2 /* SDTestCase.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDTestCase.m; sourceTree = ""; }; 321259EB1F39E3240096FE0E /* TestImageStatic.webp */ = {isa = PBXFileReference; lastKnownFileType = file; path = TestImageStatic.webp; sourceTree = ""; }; 321259ED1F39E4110096FE0E /* TestImageAnimated.webp */ = {isa = PBXFileReference; lastKnownFileType = file; path = TestImageAnimated.webp; sourceTree = ""; }; + 3264FF2D205D42CB00F6BD48 /* SDWebImageTestTransformer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDWebImageTestTransformer.h; sourceTree = ""; }; + 3264FF2E205D42CB00F6BD48 /* SDWebImageTestTransformer.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDWebImageTestTransformer.m; sourceTree = ""; }; 32B99E8A203AF8690017FD66 /* SDCategoriesTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDCategoriesTests.m; sourceTree = ""; }; 32B99E92203B2DF90017FD66 /* Tests Mac.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Tests Mac.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 32B99E96203B2DF90017FD66 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -193,6 +197,8 @@ 2D7AF05F1F329763000083C2 /* SDTestCase.m */, 32E6F0301F3A1B4700A945E6 /* SDWebImageTestDecoder.h */, 32E6F0311F3A1B4700A945E6 /* SDWebImageTestDecoder.m */, + 3264FF2D205D42CB00F6BD48 /* SDWebImageTestTransformer.h */, + 3264FF2E205D42CB00F6BD48 /* SDWebImageTestTransformer.m */, ); path = Tests; sourceTree = ""; @@ -447,6 +453,7 @@ 32B99E9E203B2F810017FD66 /* SDMockFileManager.m in Sources */, 32B99EAB203B36620017FD66 /* SDWebImageManagerTests.m in Sources */, 32B99EA9203B34B60017FD66 /* SDWebImageDecoderTests.m in Sources */, + 3264FF30205D42CB00F6BD48 /* SDWebImageTestTransformer.m in Sources */, 32B99E9B203B2EDD0017FD66 /* SDTestCase.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -464,6 +471,7 @@ DA248D69195475D800390AB0 /* SDImageCacheTests.m in Sources */, DA248D6B195476AC00390AB0 /* SDWebImageManagerTests.m in Sources */, 32B99E8B203AF8690017FD66 /* SDCategoriesTests.m in Sources */, + 3264FF2F205D42CB00F6BD48 /* SDWebImageTestTransformer.m in Sources */, 433BBBB51D7EF5C00086B6E9 /* SDWebImageDecoderTests.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Tests/Tests/SDWebImageManagerTests.m b/Tests/Tests/SDWebImageManagerTests.m index 4c79f24f..8ee3a567 100644 --- a/Tests/Tests/SDWebImageManagerTests.m +++ b/Tests/Tests/SDWebImageManagerTests.m @@ -8,6 +8,7 @@ #import "SDTestCase.h" #import +#import "SDWebImageTestTransformer.h" @interface SDWebImageManagerTests : SDTestCase @@ -135,4 +136,23 @@ [self waitForExpectationsWithTimeout:kAsyncTestTimeout * 2 handler:nil]; } +- (void)test08ThatImageTransformerWork { + XCTestExpectation *expectation = [self expectationWithDescription:@"Image transformer work"]; + NSURL *imageURL = [NSURL URLWithString:kTestJpegURL]; + SDWebImageTestTransformer *transformer = [[SDWebImageTestTransformer alloc] init]; + NSBundle *testBundle = [NSBundle bundleForClass:[self class]]; + NSString *testImagePath = [testBundle pathForResource:@"TestImage" ofType:@"jpg"]; + transformer.testImage = [[UIImage alloc] initWithContentsOfFile:testImagePath]; + SDWebImageManager *manager = [[SDWebImageManager alloc] initWithCache:[SDImageCache sharedImageCache] downloader:[SDWebImageDownloader sharedDownloader]]; + manager.transformer = transformer; + [[SDImageCache sharedImageCache] removeImageForKey:kTestJpegURL withCompletion:^{ + [manager loadImageWithURL:imageURL options:SDWebImageTransformAnimatedImage progress:nil completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, SDImageCacheType cacheType, BOOL finished, NSURL * _Nullable imageURL) { + expect(image).equal(transformer.testImage); + [expectation fulfill]; + }]; + }]; + + [self waitForExpectationsWithCommonTimeout]; +} + @end diff --git a/Tests/Tests/SDWebImageTestTransformer.h b/Tests/Tests/SDWebImageTestTransformer.h new file mode 100644 index 00000000..2f9040e8 --- /dev/null +++ b/Tests/Tests/SDWebImageTestTransformer.h @@ -0,0 +1,16 @@ +/* + * This file is part of the SDWebImage package. + * (c) Olivier Poitrey + * (c) Matt Galloway + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +#import + +@interface SDWebImageTestTransformer : NSObject + +@property (nonatomic, strong, nullable) UIImage *testImage; + +@end diff --git a/Tests/Tests/SDWebImageTestTransformer.m b/Tests/Tests/SDWebImageTestTransformer.m new file mode 100644 index 00000000..0afafe76 --- /dev/null +++ b/Tests/Tests/SDWebImageTestTransformer.m @@ -0,0 +1,22 @@ +/* + * This file is part of the SDWebImage package. + * (c) Olivier Poitrey + * (c) Matt Galloway + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +#import "SDWebImageTestTransformer.h" + +@implementation SDWebImageTestTransformer + +- (NSString *)transformerKey { + return @"SDWebImageTestTransformer"; +} + +- (UIImage *)transformedImageWithImage:(UIImage *)image forKey:(NSString *)key { + return self.testImage; +} + +@end From ed0100c323caed26e2817251700472f426eb64fc Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Sun, 18 Mar 2018 22:05:47 +0800 Subject: [PATCH 12/13] Move the NSBezierPath and UIColor category into implementation because it's now for internal use only --- SDWebImage/SDWebImageTransformer.m | 42 ++++++++++++++++++++++++++ SDWebImage/UIImage+Transform.h | 24 +-------------- SDWebImage/UIImage+Transform.m | 48 +++++++----------------------- Tests/Tests/SDCategoriesTests.m | 7 +++++ 4 files changed, 60 insertions(+), 61 deletions(-) diff --git a/SDWebImage/SDWebImageTransformer.m b/SDWebImage/SDWebImageTransformer.m index 86db61a2..80ca3394 100644 --- a/SDWebImage/SDWebImageTransformer.m +++ b/SDWebImage/SDWebImageTransformer.m @@ -21,6 +21,48 @@ NSString * _Nullable SDTransformedKeyForKey(NSString * _Nullable key, NSString * return [[key stringByAppendingString:SDWebImageTransformerKeySeparator] stringByAppendingString:transformerKey]; } +@interface UIColor (HexString) + +/** + Convenience way to get hex string from color. The output should always be 32-bit RGBA hex string like `#00000000`. + */ +@property (nonatomic, copy, readonly, nonnull) NSString *sd_hexString; + +@end + +@implementation UIColor (HexString) + +- (NSString *)sd_hexString { + CGFloat red, green, blue, alpha; +#if SD_UIKIT + if (![self getRed:&red green:&green blue:&blue alpha:&alpha]) { + [self getWhite:&red alpha:&alpha]; + green = red; + blue = red; + } +#else + @try { + [self getRed:&red green:&green blue:&blue alpha:&alpha]; + } + @catch (NSException *exception) { + [self getWhite:&red alpha:&alpha]; + green = red; + blue = red; + } +#endif + + red = roundf(red * 255.f); + green = roundf(green * 255.f); + blue = roundf(blue * 255.f); + alpha = roundf(alpha * 255.f); + + uint hex = ((uint)alpha << 24) | ((uint)red << 16) | ((uint)green << 8) | ((uint)blue); + + return [NSString stringWithFormat:@"#%08x", hex]; +} + +@end + @interface SDWebImagePipelineTransformer () @property (nonatomic, copy, readwrite, nonnull) NSArray> *transformers; diff --git a/SDWebImage/UIImage+Transform.h b/SDWebImage/UIImage+Transform.h index c5d64549..ff999bba 100644 --- a/SDWebImage/UIImage+Transform.h +++ b/SDWebImage/UIImage+Transform.h @@ -26,28 +26,6 @@ typedef NS_OPTIONS(NSUInteger, SDRectCorner) { }; #endif -#pragma mark - Useful category - -@interface UIColor (Additions) - -/** - Convenience way to get hex string from color. The output should always be 32-bit RGBA hex string like `#00000000`. - */ -@property (nonatomic, copy, readonly, nonnull) NSString *sd_hexString; - -@end - -#if SD_MAC -@interface NSBezierPath (Additions) - -/** - Convenience way to create a bezier path with the specify rounding corners on macOS. Same as the one on `UIBezierPath`. - */ -+ (nonnull instancetype)sd_bezierPathWithRoundedRect:(NSRect)rect byRoundingCorners:(SDRectCorner)corners cornerRadius:(CGFloat)cornerRadius; - -@end -#endif - /** Provide some commen method for `UIImage`. Image process is based on Core Graphics and vImage. @@ -135,7 +113,7 @@ typedef NS_OPTIONS(NSUInteger, SDRectCorner) { /** Return the pixel color array with specify rectangle. The rect is from the top-left to the bottom-right and 0-based. The returned the color is always be RGBA format. The image must be CG-based. - @note The rect's width/height should not be smaller than or equal to 0. The minX/minY should not be smaller than 0. The maxX/maxY should not be greater than width/height. Attention this limit is different from point(point: (0,0) like rect: (0, 0, 1, 1)) + @note The rect's width/height should not be smaller than or equal to 0. The minX/minY should not be smaller than 0. The maxX/maxY should not be greater than width/height. Attention this limit is different from `sd_colorAtPoint:` (point: (0, 0) like rect: (0, 0, 1, 1)) @note The overhead of object creation means this method is best suited for infrequent color sampling. For heavy image processing, grab the raw bitmap data and process yourself. @param rect The rectangle of pixels diff --git a/SDWebImage/UIImage+Transform.m b/SDWebImage/UIImage+Transform.m index 4d06a67a..f1c9f513 100644 --- a/SDWebImage/UIImage+Transform.m +++ b/SDWebImage/UIImage+Transform.m @@ -13,10 +13,6 @@ #import #endif -#ifndef SD_SWAP // swap two value -#define SD_SWAP(_a_, _b_) do { __typeof__(_a_) _tmp_ = (_a_); (_a_) = (_b_); (_b_) = _tmp_; } while (0) -#endif - #if SD_MAC static CGContextRef SDCGContextCreateARGBBitmapContext(CGSize size, BOOL opaque, CGFloat scale) { size_t width = ceil(size.width * scale); @@ -225,42 +221,17 @@ static inline UIColor * SDGetColorFromPixel(Pixel_8888 pixel, CGBitmapInfo bitma return [UIColor colorWithRed:r green:g blue:b alpha:a]; } -@implementation UIColor (Additions) +#if SD_MAC +@interface NSBezierPath (RoundedCorners) -- (NSString *)sd_hexString { - CGFloat red, green, blue, alpha; -#if SD_UIKIT - if (![self getRed:&red green:&green blue:&blue alpha:&alpha]) { - [self getWhite:&red alpha:&alpha]; - green = red; - blue = red; - } -#else - @try { - [self getRed:&red green:&green blue:&blue alpha:&alpha]; - } - @catch (NSException *exception) { - [self getWhite:&red alpha:&alpha]; - green = red; - blue = red; - } -#endif - - red = roundf(red * 255.f); - green = roundf(green * 255.f); - blue = roundf(blue * 255.f); - alpha = roundf(alpha * 255.f); - - uint hex = ((uint)alpha << 24) | ((uint)red << 16) | ((uint)green << 8) | ((uint)blue); - - return [NSString stringWithFormat:@"#%08x", hex]; -} +/** + Convenience way to create a bezier path with the specify rounding corners on macOS. Same as the one on `UIBezierPath`. + */ ++ (nonnull instancetype)sd_bezierPathWithRoundedRect:(NSRect)rect byRoundingCorners:(SDRectCorner)corners cornerRadius:(CGFloat)cornerRadius; @end -#if SD_MAC - -@implementation NSBezierPath (Additions) +@implementation NSBezierPath (RoundedCorners) + (instancetype)sd_bezierPathWithRoundedRect:(NSRect)rect byRoundingCorners:(SDRectCorner)corners cornerRadius:(CGFloat)cornerRadius { NSBezierPath *path = [NSBezierPath bezierPath]; @@ -288,7 +259,6 @@ static inline UIColor * SDGetColorFromPixel(Pixel_8888 pixel, CGBitmapInfo bitma } @end - #endif @implementation UIImage (Transform) @@ -664,7 +634,9 @@ static inline UIColor * SDGetColorFromPixel(Pixel_8888 pixel, CGBitmapInfo bitma void *temp = malloc(tempSize); for (int i = 0; i < iterations; i++) { vImageBoxConvolve_ARGB8888(input, output, temp, 0, 0, radius, radius, NULL, kvImageEdgeExtend); - SD_SWAP(input, output); + vImage_Buffer *tmp = input; + input = output; + output = tmp; } free(temp); } diff --git a/Tests/Tests/SDCategoriesTests.m b/Tests/Tests/SDCategoriesTests.m index 04247172..0d00aa09 100644 --- a/Tests/Tests/SDCategoriesTests.m +++ b/Tests/Tests/SDCategoriesTests.m @@ -18,6 +18,13 @@ #import #import +// Internal header +@interface UIColor (HexString) + +@property (nonatomic, copy, readonly, nonnull) NSString *sd_hexString; + +@end + @interface SDCategoriesTests : SDTestCase @property (nonatomic, strong) UIImage *testImage; From ec7927b25ab96ebe641d113b9aff84de602ac8ca Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Fri, 23 Mar 2018 01:45:53 +0800 Subject: [PATCH 13/13] Update the transformer to use as immutable class. Move the tests into SDWebImageTransformerTests --- SDWebImage/SDWebImageTransformer.h | 34 ++-- SDWebImage/SDWebImageTransformer.m | 179 ++++++++++-------- SDWebImage/UIImage+Transform.h | 6 +- .../project.pbxproj | 6 + Tests/Tests/SDCategoriesTests.m | 118 ------------ Tests/Tests/SDWebImageTransformerTests.m | 168 ++++++++++++++++ 6 files changed, 302 insertions(+), 209 deletions(-) create mode 100644 Tests/Tests/SDWebImageTransformerTests.m diff --git a/SDWebImage/SDWebImageTransformer.h b/SDWebImage/SDWebImageTransformer.h index 624bbe4d..31c126d7 100644 --- a/SDWebImage/SDWebImageTransformer.h +++ b/SDWebImage/SDWebImageTransformer.h @@ -48,18 +48,18 @@ FOUNDATION_EXPORT NSString * _Nullable SDTransformedKeyForKey(NSString * _Nullab #pragma mark - Pipeline // Pipeline transformer. Which you can bind multiple transformers together to let the image to be transformed one by one in order and generate the final image. +// Because transformers are lightweight, if you want to append or arrange transfomers, create another pipeline transformer instead. This class is considered as immutable. @interface SDWebImagePipelineTransformer : NSObject @property (nonatomic, copy, readonly, nonnull) NSArray> *transformers; -- (nonnull instancetype)initWithTransformers:(nonnull NSArray> *)transformers; - -- (void)addTransformer:(nonnull id)transformer; -- (void)removeTransformer:(nonnull id)transformer; +- (nonnull instancetype)init NS_UNAVAILABLE; ++ (nonnull instancetype)transformerWithTransformers:(nonnull NSArray> *)transformers; @end -// There are some build-in transformer based on the `UIImage+Transformer` category to provide the common image geometry, image blending and image effect process. Those transform are useful for static image only but you can create your own to support animated image as well. +// There are some built-in transformers based on the `UIImage+Transformer` category to provide the common image geometry, image blending and image effect process. Those transform are useful for static image only but you can create your own to support animated image as well. +// Because transformers are lightweight, these class are considered as immutable. #pragma mark - Image Geometry // Image round corner transformer @@ -70,7 +70,8 @@ FOUNDATION_EXPORT NSString * _Nullable SDTransformedKeyForKey(NSString * _Nullab @property (nonatomic, assign, readonly) CGFloat borderWidth; @property (nonatomic, strong, readonly, nullable) UIColor *borderColor; -- (nonnull instancetype)initWithRadius:(CGFloat)cornerRadius corners:(SDRectCorner)corners borderWidth:(CGFloat)borderWidth borderColor:(nullable UIColor *)borderColor; +- (nonnull instancetype)init NS_UNAVAILABLE; ++ (nonnull instancetype)transformerWithRadius:(CGFloat)cornerRadius corners:(SDRectCorner)corners borderWidth:(CGFloat)borderWidth borderColor:(nullable UIColor *)borderColor; @end @@ -80,7 +81,8 @@ FOUNDATION_EXPORT NSString * _Nullable SDTransformedKeyForKey(NSString * _Nullab @property (nonatomic, assign, readonly) CGSize size; @property (nonatomic, assign, readonly) SDImageScaleMode scaleMode; -- (nonnull instancetype)initWithSize:(CGSize)size scaleMode:(SDImageScaleMode)scaleMode; +- (nonnull instancetype)init NS_UNAVAILABLE; ++ (nonnull instancetype)transformerWithSize:(CGSize)size scaleMode:(SDImageScaleMode)scaleMode; @end @@ -89,7 +91,8 @@ FOUNDATION_EXPORT NSString * _Nullable SDTransformedKeyForKey(NSString * _Nullab @property (nonatomic, assign, readonly) CGRect rect; -- (nonnull instancetype)initWithRect:(CGRect)rect; +- (nonnull instancetype)init NS_UNAVAILABLE; ++ (nonnull instancetype)transformerWithRect:(CGRect)rect; @end @@ -99,7 +102,8 @@ FOUNDATION_EXPORT NSString * _Nullable SDTransformedKeyForKey(NSString * _Nullab @property (nonatomic, assign, readonly) BOOL horizontal; @property (nonatomic, assign, readonly) BOOL vertical; -- (nonnull instancetype)initWithHorizontal:(BOOL)horizontal vertical:(BOOL)vertical; +- (nonnull instancetype)init NS_UNAVAILABLE; ++ (nonnull instancetype)transformerWithHorizontal:(BOOL)horizontal vertical:(BOOL)vertical; @end @@ -109,7 +113,8 @@ FOUNDATION_EXPORT NSString * _Nullable SDTransformedKeyForKey(NSString * _Nullab @property (nonatomic, assign, readonly) CGFloat angle; @property (nonatomic, assign, readonly) BOOL fitSize; -- (nonnull instancetype)initWithAngle:(CGFloat)angle fitSize:(BOOL)fitSize; +- (nonnull instancetype)init NS_UNAVAILABLE; ++ (nonnull instancetype)transformerWithAngle:(CGFloat)angle fitSize:(BOOL)fitSize; @end @@ -120,7 +125,8 @@ FOUNDATION_EXPORT NSString * _Nullable SDTransformedKeyForKey(NSString * _Nullab @property (nonatomic, strong, readonly, nonnull) UIColor *tintColor; -- (nonnull instancetype)initWithColor:(nonnull UIColor *)tintColor; +- (nonnull instancetype)init NS_UNAVAILABLE; ++ (nonnull instancetype)transformerWithColor:(nonnull UIColor *)tintColor; @end @@ -131,7 +137,8 @@ FOUNDATION_EXPORT NSString * _Nullable SDTransformedKeyForKey(NSString * _Nullab @property (nonatomic, assign, readonly) CGFloat blurRadius; -- (nonnull instancetype)initWithRadius:(CGFloat)blurRadius; +- (nonnull instancetype)init NS_UNAVAILABLE; ++ (nonnull instancetype)transformerWithRadius:(CGFloat)blurRadius; @end @@ -141,7 +148,8 @@ FOUNDATION_EXPORT NSString * _Nullable SDTransformedKeyForKey(NSString * _Nullab @property (nonatomic, strong, readonly, nonnull) CIFilter *filter; -- (nonnull instancetype)initWithFilter:(nonnull CIFilter *)filter; +- (nonnull instancetype)init NS_UNAVAILABLE; ++ (nonnull instancetype)transformerWithFilter:(nonnull CIFilter *)filter; @end #endif diff --git a/SDWebImage/SDWebImageTransformer.m b/SDWebImage/SDWebImageTransformer.m index 80ca3394..740ff09e 100644 --- a/SDWebImage/SDWebImageTransformer.m +++ b/SDWebImage/SDWebImageTransformer.m @@ -72,13 +72,12 @@ NSString * _Nullable SDTransformedKeyForKey(NSString * _Nullable key, NSString * @implementation SDWebImagePipelineTransformer -- (instancetype)initWithTransformers:(NSArray> *)transformers { - self = [super init]; - if (self) { - _transformers = [transformers copy]; - _transformerKey = [[self class] cacheKeyForTransformers:transformers]; - } - return self; ++ (instancetype)transformerWithTransformers:(NSArray> *)transformers { + SDWebImagePipelineTransformer *transformer = [SDWebImagePipelineTransformer new]; + transformer.transformers = transformers; + transformer.transformerKey = [[self class] cacheKeyForTransformers:transformers]; + + return transformer; } + (NSString *)cacheKeyForTransformers:(NSArray> *)transformers { @@ -105,35 +104,27 @@ NSString * _Nullable SDTransformedKeyForKey(NSString * _Nullable key, NSString * return transformedImage; } -- (void)addTransformer:(id)transformer { - if (!transformer) { - return; - } - self.transformers = [self.transformers arrayByAddingObject:transformer]; -} +@end -- (void)removeTransformer:(id)transformer { - if (!transformer) { - return; - } - NSMutableArray> *transformers = [self.transformers mutableCopy]; - [transformers removeObject:transformer]; - self.transformers = [transformers copy]; -} +@interface SDWebImageRoundCornerTransformer () + +@property (nonatomic, assign) CGFloat cornerRadius; +@property (nonatomic, assign) SDRectCorner corners; +@property (nonatomic, assign) CGFloat borderWidth; +@property (nonatomic, strong, nullable) UIColor *borderColor; @end @implementation SDWebImageRoundCornerTransformer -- (instancetype)initWithRadius:(CGFloat)cornerRadius corners:(SDRectCorner)corners borderWidth:(CGFloat)borderWidth borderColor:(nullable UIColor *)borderColor { - self = [super init]; - if (self) { - _cornerRadius = cornerRadius; - _corners = corners; - _borderWidth = borderWidth; - _borderColor = borderColor; - } - return self; ++ (instancetype)transformerWithRadius:(CGFloat)cornerRadius corners:(SDRectCorner)corners borderWidth:(CGFloat)borderWidth borderColor:(UIColor *)borderColor { + SDWebImageRoundCornerTransformer *transformer = [SDWebImageRoundCornerTransformer new]; + transformer.cornerRadius = cornerRadius; + transformer.corners = corners; + transformer.borderWidth = borderWidth; + transformer.borderColor = borderColor; + + return transformer; } - (NSString *)transformerKey { @@ -149,15 +140,21 @@ NSString * _Nullable SDTransformedKeyForKey(NSString * _Nullable key, NSString * @end +@interface SDWebImageResizingTransformer () + +@property (nonatomic, assign) CGSize size; +@property (nonatomic, assign) SDImageScaleMode scaleMode; + +@end + @implementation SDWebImageResizingTransformer -- (instancetype)initWithSize:(CGSize)size scaleMode:(SDImageScaleMode)scaleMode { - self = [super init]; - if (self) { - _size = size; - _scaleMode = scaleMode; - } - return self; ++ (instancetype)transformerWithSize:(CGSize)size scaleMode:(SDImageScaleMode)scaleMode { + SDWebImageResizingTransformer *transformer = [SDWebImageResizingTransformer new]; + transformer.size = size; + transformer.scaleMode = scaleMode; + + return transformer; } - (NSString *)transformerKey { @@ -174,14 +171,19 @@ NSString * _Nullable SDTransformedKeyForKey(NSString * _Nullable key, NSString * @end +@interface SDWebImageCroppingTransformer () + +@property (nonatomic, assign) CGRect rect; + +@end + @implementation SDWebImageCroppingTransformer -- (instancetype)initWithRect:(CGRect)rect { - self = [super init]; - if (self) { - _rect = rect; - } - return self; ++ (instancetype)transformerWithRect:(CGRect)rect { + SDWebImageCroppingTransformer *transformer = [SDWebImageCroppingTransformer new]; + transformer.rect = rect; + + return transformer; } - (NSString *)transformerKey { @@ -198,15 +200,21 @@ NSString * _Nullable SDTransformedKeyForKey(NSString * _Nullable key, NSString * @end +@interface SDWebImageFlippingTransformer () + +@property (nonatomic, assign) BOOL horizontal; +@property (nonatomic, assign) BOOL vertical; + +@end + @implementation SDWebImageFlippingTransformer -- (instancetype)initWithHorizontal:(BOOL)horizontal vertical:(BOOL)vertical { - self = [super init]; - if (self) { - _horizontal = horizontal; - _vertical = vertical; - } - return self; ++ (instancetype)transformerWithHorizontal:(BOOL)horizontal vertical:(BOOL)vertical { + SDWebImageFlippingTransformer *transformer = [SDWebImageFlippingTransformer new]; + transformer.horizontal = horizontal; + transformer.vertical = vertical; + + return transformer; } - (NSString *)transformerKey { @@ -222,15 +230,21 @@ NSString * _Nullable SDTransformedKeyForKey(NSString * _Nullable key, NSString * @end +@interface SDWebImageRotationTransformer () + +@property (nonatomic, assign) CGFloat angle; +@property (nonatomic, assign) BOOL fitSize; + +@end + @implementation SDWebImageRotationTransformer -- (instancetype)initWithAngle:(CGFloat)angle fitSize:(BOOL)fitSize { - self = [super init]; - if (self) { - _angle = angle; - _fitSize = fitSize; - } - return self; ++ (instancetype)transformerWithAngle:(CGFloat)angle fitSize:(BOOL)fitSize { + SDWebImageRotationTransformer *transformer = [SDWebImageRotationTransformer new]; + transformer.angle = angle; + transformer.fitSize = fitSize; + + return transformer; } - (NSString *)transformerKey { @@ -248,14 +262,19 @@ NSString * _Nullable SDTransformedKeyForKey(NSString * _Nullable key, NSString * #pragma mark - Image Blending +@interface SDWebImageTintTransformer () + +@property (nonatomic, strong, nonnull) UIColor *tintColor; + +@end + @implementation SDWebImageTintTransformer -- (instancetype)initWithColor:(UIColor *)tintColor { - self = [super init]; - if (self) { - _tintColor = tintColor; - } - return self; ++ (instancetype)transformerWithColor:(UIColor *)tintColor { + SDWebImageTintTransformer *transformer = [SDWebImageTintTransformer new]; + transformer.tintColor = tintColor; + + return transformer; } - (NSString *)transformerKey { @@ -273,14 +292,19 @@ NSString * _Nullable SDTransformedKeyForKey(NSString * _Nullable key, NSString * #pragma mark - Image Effect +@interface SDWebImageBlurTransformer () + +@property (nonatomic, assign) CGFloat blurRadius; + +@end + @implementation SDWebImageBlurTransformer -- (instancetype)initWithRadius:(CGFloat)blurRadius { - self = [super init]; - if (self) { - _blurRadius = blurRadius; - } - return self; ++ (instancetype)transformerWithRadius:(CGFloat)blurRadius { + SDWebImageBlurTransformer *transformer = [SDWebImageBlurTransformer new]; + transformer.blurRadius = blurRadius; + + return transformer; } - (NSString *)transformerKey { @@ -297,14 +321,19 @@ NSString * _Nullable SDTransformedKeyForKey(NSString * _Nullable key, NSString * @end #if SD_UIKIT || SD_MAC +@interface SDWebImageFilterTransformer () + +@property (nonatomic, strong, nonnull) CIFilter *filter; + +@end + @implementation SDWebImageFilterTransformer -- (instancetype)initWithFilter:(CIFilter *)filter { - self = [super init]; - if (self) { - _filter = filter; - } - return self; ++ (instancetype)transformerWithFilter:(CIFilter *)filter { + SDWebImageFilterTransformer *transformer = [SDWebImageFilterTransformer new]; + transformer.filter = filter; + + return transformer; } - (NSString *)transformerKey { diff --git a/SDWebImage/UIImage+Transform.h b/SDWebImage/UIImage+Transform.h index ff999bba..717c08cc 100644 --- a/SDWebImage/UIImage+Transform.h +++ b/SDWebImage/UIImage+Transform.h @@ -35,10 +35,10 @@ typedef NS_OPTIONS(NSUInteger, SDRectCorner) { #pragma mark - Image Geometry /** - Returns a new image which is scaled from this image. - The image content will be changed with the scale mode. + Returns a new image which is resized from this image. + You can specify a larger or smaller size than the image size. The image content will be changed with the scale mode. - @param size The new size to be scaled, values should be positive. + @param size The new size to be resized, values should be positive. @param scaleMode The scale mode for image content. @return The new image with the given size. */ diff --git a/Tests/SDWebImage Tests.xcodeproj/project.pbxproj b/Tests/SDWebImage Tests.xcodeproj/project.pbxproj index 790c3e7e..5ce63173 100644 --- a/Tests/SDWebImage Tests.xcodeproj/project.pbxproj +++ b/Tests/SDWebImage Tests.xcodeproj/project.pbxproj @@ -12,6 +12,8 @@ 2D7AF0601F329763000083C2 /* SDTestCase.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D7AF05F1F329763000083C2 /* SDTestCase.m */; }; 321259EC1F39E3240096FE0E /* TestImageStatic.webp in Resources */ = {isa = PBXBuildFile; fileRef = 321259EB1F39E3240096FE0E /* TestImageStatic.webp */; }; 321259EE1F39E4110096FE0E /* TestImageAnimated.webp in Resources */ = {isa = PBXBuildFile; fileRef = 321259ED1F39E4110096FE0E /* TestImageAnimated.webp */; }; + 3254C32020641077008D1022 /* SDWebImageTransformerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3254C31F20641077008D1022 /* SDWebImageTransformerTests.m */; }; + 3254C32120641077008D1022 /* SDWebImageTransformerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3254C31F20641077008D1022 /* SDWebImageTransformerTests.m */; }; 3264FF2F205D42CB00F6BD48 /* SDWebImageTestTransformer.m in Sources */ = {isa = PBXBuildFile; fileRef = 3264FF2E205D42CB00F6BD48 /* SDWebImageTestTransformer.m */; }; 3264FF30205D42CB00F6BD48 /* SDWebImageTestTransformer.m in Sources */ = {isa = PBXBuildFile; fileRef = 3264FF2E205D42CB00F6BD48 /* SDWebImageTestTransformer.m */; }; 32B99E8B203AF8690017FD66 /* SDCategoriesTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 32B99E8A203AF8690017FD66 /* SDCategoriesTests.m */; }; @@ -58,6 +60,7 @@ 2D7AF05F1F329763000083C2 /* SDTestCase.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDTestCase.m; sourceTree = ""; }; 321259EB1F39E3240096FE0E /* TestImageStatic.webp */ = {isa = PBXFileReference; lastKnownFileType = file; path = TestImageStatic.webp; sourceTree = ""; }; 321259ED1F39E4110096FE0E /* TestImageAnimated.webp */ = {isa = PBXFileReference; lastKnownFileType = file; path = TestImageAnimated.webp; sourceTree = ""; }; + 3254C31F20641077008D1022 /* SDWebImageTransformerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDWebImageTransformerTests.m; sourceTree = ""; }; 3264FF2D205D42CB00F6BD48 /* SDWebImageTestTransformer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDWebImageTestTransformer.h; sourceTree = ""; }; 3264FF2E205D42CB00F6BD48 /* SDWebImageTestTransformer.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDWebImageTestTransformer.m; sourceTree = ""; }; 32B99E8A203AF8690017FD66 /* SDCategoriesTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDCategoriesTests.m; sourceTree = ""; }; @@ -189,6 +192,7 @@ 1E3C51E819B46E370092B5E6 /* SDWebImageDownloaderTests.m */, 433BBBB41D7EF5C00086B6E9 /* SDWebImageDecoderTests.m */, 4369C1D01D97F80F007E863A /* SDWebImagePrefetcherTests.m */, + 3254C31F20641077008D1022 /* SDWebImageTransformerTests.m */, 4369C2731D9804B1007E863A /* SDWebCacheCategoriesTests.m */, 32B99E8A203AF8690017FD66 /* SDCategoriesTests.m */, 37D122861EC48B5E00D98CEB /* SDMockFileManager.h */, @@ -445,6 +449,7 @@ buildActionMask = 2147483647; files = ( 32B99EAC203B36650017FD66 /* SDWebImageDownloaderTests.m in Sources */, + 3254C32120641077008D1022 /* SDWebImageTransformerTests.m in Sources */, 32B99E9C203B2EE40017FD66 /* SDCategoriesTests.m in Sources */, 32B99EAA203B365F0017FD66 /* SDImageCacheTests.m in Sources */, 32B99EAD203B36690017FD66 /* SDWebImagePrefetcherTests.m in Sources */, @@ -463,6 +468,7 @@ buildActionMask = 2147483647; files = ( 32E6F0321F3A1B4700A945E6 /* SDWebImageTestDecoder.m in Sources */, + 3254C32020641077008D1022 /* SDWebImageTransformerTests.m in Sources */, 1E3C51E919B46E370092B5E6 /* SDWebImageDownloaderTests.m in Sources */, 37D122881EC48B5E00D98CEB /* SDMockFileManager.m in Sources */, 4369C2741D9804B1007E863A /* SDWebCacheCategoriesTests.m in Sources */, diff --git a/Tests/Tests/SDCategoriesTests.m b/Tests/Tests/SDCategoriesTests.m index 0d00aa09..b49378ed 100644 --- a/Tests/Tests/SDCategoriesTests.m +++ b/Tests/Tests/SDCategoriesTests.m @@ -15,20 +15,9 @@ #import #import #import -#import -#import - -// Internal header -@interface UIColor (HexString) - -@property (nonatomic, copy, readonly, nonnull) NSString *sd_hexString; - -@end @interface SDCategoriesTests : SDTestCase -@property (nonatomic, strong) UIImage *testImage; - @end @implementation SDCategoriesTests @@ -76,110 +65,8 @@ expect(image).notTo.beNil(); } -// UIImage+Transform test is hard to write because it's more about visual effect. Current it's tied to the `TestImage.png`, please keep that image or write new test with new image -- (void)test05UIImageTransformResize { - CGSize size = CGSizeMake(200, 100); - UIImage *resizedImage = [self.testImage sd_resizedImageWithSize:size scaleMode:SDImageScaleModeFill]; - expect(CGSizeEqualToSize(resizedImage.size, size)).beTruthy(); -} - -- (void)test06UIImageTransformCrop { - CGRect rect = CGRectMake(50, 50, 200, 200); - UIImage *croppedImage = [self.testImage sd_croppedImageWithRect:rect]; - expect(CGSizeEqualToSize(croppedImage.size, CGSizeMake(200, 200))).beTruthy(); - UIColor *startColor = [croppedImage sd_colorAtPoint:CGPointZero]; - expect([startColor.sd_hexString isEqualToString:[UIColor clearColor].sd_hexString]).beTruthy(); -} - -- (void)test07UIImageTransformRoundedCorner { - CGFloat radius = 50; -#if SD_UIKIT - SDRectCorner corners = UIRectCornerAllCorners; -#else - SDRectCorner corners = SDRectCornerAllCorners; -#endif - CGFloat borderWidth = 1; - UIColor *borderCoder = [UIColor blackColor]; - UIImage *roundedCornerImage = [self.testImage sd_roundedCornerImageWithRadius:radius corners:corners borderWidth:borderWidth borderColor:borderCoder]; - expect(CGSizeEqualToSize(roundedCornerImage.size, CGSizeMake(300, 300))).beTruthy(); - UIColor *startColor = [roundedCornerImage sd_colorAtPoint:CGPointZero]; - expect([startColor.sd_hexString isEqualToString:[UIColor clearColor].sd_hexString]).beTruthy(); - // Check the left center pixel, should be border :) - UIColor *checkBorderColor = [roundedCornerImage sd_colorAtPoint:CGPointMake(1, 150)]; - expect([checkBorderColor.sd_hexString isEqualToString:borderCoder.sd_hexString]).beTruthy(); -} - -- (void)test08UIImageTransformRotate { - CGFloat angle = M_PI_4; - UIImage *rotatedImage = [self.testImage sd_rotatedImageWithAngle:angle fitSize:NO]; - // Not fit size and no change - expect(CGSizeEqualToSize(rotatedImage.size, self.testImage.size)).beTruthy(); - // Fit size, may change size - rotatedImage = [self.testImage sd_rotatedImageWithAngle:angle fitSize:YES]; - CGSize rotatedSize = CGSizeMake(floor(300 * 1.414), floor(300 * 1.414)); // 45º, square length * sqrt(2) - expect(CGSizeEqualToSize(rotatedImage.size, rotatedSize)).beTruthy(); - rotatedImage = [self.testImage sd_rotatedImageWithAngle:angle fitSize:NO]; -} - -- (void)test09UIImageTransformFlip { - BOOL horizontal = YES; - BOOL vertical = YES; - UIImage *flippedImage = [self.testImage sd_flippedImageWithHorizontal:horizontal vertical:vertical]; - expect(CGSizeEqualToSize(flippedImage.size, self.testImage.size)).beTruthy(); - // Test pixel colors method here - UIColor *checkColor = [flippedImage sd_colorAtPoint:CGPointMake(75, 75)]; - expect(checkColor); - NSArray *checkColors = [flippedImage sd_colorsWithRect:CGRectMake(75, 75, 10, 10)]; // Rect are all same color - expect(checkColors.count).to.equal(10 * 10); - for (UIColor *color in checkColors) { - expect([color isEqual:checkColor]).to.beTruthy(); - } -} - -- (void)test10UIImageTransformTint { - UIColor *tintColor = [UIColor blackColor]; - UIImage *tintedImage = [self.testImage sd_tintedImageWithColor:tintColor]; - expect(CGSizeEqualToSize(tintedImage.size, self.testImage.size)).beTruthy(); - // Check center color, should keep clear - UIColor *centerColor = [tintedImage sd_colorAtPoint:CGPointMake(150, 150)]; - expect([centerColor.sd_hexString isEqualToString:[UIColor clearColor].sd_hexString]); - // Check left color, should be tinted - UIColor *leftColor = [tintedImage sd_colorAtPoint:CGPointMake(80, 150)]; - expect([leftColor.sd_hexString isEqualToString:tintColor.sd_hexString]); -} - -- (void)test11UIImageTransformBlur { - CGFloat radius = 50; - UIImage *blurredImage = [self.testImage sd_blurredImageWithRadius:radius]; - expect(CGSizeEqualToSize(blurredImage.size, self.testImage.size)).beTruthy(); - // Check left color, should be blurred - UIColor *leftColor = [blurredImage sd_colorAtPoint:CGPointMake(80, 150)]; - // Hard-code from the output - UIColor *expectedColor = [UIColor colorWithRed:0.431373 green:0.101961 blue:0.0901961 alpha:0.729412]; - expect([leftColor.sd_hexString isEqualToString:expectedColor.sd_hexString]); -} - -- (void)test12UIImageTransformFilter { - // Invert color filter - CIFilter *filter = [CIFilter filterWithName:@"CIColorInvert"]; - UIImage *filteredImage = [self.testImage sd_filteredImageWithFilter:filter]; - expect(CGSizeEqualToSize(filteredImage.size, self.testImage.size)).beTruthy(); - // Check left color, should be inverted - UIColor *leftColor = [filteredImage sd_colorAtPoint:CGPointMake(80, 150)]; - // Hard-code from the output - UIColor *expectedColor = [UIColor colorWithRed:0.85098 green:0.992157 blue:0.992157 alpha:1]; - expect([leftColor.sd_hexString isEqualToString:expectedColor.sd_hexString]); -} - #pragma mark - Helper -- (UIImage *)testImage { - if (!_testImage) { - _testImage = [[UIImage alloc] initWithContentsOfFile:[self testPNGPath]]; - } - return _testImage; -} - - (NSString *)testJPEGPath { NSBundle *testBundle = [NSBundle bundleForClass:[self class]]; return [testBundle pathForResource:@"TestImage" ofType:@"jpg"]; @@ -195,9 +82,4 @@ return [testBundle pathForResource:@"TestImageStatic" ofType:@"webp"]; } -- (NSString *)testPNGPath { - NSBundle *testBundle = [NSBundle bundleForClass:[self class]]; - return [testBundle pathForResource:@"TestImage" ofType:@"png"]; -} - @end diff --git a/Tests/Tests/SDWebImageTransformerTests.m b/Tests/Tests/SDWebImageTransformerTests.m new file mode 100644 index 00000000..feff0241 --- /dev/null +++ b/Tests/Tests/SDWebImageTransformerTests.m @@ -0,0 +1,168 @@ +/* + * This file is part of the SDWebImage package. + * (c) Olivier Poitrey + * (c) Matt Galloway + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +#import "SDTestCase.h" +#import +#import +#import + +// Internal header +@interface UIColor (HexString) + +@property (nonatomic, copy, readonly, nonnull) NSString *sd_hexString; + +@end + +@interface SDWebImageTransformerTests : SDTestCase + +@property (nonatomic, strong) UIImage *testImage; + +@end + +@implementation SDWebImageTransformerTests + +#pragma mark - UIImage+Transform + +// UIImage+Transform test is hard to write because it's more about visual effect. Current it's tied to the `TestImage.png`, please keep that image or write new test with new image +- (void)test01UIImageTransformResize { + CGSize scaleDownSize = CGSizeMake(200, 100); + UIImage *scaledDownImage = [self.testImage sd_resizedImageWithSize:scaleDownSize scaleMode:SDImageScaleModeFill]; + expect(CGSizeEqualToSize(scaledDownImage.size, scaleDownSize)).beTruthy(); + CGSize scaleUpSize = CGSizeMake(2000, 1000); + UIImage *scaledUpImage = [self.testImage sd_resizedImageWithSize:scaleUpSize scaleMode:SDImageScaleModeAspectFit]; + expect(CGSizeEqualToSize(scaledUpImage.size, scaleUpSize)).beTruthy(); +} + +- (void)test02UIImageTransformCrop { + CGRect rect = CGRectMake(50, 50, 200, 200); + UIImage *croppedImage = [self.testImage sd_croppedImageWithRect:rect]; + expect(CGSizeEqualToSize(croppedImage.size, CGSizeMake(200, 200))).beTruthy(); + UIColor *startColor = [croppedImage sd_colorAtPoint:CGPointZero]; + expect([startColor.sd_hexString isEqualToString:[UIColor clearColor].sd_hexString]).beTruthy(); +} + +- (void)test03UIImageTransformRoundedCorner { + CGFloat radius = 50; +#if SD_UIKIT + SDRectCorner corners = UIRectCornerAllCorners; +#else + SDRectCorner corners = SDRectCornerAllCorners; +#endif + CGFloat borderWidth = 1; + UIColor *borderColor = [UIColor blackColor]; + UIImage *roundedCornerImage = [self.testImage sd_roundedCornerImageWithRadius:radius corners:corners borderWidth:borderWidth borderColor:borderColor]; + expect(CGSizeEqualToSize(roundedCornerImage.size, CGSizeMake(300, 300))).beTruthy(); + UIColor *startColor = [roundedCornerImage sd_colorAtPoint:CGPointZero]; + expect([startColor.sd_hexString isEqualToString:[UIColor clearColor].sd_hexString]).beTruthy(); + // Check the left center pixel, should be border :) + UIColor *checkBorderColor = [roundedCornerImage sd_colorAtPoint:CGPointMake(1, 150)]; + expect([checkBorderColor.sd_hexString isEqualToString:borderColor.sd_hexString]).beTruthy(); +} + +- (void)test04UIImageTransformRotate { + CGFloat angle = M_PI_4; + UIImage *rotatedImage = [self.testImage sd_rotatedImageWithAngle:angle fitSize:NO]; + // Not fit size and no change + expect(CGSizeEqualToSize(rotatedImage.size, self.testImage.size)).beTruthy(); + // Fit size, may change size + rotatedImage = [self.testImage sd_rotatedImageWithAngle:angle fitSize:YES]; + CGSize rotatedSize = CGSizeMake(floor(300 * 1.414), floor(300 * 1.414)); // 45º, square length * sqrt(2) + expect(CGSizeEqualToSize(rotatedImage.size, rotatedSize)).beTruthy(); + rotatedImage = [self.testImage sd_rotatedImageWithAngle:angle fitSize:NO]; +} + +- (void)test05UIImageTransformFlip { + BOOL horizontal = YES; + BOOL vertical = YES; + UIImage *flippedImage = [self.testImage sd_flippedImageWithHorizontal:horizontal vertical:vertical]; + expect(CGSizeEqualToSize(flippedImage.size, self.testImage.size)).beTruthy(); + // Test pixel colors method here + UIColor *checkColor = [flippedImage sd_colorAtPoint:CGPointMake(75, 75)]; + expect(checkColor); + NSArray *checkColors = [flippedImage sd_colorsWithRect:CGRectMake(75, 75, 10, 10)]; // Rect are all same color + expect(checkColors.count).to.equal(10 * 10); + for (UIColor *color in checkColors) { + expect([color isEqual:checkColor]).to.beTruthy(); + } +} + +- (void)test06UIImageTransformTint { + UIColor *tintColor = [UIColor blackColor]; + UIImage *tintedImage = [self.testImage sd_tintedImageWithColor:tintColor]; + expect(CGSizeEqualToSize(tintedImage.size, self.testImage.size)).beTruthy(); + // Check center color, should keep clear + UIColor *centerColor = [tintedImage sd_colorAtPoint:CGPointMake(150, 150)]; + expect([centerColor.sd_hexString isEqualToString:[UIColor clearColor].sd_hexString]); + // Check left color, should be tinted + UIColor *leftColor = [tintedImage sd_colorAtPoint:CGPointMake(80, 150)]; + expect([leftColor.sd_hexString isEqualToString:tintColor.sd_hexString]); +} + +- (void)test07UIImageTransformBlur { + CGFloat radius = 50; + UIImage *blurredImage = [self.testImage sd_blurredImageWithRadius:radius]; + expect(CGSizeEqualToSize(blurredImage.size, self.testImage.size)).beTruthy(); + // Check left color, should be blurred + UIColor *leftColor = [blurredImage sd_colorAtPoint:CGPointMake(80, 150)]; + // Hard-code from the output + UIColor *expectedColor = [UIColor colorWithRed:0.431373 green:0.101961 blue:0.0901961 alpha:0.729412]; + expect([leftColor.sd_hexString isEqualToString:expectedColor.sd_hexString]); +} + +- (void)test08UIImageTransformFilter { + // Invert color filter + CIFilter *filter = [CIFilter filterWithName:@"CIColorInvert"]; + UIImage *filteredImage = [self.testImage sd_filteredImageWithFilter:filter]; + expect(CGSizeEqualToSize(filteredImage.size, self.testImage.size)).beTruthy(); + // Check left color, should be inverted + UIColor *leftColor = [filteredImage sd_colorAtPoint:CGPointMake(80, 150)]; + // Hard-code from the output + UIColor *expectedColor = [UIColor colorWithRed:0.85098 green:0.992157 blue:0.992157 alpha:1]; + expect([leftColor.sd_hexString isEqualToString:expectedColor.sd_hexString]); +} + +#pragma mark - SDWebImageTransformer + +- (void)test09ImagePipelineTransformer { + CGSize size = CGSizeMake(100, 100); + SDImageScaleMode scaleMode = SDImageScaleModeAspectFill; + CGFloat angle = M_PI_4; + BOOL fitSize = NO; + CGFloat radius = 50; +#if SD_UIKIT + SDRectCorner corners = UIRectCornerAllCorners; +#else + SDRectCorner corners = SDRectCornerAllCorners; +#endif + CGFloat borderWidth = 1; + UIColor *borderCoder = [UIColor blackColor]; + SDWebImageResizingTransformer *transformer1 = [SDWebImageResizingTransformer transformerWithSize:size scaleMode:scaleMode]; + SDWebImageRotationTransformer *transformer2 = [SDWebImageRotationTransformer transformerWithAngle:angle fitSize:fitSize]; + SDWebImageRoundCornerTransformer *transformer3 = [SDWebImageRoundCornerTransformer transformerWithRadius:radius corners:corners borderWidth:borderWidth borderColor:borderCoder]; + SDWebImagePipelineTransformer *pipelineTransformer = [SDWebImagePipelineTransformer transformerWithTransformers:@[transformer1, transformer2, transformer3]]; + + UIImage *transformedImage = [pipelineTransformer transformedImageWithImage:self.testImage forKey:@"Test"]; + expect(CGSizeEqualToSize(transformedImage.size, size)).beTruthy(); +} + +#pragma mark - Helper + +- (UIImage *)testImage { + if (!_testImage) { + _testImage = [[UIImage alloc] initWithContentsOfFile:[self testPNGPath]]; + } + return _testImage; +} + +- (NSString *)testPNGPath { + NSBundle *testBundle = [NSBundle bundleForClass:[self class]]; + return [testBundle pathForResource:@"TestImage" ofType:@"png"]; +} + +@end