From 6ff83fde6bba99491a78096cdbc8ac5711f4666b Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Sun, 15 Dec 2019 15:54:15 +0800 Subject: [PATCH 01/12] Added SDGraphicsImageRenderer (which bridge to UIGraphicsImageRenderer on iOS 10+), prepare to replace old CGContext create code --- SDWebImage.xcodeproj/project.pbxproj | 12 ++ SDWebImage/Core/SDGraphicsImageRenderer.h | 44 +++++++ SDWebImage/Core/SDGraphicsImageRenderer.m | 140 ++++++++++++++++++++++ WebImage/SDWebImage.h | 1 + 4 files changed, 197 insertions(+) create mode 100644 SDWebImage/Core/SDGraphicsImageRenderer.h create mode 100644 SDWebImage/Core/SDGraphicsImageRenderer.m diff --git a/SDWebImage.xcodeproj/project.pbxproj b/SDWebImage.xcodeproj/project.pbxproj index fefb9564..39537274 100644 --- a/SDWebImage.xcodeproj/project.pbxproj +++ b/SDWebImage.xcodeproj/project.pbxproj @@ -52,6 +52,9 @@ 3244062C2296C5F400A36084 /* SDWebImageOptionsProcessor.h in Headers */ = {isa = PBXBuildFile; fileRef = 324406292296C5F400A36084 /* SDWebImageOptionsProcessor.h */; settings = {ATTRIBUTES = (Public, ); }; }; 3244062D2296C5F400A36084 /* SDWebImageOptionsProcessor.m in Sources */ = {isa = PBXBuildFile; fileRef = 3244062A2296C5F400A36084 /* SDWebImageOptionsProcessor.m */; }; 3244062E2296C5F400A36084 /* SDWebImageOptionsProcessor.m in Sources */ = {isa = PBXBuildFile; fileRef = 3244062A2296C5F400A36084 /* SDWebImageOptionsProcessor.m */; }; + 3246A70323A567AC00FBEA10 /* SDGraphicsImageRenderer.h in Headers */ = {isa = PBXBuildFile; fileRef = 3246A70123A567AC00FBEA10 /* SDGraphicsImageRenderer.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 3246A70423A567AC00FBEA10 /* SDGraphicsImageRenderer.m in Sources */ = {isa = PBXBuildFile; fileRef = 3246A70223A567AC00FBEA10 /* SDGraphicsImageRenderer.m */; }; + 3246A70523A567AC00FBEA10 /* SDGraphicsImageRenderer.m in Sources */ = {isa = PBXBuildFile; fileRef = 3246A70223A567AC00FBEA10 /* SDGraphicsImageRenderer.m */; }; 3248475D201775F600AF9E5A /* SDAnimatedImageView.m in Sources */ = {isa = PBXBuildFile; fileRef = 32484757201775F600AF9E5A /* SDAnimatedImageView.m */; }; 3248475F201775F600AF9E5A /* SDAnimatedImageView.m in Sources */ = {isa = PBXBuildFile; fileRef = 32484757201775F600AF9E5A /* SDAnimatedImageView.m */; }; 32484765201775F600AF9E5A /* SDAnimatedImageView+WebCache.h in Headers */ = {isa = PBXBuildFile; fileRef = 32484758201775F600AF9E5A /* SDAnimatedImageView+WebCache.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -121,6 +124,7 @@ 328BB6CF2082581100760D6C /* SDMemoryCache.h in Headers */ = {isa = PBXBuildFile; fileRef = 328BB6BF2082581100760D6C /* SDMemoryCache.h */; settings = {ATTRIBUTES = (Public, ); }; }; 328BB6D32082581100760D6C /* SDMemoryCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 328BB6C02082581100760D6C /* SDMemoryCache.m */; }; 328BB6D52082581100760D6C /* SDMemoryCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 328BB6C02082581100760D6C /* SDMemoryCache.m */; }; + 328E9DE523A61DD30051C893 /* SDGraphicsImageRenderer.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 3246A70123A567AC00FBEA10 /* SDGraphicsImageRenderer.h */; }; 3290FA061FA478AF0047D20C /* SDImageFrame.h in Headers */ = {isa = PBXBuildFile; fileRef = 3290FA021FA478AF0047D20C /* SDImageFrame.h */; settings = {ATTRIBUTES = (Public, ); }; }; 3290FA0A1FA478AF0047D20C /* SDImageFrame.m in Sources */ = {isa = PBXBuildFile; fileRef = 3290FA031FA478AF0047D20C /* SDImageFrame.m */; }; 3290FA0C1FA478AF0047D20C /* SDImageFrame.m in Sources */ = {isa = PBXBuildFile; fileRef = 3290FA031FA478AF0047D20C /* SDImageFrame.m */; }; @@ -308,6 +312,7 @@ dstPath = include/SDWebImage; dstSubfolderSpec = 16; files = ( + 328E9DE523A61DD30051C893 /* SDGraphicsImageRenderer.h in Copy Headers */, 325F7CCD2389467800AEDFCC /* UIImage+ExtendedCacheData.h in Copy Headers */, 326E2F36236F1E30006F847F /* SDAnimatedImagePlayer.h in Copy Headers */, 3250C9F12355E3DF0093A896 /* SDWebImageDownloaderDecryptor.h in Copy Headers */, @@ -394,6 +399,8 @@ 3240BB6723968FE6003BA07D /* SDAssociatedObject.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDAssociatedObject.m; sourceTree = ""; }; 324406292296C5F400A36084 /* SDWebImageOptionsProcessor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SDWebImageOptionsProcessor.h; path = Core/SDWebImageOptionsProcessor.h; sourceTree = ""; }; 3244062A2296C5F400A36084 /* SDWebImageOptionsProcessor.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = SDWebImageOptionsProcessor.m; path = Core/SDWebImageOptionsProcessor.m; sourceTree = ""; }; + 3246A70123A567AC00FBEA10 /* SDGraphicsImageRenderer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SDGraphicsImageRenderer.h; path = Core/SDGraphicsImageRenderer.h; sourceTree = ""; }; + 3246A70223A567AC00FBEA10 /* SDGraphicsImageRenderer.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = SDGraphicsImageRenderer.m; path = Core/SDGraphicsImageRenderer.m; sourceTree = ""; }; 32484757201775F600AF9E5A /* SDAnimatedImageView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SDAnimatedImageView.m; path = Core/SDAnimatedImageView.m; sourceTree = ""; }; 32484758201775F600AF9E5A /* SDAnimatedImageView+WebCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "SDAnimatedImageView+WebCache.h"; path = "Core/SDAnimatedImageView+WebCache.h"; sourceTree = ""; }; 32484759201775F600AF9E5A /* SDAnimatedImageView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SDAnimatedImageView.h; path = Core/SDAnimatedImageView.h; sourceTree = ""; }; @@ -577,6 +584,8 @@ 32CF1C061FA496B000004BD1 /* SDImageCoderHelper.m */, 3257EAF721898AED0097B271 /* SDImageGraphics.h */, 3257EAF821898AED0097B271 /* SDImageGraphics.m */, + 3246A70123A567AC00FBEA10 /* SDGraphicsImageRenderer.h */, + 3246A70223A567AC00FBEA10 /* SDGraphicsImageRenderer.m */, ); name = Decoder; sourceTree = ""; @@ -905,6 +914,7 @@ 4A2CAE1D1AB4BB6800B6BC39 /* SDWebImageDownloaderOperation.h in Headers */, 4A2CAE2B1AB4BB7500B6BC39 /* UIButton+WebCache.h in Headers */, 4A2CAE251AB4BB7000B6BC39 /* SDWebImagePrefetcher.h in Headers */, + 3246A70323A567AC00FBEA10 /* SDGraphicsImageRenderer.h in Headers */, 328BB6CF2082581100760D6C /* SDMemoryCache.h in Headers */, 325C460F223394D8004CAE11 /* SDImageCachesManagerOperation.h in Headers */, 321E60881F38E8C800405457 /* SDImageCoder.h in Headers */, @@ -1125,6 +1135,7 @@ 3290FA0C1FA478AF0047D20C /* SDImageFrame.m in Sources */, 325C46232233A02E004CAE11 /* UIColor+HexString.m in Sources */, 325F7CCB238942AB00AEDFCC /* UIImage+ExtendedCacheData.m in Sources */, + 3246A70523A567AC00FBEA10 /* SDGraphicsImageRenderer.m in Sources */, 321E60C61F38E91700405457 /* UIImage+ForceDecode.m in Sources */, 3244062E2296C5F400A36084 /* SDWebImageOptionsProcessor.m in Sources */, 3250C9F02355D9DA0093A896 /* SDWebImageDownloaderDecryptor.m in Sources */, @@ -1198,6 +1209,7 @@ 3290FA0A1FA478AF0047D20C /* SDImageFrame.m in Sources */, 325C46222233A02E004CAE11 /* UIColor+HexString.m in Sources */, 321E60C41F38E91700405457 /* UIImage+ForceDecode.m in Sources */, + 3246A70423A567AC00FBEA10 /* SDGraphicsImageRenderer.m in Sources */, 3244062D2296C5F400A36084 /* SDWebImageOptionsProcessor.m in Sources */, 3250C9EF2355D9DA0093A896 /* SDWebImageDownloaderDecryptor.m in Sources */, 3240BB6523968FA1003BA07D /* SDFileAttributeHelper.m in Sources */, diff --git a/SDWebImage/Core/SDGraphicsImageRenderer.h b/SDWebImage/Core/SDGraphicsImageRenderer.h new file mode 100644 index 00000000..2899b3b4 --- /dev/null +++ b/SDWebImage/Core/SDGraphicsImageRenderer.h @@ -0,0 +1,44 @@ +/* +* 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 void (^SDGraphicsImageDrawingActions)(CGContextRef _Nonnull context); + +typedef NS_ENUM(NSInteger, SDGraphicsImageRendererFormatRange) { + SDGraphicsImageRendererFormatRangeUnspecified = -1, + SDGraphicsImageRendererFormatRangeAutomatic = 0, + SDGraphicsImageRendererFormatRangeExtended, + SDGraphicsImageRendererFormatRangeStandard +}; + +@interface SDGraphicsImageRendererFormat : NSObject + +@property (nonatomic) CGFloat scale; +@property (nonatomic) BOOL opaque; + +/** + For iOS 12+, the value is from system API + For iOS 10-11, the value is from `prefersExtendedRange` property + For iOS 9, the value is `.unspecified` + */ +@property (nonatomic) SDGraphicsImageRendererFormatRange preferredRange; + +- (nonnull instancetype)init; ++ (nonnull instancetype)preferredFormat; + +@end + +@interface SDGraphicsImageRenderer : NSObject + +- (nonnull instancetype)initWithSize:(CGSize)size; +- (nonnull instancetype)initWithSize:(CGSize)size format:(nonnull SDGraphicsImageRendererFormat *)format; + +- (nonnull UIImage *)imageWithActions:(nonnull NS_NOESCAPE SDGraphicsImageDrawingActions)actions; + +@end diff --git a/SDWebImage/Core/SDGraphicsImageRenderer.m b/SDWebImage/Core/SDGraphicsImageRenderer.m new file mode 100644 index 00000000..980ef2b4 --- /dev/null +++ b/SDWebImage/Core/SDGraphicsImageRenderer.m @@ -0,0 +1,140 @@ +/* +* 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 "SDGraphicsImageRenderer.h" +#import "SDImageGraphics.h" + +@interface SDGraphicsImageRendererFormat () +@property (nonatomic, strong) UIGraphicsImageRendererFormat *uiformat API_AVAILABLE(ios(10.0)); +@end + +@implementation SDGraphicsImageRendererFormat + +- (instancetype)init { + self = [super init]; + if (self) { + if (@available(iOS 10.0, *)) { + UIGraphicsImageRendererFormat *uiformat = [[UIGraphicsImageRendererFormat alloc] init]; + self.uiformat = uiformat; + self.scale = uiformat.scale; + self.opaque = uiformat.opaque; + if (@available(iOS 12.0, *)) { + self.preferredRange = (SDGraphicsImageRendererFormatRange)uiformat.preferredRange; + } else { + if (uiformat.prefersExtendedRange) { + self.preferredRange = SDGraphicsImageRendererFormatRangeExtended; + } else { + self.preferredRange = SDGraphicsImageRendererFormatRangeStandard; + } + } + } else { + self.scale = 1.0; + self.opaque = NO; + self.preferredRange = SDGraphicsImageRendererFormatRangeUnspecified; + } + } + return self; +} + +- (instancetype)initForMainScreen { + self = [super init]; + if (self) { + if (@available(iOS 10.0, *)) { + UIGraphicsImageRendererFormat *uiformat; +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunguarded-availability" + // iOS 11.0.0 GM does have `preferredFormat`, but iOS 11 betas did not (argh!) + if ([UIGraphicsImageRenderer respondsToSelector:@selector(preferredFormat)]) { + uiformat = [UIGraphicsImageRendererFormat preferredFormat]; + } else { + uiformat = [UIGraphicsImageRendererFormat defaultFormat]; + } + self.uiformat = uiformat; + self.scale = uiformat.scale; + self.opaque = uiformat.opaque; + if (@available(iOS 12.0, *)) { + self.preferredRange = (SDGraphicsImageRendererFormatRange)uiformat.preferredRange; + } else { + if (uiformat.prefersExtendedRange) { + self.preferredRange = SDGraphicsImageRendererFormatRangeExtended; + } else { + self.preferredRange = SDGraphicsImageRendererFormatRangeStandard; + } + } +#pragma clang diagnostic pop + } else { +#if SD_WATCH + CGFloat screenScale = [WKInterfaceDevice currentDevice].screenScale; +#elif SD_UIKIT + CGFloat screenScale = [UIScreen mainScreen].scale; +#elif SD_MAC + CGFloat screenScale = [NSScreen mainScreen].backingScaleFactor; +#endif + self.scale = screenScale; + self.opaque = NO; + self.preferredRange = SDGraphicsImageRendererFormatRangeUnspecified; + } + } + return self; +} + ++ (instancetype)preferredFormat { + SDGraphicsImageRendererFormat *format = [[SDGraphicsImageRendererFormat alloc] initForMainScreen]; + return format; +} + +@end + +@interface SDGraphicsImageRenderer () +@property (nonatomic, assign) CGSize size; +@property (nonatomic, strong) SDGraphicsImageRendererFormat *format; +@property (nonatomic, strong) UIGraphicsImageRenderer *uirenderer API_AVAILABLE(ios(10.0)); +@end + +@implementation SDGraphicsImageRenderer + +- (instancetype)initWithSize:(CGSize)size { + return [self initWithSize:size format:SDGraphicsImageRendererFormat.preferredFormat]; +} + +- (instancetype)initWithSize:(CGSize)size format:(SDGraphicsImageRendererFormat *)format { + NSParameterAssert(format); + self = [super init]; + if (self) { + self.size = size; + self.format = format; + if (@available(iOS 10.0, *)) { + UIGraphicsImageRendererFormat *uiformat = format.uiformat; + self.uirenderer = [[UIGraphicsImageRenderer alloc] initWithSize:size format:uiformat]; + } + } + return self; +} + +- (UIImage *)imageWithActions:(NS_NOESCAPE SDGraphicsImageDrawingActions)actions { + NSParameterAssert(actions); + if (@available(iOS 10.0, *)) { + UIGraphicsImageDrawingActions uiactions = ^(UIGraphicsImageRendererContext *rendererContext) { + if (actions) { + actions(rendererContext.CGContext); + } + }; + return [self.uirenderer imageWithActions:uiactions]; + } else { + SDGraphicsBeginImageContextWithOptions(self.size, self.format.opaque, self.format.scale); + CGContextRef context = SDGraphicsGetCurrentContext(); + if (actions) { + actions(context); + } + UIImage *image = SDGraphicsGetImageFromCurrentImageContext(); + SDGraphicsEndImageContext(); + return image; + } +} + +@end diff --git a/WebImage/SDWebImage.h b/WebImage/SDWebImage.h index ab0f43e5..f219978e 100644 --- a/WebImage/SDWebImage.h +++ b/WebImage/SDWebImage.h @@ -65,6 +65,7 @@ FOUNDATION_EXPORT const unsigned char WebImageVersionString[]; #import #import #import +#import #import #import #import From ee0aa220e051f6807017afe98c34ba2a1a3643e6 Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Sun, 15 Dec 2019 16:36:13 +0800 Subject: [PATCH 02/12] Fix the compile issue on watchOS/macOS --- SDWebImage/Core/SDGraphicsImageRenderer.m | 40 ++++++++++++++++------- 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/SDWebImage/Core/SDGraphicsImageRenderer.m b/SDWebImage/Core/SDGraphicsImageRenderer.m index 980ef2b4..029a4f40 100644 --- a/SDWebImage/Core/SDGraphicsImageRenderer.m +++ b/SDWebImage/Core/SDGraphicsImageRenderer.m @@ -10,7 +10,9 @@ #import "SDImageGraphics.h" @interface SDGraphicsImageRendererFormat () -@property (nonatomic, strong) UIGraphicsImageRendererFormat *uiformat API_AVAILABLE(ios(10.0)); +#if SD_UIKIT +@property (nonatomic, strong) UIGraphicsImageRendererFormat *uiformat API_AVAILABLE(ios(10.0), tvos(10.0)); +#endif @end @implementation SDGraphicsImageRendererFormat @@ -18,12 +20,13 @@ - (instancetype)init { self = [super init]; if (self) { - if (@available(iOS 10.0, *)) { +#if SD_UIKIT + if (@available(iOS 10.0, tvOS 10.10, *)) { UIGraphicsImageRendererFormat *uiformat = [[UIGraphicsImageRendererFormat alloc] init]; self.uiformat = uiformat; self.scale = uiformat.scale; self.opaque = uiformat.opaque; - if (@available(iOS 12.0, *)) { + if (@available(iOS 12.0, tvOS 12.0, *)) { self.preferredRange = (SDGraphicsImageRendererFormatRange)uiformat.preferredRange; } else { if (uiformat.prefersExtendedRange) { @@ -33,21 +36,25 @@ } } } else { +#endif self.scale = 1.0; self.opaque = NO; self.preferredRange = SDGraphicsImageRendererFormatRangeUnspecified; +#if SD_UIKIT } +#endif } return self; } +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunguarded-availability" - (instancetype)initForMainScreen { self = [super init]; if (self) { - if (@available(iOS 10.0, *)) { +#if SD_UIKIT + if (@available(iOS 10.0, tvOS 10.0, *)) { UIGraphicsImageRendererFormat *uiformat; -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wunguarded-availability" // iOS 11.0.0 GM does have `preferredFormat`, but iOS 11 betas did not (argh!) if ([UIGraphicsImageRenderer respondsToSelector:@selector(preferredFormat)]) { uiformat = [UIGraphicsImageRendererFormat preferredFormat]; @@ -57,7 +64,7 @@ self.uiformat = uiformat; self.scale = uiformat.scale; self.opaque = uiformat.opaque; - if (@available(iOS 12.0, *)) { + if (@available(iOS 12.0, tvOS 12.0, *)) { self.preferredRange = (SDGraphicsImageRendererFormatRange)uiformat.preferredRange; } else { if (uiformat.prefersExtendedRange) { @@ -66,8 +73,8 @@ self.preferredRange = SDGraphicsImageRendererFormatRangeStandard; } } -#pragma clang diagnostic pop } else { +#endif #if SD_WATCH CGFloat screenScale = [WKInterfaceDevice currentDevice].screenScale; #elif SD_UIKIT @@ -78,10 +85,13 @@ self.scale = screenScale; self.opaque = NO; self.preferredRange = SDGraphicsImageRendererFormatRangeUnspecified; +#if SD_UIKIT } +#endif } return self; } +#pragma clang diagnostic pop + (instancetype)preferredFormat { SDGraphicsImageRendererFormat *format = [[SDGraphicsImageRendererFormat alloc] initForMainScreen]; @@ -93,7 +103,9 @@ @interface SDGraphicsImageRenderer () @property (nonatomic, assign) CGSize size; @property (nonatomic, strong) SDGraphicsImageRendererFormat *format; -@property (nonatomic, strong) UIGraphicsImageRenderer *uirenderer API_AVAILABLE(ios(10.0)); +#if SD_UIKIT +@property (nonatomic, strong) UIGraphicsImageRenderer *uirenderer API_AVAILABLE(ios(10.0), tvos(10.0)); +#endif @end @implementation SDGraphicsImageRenderer @@ -108,17 +120,20 @@ if (self) { self.size = size; self.format = format; - if (@available(iOS 10.0, *)) { +#if SD_UIKIT + if (@available(iOS 10.0, tvOS 10.0, *)) { UIGraphicsImageRendererFormat *uiformat = format.uiformat; self.uirenderer = [[UIGraphicsImageRenderer alloc] initWithSize:size format:uiformat]; } +#endif } return self; } - (UIImage *)imageWithActions:(NS_NOESCAPE SDGraphicsImageDrawingActions)actions { NSParameterAssert(actions); - if (@available(iOS 10.0, *)) { +#if SD_UIKIT + if (@available(iOS 10.0, tvOS 10.0, *)) { UIGraphicsImageDrawingActions uiactions = ^(UIGraphicsImageRendererContext *rendererContext) { if (actions) { actions(rendererContext.CGContext); @@ -126,6 +141,7 @@ }; return [self.uirenderer imageWithActions:uiactions]; } else { +#endif SDGraphicsBeginImageContextWithOptions(self.size, self.format.opaque, self.format.scale); CGContextRef context = SDGraphicsGetCurrentContext(); if (actions) { @@ -134,7 +150,9 @@ UIImage *image = SDGraphicsGetImageFromCurrentImageContext(); SDGraphicsEndImageContext(); return image; +#if SD_UIKIT } +#endif } @end From 8fa6c7519c2e41b5f8a5031c3b0e730d0a21ae2c Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Sun, 15 Dec 2019 16:58:20 +0800 Subject: [PATCH 03/12] Replace the SDGraphicsBeginImageContextWithOptions with SDGraphicsImageRenderer --- SDWebImage/Core/UIImage+Transform.m | 96 +++++++++++++++-------------- 1 file changed, 49 insertions(+), 47 deletions(-) diff --git a/SDWebImage/Core/UIImage+Transform.m b/SDWebImage/Core/UIImage+Transform.m index 8637b1a2..723d48be 100644 --- a/SDWebImage/Core/UIImage+Transform.m +++ b/SDWebImage/Core/UIImage+Transform.m @@ -9,6 +9,7 @@ #import "UIImage+Transform.h" #import "NSImage+Compatibility.h" #import "SDImageGraphics.h" +#import "SDGraphicsImageRenderer.h" #import "NSBezierPath+RoundedCorners.h" #import #if SD_UIKIT || SD_MAC @@ -165,11 +166,10 @@ static inline UIColor * SDGetColorFromPixel(Pixel_8888 pixel, CGBitmapInfo bitma @implementation UIImage (Transform) -- (void)sd_drawInRect:(CGRect)rect withScaleMode:(SDImageScaleMode)scaleMode clipsToBounds:(BOOL)clips { +- (void)sd_drawInRect:(CGRect)rect context:(CGContextRef)context scaleMode:(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); @@ -184,10 +184,12 @@ static inline UIColor * SDGetColorFromPixel(Pixel_8888 pixel, CGBitmapInfo bitma - (nullable 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(); + SDGraphicsImageRendererFormat *format = [[SDGraphicsImageRendererFormat alloc] init]; + format.scale = self.scale; + SDGraphicsImageRenderer *renderer = [[SDGraphicsImageRenderer alloc] initWithSize:size format:format]; + UIImage *image = [renderer imageWithActions:^(CGContextRef _Nonnull context) { + [self sd_drawInRect:CGRectMake(0, 0, size.width, size.height) context:context scaleMode:scaleMode clipsToBounds:NO]; + }]; return image; } @@ -213,43 +215,43 @@ static inline UIColor * SDGetColorFromPixel(Pixel_8888 pixel, CGBitmapInfo bitma - (nullable UIImage *)sd_roundedCornerImageWithRadius:(CGFloat)cornerRadius corners:(SDRectCorner)corners borderWidth:(CGFloat)borderWidth borderColor:(nullable UIColor *)borderColor { if (!self.CGImage) return nil; - 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 sd_bezierPathWithRoundedRect:CGRectInset(rect, borderWidth, borderWidth) byRoundingCorners:corners cornerRadius:cornerRadius]; -#endif - [path closePath]; + SDGraphicsImageRendererFormat *format = [[SDGraphicsImageRendererFormat alloc] init]; + format.scale = self.scale; + SDGraphicsImageRenderer *renderer = [[SDGraphicsImageRenderer alloc] initWithSize:self.size format:format]; + UIImage *image = [renderer imageWithActions:^(CGContextRef _Nonnull context) { + CGRect rect = CGRectMake(0, 0, self.size.width, self.size.height); - CGContextSaveGState(context); - [path addClip]; - [self drawInRect:rect]; - 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; + CGFloat minSize = MIN(self.size.width, self.size.height); + if (borderWidth < minSize / 2) { #if SD_UIKIT || SD_WATCH - UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:strokeRect byRoundingCorners:corners cornerRadii:CGSizeMake(strokeRadius, strokeRadius)]; + UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectInset(rect, borderWidth, borderWidth) byRoundingCorners:corners cornerRadii:CGSizeMake(cornerRadius, cornerRadius)]; #else - NSBezierPath *path = [NSBezierPath sd_bezierPathWithRoundedRect:strokeRect byRoundingCorners:corners cornerRadius:strokeRadius]; + NSBezierPath *path = [NSBezierPath sd_bezierPathWithRoundedRect:CGRectInset(rect, borderWidth, borderWidth) byRoundingCorners:corners cornerRadius:cornerRadius]; #endif - [path closePath]; + [path closePath]; + + CGContextSaveGState(context); + [path addClip]; + [self drawInRect:rect]; + CGContextRestoreGState(context); + } - path.lineWidth = borderWidth; - [borderColor setStroke]; - [path stroke]; - } - - UIImage *image = SDGraphicsGetImageFromCurrentImageContext(); - SDGraphicsEndImageContext(); + 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 sd_bezierPathWithRoundedRect:strokeRect byRoundingCorners:corners cornerRadius:strokeRadius]; +#endif + [path closePath]; + + path.lineWidth = borderWidth; + [borderColor setStroke]; + [path stroke]; + } + }]; return image; } @@ -347,15 +349,15 @@ static inline UIColor * SDGetColorFromPixel(Pixel_8888 pixel, CGBitmapInfo bitma // blend mode, see https://en.wikipedia.org/wiki/Alpha_compositing CGBlendMode blendMode = kCGBlendModeSourceAtop; - SDGraphicsBeginImageContextWithOptions(size, NO, scale); - CGContextRef context = SDGraphicsGetCurrentContext(); - [self drawInRect:rect]; - CGContextSetBlendMode(context, blendMode); - CGContextSetFillColorWithColor(context, tintColor.CGColor); - CGContextFillRect(context, rect); - UIImage *image = SDGraphicsGetImageFromCurrentImageContext(); - SDGraphicsEndImageContext(); - + SDGraphicsImageRendererFormat *format = [[SDGraphicsImageRendererFormat alloc] init]; + format.scale = scale; + SDGraphicsImageRenderer *renderer = [[SDGraphicsImageRenderer alloc] initWithSize:size format:format]; + UIImage *image = [renderer imageWithActions:^(CGContextRef _Nonnull context) { + [self drawInRect:rect]; + CGContextSetBlendMode(context, blendMode); + CGContextSetFillColorWithColor(context, tintColor.CGColor); + CGContextFillRect(context, rect); + }]; return image; } From d5734cd6cd2d67cc2eb08368b7150f2e793c6a72 Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Sun, 15 Dec 2019 19:41:52 +0800 Subject: [PATCH 04/12] Fix the implementation of SDGraphicsImageRendererFormat, now use the dynamic getter/setter to forward to UIGraphicsImageRendererFormat --- SDWebImage/Core/SDGraphicsImageRenderer.m | 120 ++++++++++++++++++---- 1 file changed, 98 insertions(+), 22 deletions(-) diff --git a/SDWebImage/Core/SDGraphicsImageRenderer.m b/SDWebImage/Core/SDGraphicsImageRenderer.m index 029a4f40..141da2ff 100644 --- a/SDWebImage/Core/SDGraphicsImageRenderer.m +++ b/SDWebImage/Core/SDGraphicsImageRenderer.m @@ -16,6 +16,104 @@ @end @implementation SDGraphicsImageRendererFormat +@synthesize scale = _scale; +@synthesize opaque = _opaque; +@synthesize preferredRange = _preferredRange; + +#pragma mark - Property +- (CGFloat)scale { +#if SD_UIKIT + if (@available(iOS 10.0, tvOS 10.10, *)) { + return self.uiformat.scale; + } else { + return _scale; + } +#else + return _scale; +#endif +} + +- (void)setScale:(CGFloat)scale { +#if SD_UIKIT + if (@available(iOS 10.0, tvOS 10.10, *)) { + self.uiformat.scale = scale; + } else { + _scale = scale; + } +#else + _scale = scale; +#endif +} + +- (BOOL)opaque { +#if SD_UIKIT + if (@available(iOS 10.0, tvOS 10.10, *)) { + return self.uiformat.opaque; + } else { + return _opaque; + } +#else + return _opaque; +#endif +} + +- (void)setOpaque:(BOOL)opaque { +#if SD_UIKIT + if (@available(iOS 10.0, tvOS 10.10, *)) { + self.uiformat.opaque = opaque; + } else { + _opaque = opaque; + } +#else + _opaque = opaque; +#endif +} + +- (SDGraphicsImageRendererFormatRange)preferredRange { +#if SD_UIKIT + if (@available(iOS 10.0, tvOS 10.10, *)) { + if (@available(iOS 12.0, tvOS 12.0, *)) { + return (SDGraphicsImageRendererFormatRange)self.uiformat.preferredRange; + } else { + BOOL prefersExtendedRange = self.uiformat.prefersExtendedRange; + if (prefersExtendedRange) { + return SDGraphicsImageRendererFormatRangeExtended; + } else { + return SDGraphicsImageRendererFormatRangeStandard; + } + } + } else { + return _preferredRange; + } +#else + return _preferredRange; +#endif +} + +- (void)setPreferredRange:(SDGraphicsImageRendererFormatRange)preferredRange { +#if SD_UIKIT + if (@available(iOS 10.0, tvOS 10.10, *)) { + if (@available(iOS 12.0, tvOS 12.0, *)) { + self.uiformat.preferredRange = (UIGraphicsImageRendererFormatRange)preferredRange; + } else { + switch (preferredRange) { + case SDGraphicsImageRendererFormatRangeExtended: + self.uiformat.prefersExtendedRange = YES; + break; + case SDGraphicsImageRendererFormatRangeStandard: + self.uiformat.prefersExtendedRange = NO; + default: + // Automatic means default + break; + } + } + } else { + _preferredRange = preferredRange; + } +#else + _preferredRange = preferredRange; +#endif +} - (instancetype)init { self = [super init]; @@ -24,17 +122,6 @@ if (@available(iOS 10.0, tvOS 10.10, *)) { UIGraphicsImageRendererFormat *uiformat = [[UIGraphicsImageRendererFormat alloc] init]; self.uiformat = uiformat; - self.scale = uiformat.scale; - self.opaque = uiformat.opaque; - if (@available(iOS 12.0, tvOS 12.0, *)) { - self.preferredRange = (SDGraphicsImageRendererFormatRange)uiformat.preferredRange; - } else { - if (uiformat.prefersExtendedRange) { - self.preferredRange = SDGraphicsImageRendererFormatRangeExtended; - } else { - self.preferredRange = SDGraphicsImageRendererFormatRangeStandard; - } - } } else { #endif self.scale = 1.0; @@ -62,17 +149,6 @@ uiformat = [UIGraphicsImageRendererFormat defaultFormat]; } self.uiformat = uiformat; - self.scale = uiformat.scale; - self.opaque = uiformat.opaque; - if (@available(iOS 12.0, tvOS 12.0, *)) { - self.preferredRange = (SDGraphicsImageRendererFormatRange)uiformat.preferredRange; - } else { - if (uiformat.prefersExtendedRange) { - self.preferredRange = SDGraphicsImageRendererFormatRangeExtended; - } else { - self.preferredRange = SDGraphicsImageRendererFormatRangeStandard; - } - } } else { #endif #if SD_WATCH From c49bc5c925559b2ec4441654328eed8e3b5d1306 Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Sun, 15 Dec 2019 19:42:52 +0800 Subject: [PATCH 05/12] Change the implementation of `sd_rotatedImageWithAngle` using the UIGraphicsRenderer, avoid always using ARGB8888 --- SDWebImage/Core/UIImage+Transform.m | 44 +++++++++------------------ Tests/Tests/SDImageTransformerTests.m | 2 +- 2 files changed, 16 insertions(+), 30 deletions(-) diff --git a/SDWebImage/Core/UIImage+Transform.m b/SDWebImage/Core/UIImage+Transform.m index 723d48be..45cd7fba 100644 --- a/SDWebImage/Core/UIImage+Transform.m +++ b/SDWebImage/Core/UIImage+Transform.m @@ -259,37 +259,23 @@ static inline UIColor * SDGetColorFromPixel(Pixel_8888 pixel, CGBitmapInfo bitma 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), + 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 orientation:kCGImagePropertyOrientationUp]; -#endif - CGImageRelease(imgRef); - CGContextRelease(context); - return img; + SDGraphicsImageRendererFormat *format = [[SDGraphicsImageRendererFormat alloc] init]; + format.scale = self.scale; + SDGraphicsImageRenderer *renderer = [[SDGraphicsImageRenderer alloc] initWithSize:newRect.size format:format]; + UIImage *image = [renderer imageWithActions:^(CGContextRef _Nonnull context) { + CGContextSetShouldAntialias(context, true); + CGContextSetAllowsAntialiasing(context, true); + CGContextSetInterpolationQuality(context, kCGInterpolationHigh); + // Use UIKit coordinate counterclockwise (⟲) + CGContextTranslateCTM(context, +(newRect.size.width * 0.5), +(newRect.size.height * 0.5)); + CGContextRotateCTM(context, -angle); + + [self drawInRect:CGRectMake(-(width * 0.5), -(height * 0.5), width, height)]; + }]; + return image; } - (nullable UIImage *)sd_flippedImageWithHorizontal:(BOOL)horizontal vertical:(BOOL)vertical { diff --git a/Tests/Tests/SDImageTransformerTests.m b/Tests/Tests/SDImageTransformerTests.m index d0105f57..6ddcfc14 100644 --- a/Tests/Tests/SDImageTransformerTests.m +++ b/Tests/Tests/SDImageTransformerTests.m @@ -73,7 +73,7 @@ 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) + CGSize rotatedSize = CGSizeMake(ceil(300 * 1.414), ceil(300 * 1.414)); // 45º, square length * sqrt(2) expect(CGSizeEqualToSize(rotatedImage.size, rotatedSize)).beTruthy(); // Check image not inversion UIColor *leftCenterColor = [rotatedImage sd_colorAtPoint:CGPointMake(60, 175)]; From 48a7b7f943a9a267eb58857fac88837db735ef38 Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Sun, 15 Dec 2019 19:49:41 +0800 Subject: [PATCH 06/12] Fix the issues during refactory, the UIGraphicsRenderer using the point size, not pixel size --- SDWebImage/Core/UIImage+Transform.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SDWebImage/Core/UIImage+Transform.m b/SDWebImage/Core/UIImage+Transform.m index 45cd7fba..db5de997 100644 --- a/SDWebImage/Core/UIImage+Transform.m +++ b/SDWebImage/Core/UIImage+Transform.m @@ -257,8 +257,8 @@ static inline UIColor * SDGetColorFromPixel(Pixel_8888 pixel, CGBitmapInfo bitma - (nullable 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); + size_t width = self.size.width; + size_t height = self.size.height; CGRect newRect = CGRectApplyAffineTransform(CGRectMake(0, 0, width, height), fitSize ? CGAffineTransformMakeRotation(angle) : CGAffineTransformIdentity); From 1e778f0fe66461a76b24018efff36ff2a754aad4 Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Sun, 15 Dec 2019 20:20:38 +0800 Subject: [PATCH 07/12] Refactory the `sd_flippedImageWithHorizontal` with the UIGraphicsRenderer, do not always need ARGB8888 --- SDWebImage/Core/UIImage+Transform.m | 48 +++++++++++------------------ 1 file changed, 18 insertions(+), 30 deletions(-) diff --git a/SDWebImage/Core/UIImage+Transform.m b/SDWebImage/Core/UIImage+Transform.m index db5de997..ffcd9327 100644 --- a/SDWebImage/Core/UIImage+Transform.m +++ b/SDWebImage/Core/UIImage+Transform.m @@ -280,37 +280,25 @@ static inline UIColor * SDGetColorFromPixel(Pixel_8888 pixel, CGBitmapInfo bitma - (nullable 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; + size_t width = self.size.width; + size_t height = self.size.height; - 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 orientation:kCGImagePropertyOrientationUp]; -#endif - CGImageRelease(imgRef); - return img; + SDGraphicsImageRendererFormat *format = [[SDGraphicsImageRendererFormat alloc] init]; + format.scale = self.scale; + SDGraphicsImageRenderer *renderer = [[SDGraphicsImageRenderer alloc] initWithSize:self.size format:format]; + UIImage *image = [renderer imageWithActions:^(CGContextRef _Nonnull context) { + // Use UIKit coordinate system + if (horizontal) { + CGAffineTransform flipHorizontal = CGAffineTransformMake(-1, 0, 0, 1, width, 0); + CGContextConcatCTM(context, flipHorizontal); + } + if (vertical) { + CGAffineTransform flipVertical = CGAffineTransformMake(1, 0, 0, -1, 0, height); + CGContextConcatCTM(context, flipVertical); + } + [self drawInRect:CGRectMake(0, 0, width, height)]; + }]; + return image; } #pragma mark - Image Blending From 3929f2cd2a354f2fc23b1be819c73451f9ed35e5 Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Sun, 15 Dec 2019 20:44:32 +0800 Subject: [PATCH 08/12] Replace the background decode core function CGImageCreateDecoded with UIGraphicsRenderer, this one should use the perferred format --- SDWebImage/Core/SDImageCoderHelper.m | 35 ++++++++++++++++------------ 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/SDWebImage/Core/SDImageCoderHelper.m b/SDWebImage/Core/SDImageCoderHelper.m index 3cc0c7ea..1a3391e1 100644 --- a/SDWebImage/Core/SDImageCoderHelper.m +++ b/SDWebImage/Core/SDImageCoderHelper.m @@ -12,6 +12,7 @@ #import "NSData+ImageContentType.h" #import "SDAnimatedImageRep.h" #import "UIImage+ForceDecode.h" +#import "SDGraphicsImageRenderer.h" #import "SDAssociatedObject.h" #if SD_UIKIT || SD_WATCH @@ -257,22 +258,26 @@ static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to over } BOOL hasAlpha = [self CGImageContainsAlpha:cgImage]; - // iOS prefer BGRA8888 (premultiplied) or BGRX8888 bitmapInfo for screen rendering, which is same as `UIGraphicsBeginImageContext()` or `- [CALayer drawInContext:]` - // Though you can use any supported bitmapInfo (see: https://developer.apple.com/library/content/documentation/GraphicsImaging/Conceptual/drawingwithquartz2d/dq_context/dq_context.html#//apple_ref/doc/uid/TP30001066-CH203-BCIBHHBB ) and let Core Graphics reorder it when you call `CGContextDrawImage` - // But since our build-in coders use this bitmapInfo, this can have a little performance benefit - CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Host; - bitmapInfo |= hasAlpha ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaNoneSkipFirst; - CGContextRef context = CGBitmapContextCreate(NULL, newWidth, newHeight, 8, 0, [self colorSpaceGetDeviceRGB], bitmapInfo); - if (!context) { - return NULL; - } - // Apply transform - CGAffineTransform transform = SDCGContextTransformFromOrientation(orientation, CGSizeMake(newWidth, newHeight)); - CGContextConcatCTM(context, transform); - CGContextDrawImage(context, CGRectMake(0, 0, width, height), cgImage); // The rect is bounding box of CGImage, don't swap width & height - CGImageRef newImageRef = CGBitmapContextCreateImage(context); - CGContextRelease(context); + // Using UIGraphicsRenderer on supported platforms, and use the main screeen's perferred format (like Wide Color) + SDGraphicsImageRendererFormat *format = [SDGraphicsImageRendererFormat preferredFormat]; + format.scale = 1; // use pixel instead of point + format.opaque = !hasAlpha; + SDGraphicsImageRenderer *renderer = [[SDGraphicsImageRenderer alloc] initWithSize:CGSizeMake(newWidth, newHeight) format:format]; + UIImage *newImage = [renderer imageWithActions:^(CGContextRef _Nonnull context) { + // Apply transform + CGAffineTransform transform = SDCGContextTransformFromOrientation(orientation, CGSizeMake(newWidth, newHeight)); + CGContextConcatCTM(context, transform); +#if SD_MAC + UIImage *drawImage = [[UIImage alloc] initWithCGImage:cgImage scale:1 orientation:kCGImagePropertyOrientationUp]; +#else + UIImage *drawImage = [[UIImage alloc] initWithCGImage:cgImage scale:1 orientation:UIImageOrientationUp]; +#endif + [drawImage drawInRect:CGRectMake(0, 0, width, height)]; // The rect is bounding box of CGImage, don't swap width & height + }]; + CGImageRef newImageRef = newImage.CGImage; + // Retain the CGImage because this temp `newImage` will release after function return + CGImageRetain(newImageRef); return newImageRef; } From 92e3bfcc3e9ee89e0b0250cc48603c64406fbedf Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Mon, 16 Dec 2019 11:44:52 +0800 Subject: [PATCH 09/12] Fix the sd_rotatedImageWithAngle on macOS, which should not apply the counterclockwise reverse --- SDWebImage/Core/UIImage+Transform.m | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/SDWebImage/Core/UIImage+Transform.m b/SDWebImage/Core/UIImage+Transform.m index ffcd9327..54d3cef9 100644 --- a/SDWebImage/Core/UIImage+Transform.m +++ b/SDWebImage/Core/UIImage+Transform.m @@ -269,9 +269,13 @@ static inline UIColor * SDGetColorFromPixel(Pixel_8888 pixel, CGBitmapInfo bitma CGContextSetShouldAntialias(context, true); CGContextSetAllowsAntialiasing(context, true); CGContextSetInterpolationQuality(context, kCGInterpolationHigh); - // Use UIKit coordinate counterclockwise (⟲) CGContextTranslateCTM(context, +(newRect.size.width * 0.5), +(newRect.size.height * 0.5)); +#if SD_UIKIT + // Use UIKit coordinate system counterclockwise (⟲) CGContextRotateCTM(context, -angle); +#else + CGContextRotateCTM(context, angle); +#endif [self drawInRect:CGRectMake(-(width * 0.5), -(height * 0.5), width, height)]; }]; From 1ee04f64b0a97698ad51e67fce85e220c50696db Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Mon, 16 Dec 2019 17:26:09 +0800 Subject: [PATCH 10/12] Update all the documentation of the SDGraphicsImageRenderer, fix small behavior to match Apple's documentation --- SDWebImage/Core/SDGraphicsImageRenderer.h | 40 +++++++++++++++++++---- SDWebImage/Core/SDGraphicsImageRenderer.m | 13 ++++++-- SDWebImage/Core/SDImageGraphics.h | 1 + 3 files changed, 45 insertions(+), 9 deletions(-) diff --git a/SDWebImage/Core/SDGraphicsImageRenderer.h b/SDWebImage/Core/SDGraphicsImageRenderer.h index 2899b3b4..e450d639 100644 --- a/SDWebImage/Core/SDGraphicsImageRenderer.h +++ b/SDWebImage/Core/SDGraphicsImageRenderer.h @@ -8,8 +8,15 @@ #import "SDWebImageCompat.h" -typedef void (^SDGraphicsImageDrawingActions)(CGContextRef _Nonnull context); +/** + These following class are provided to use `UIGraphicsImageRenderer` with polyfill, which allows write cross-platform(AppKit/UIKit) code and avoid runtime version check. + Compared to `UIGraphicsBeginImageContext`, `UIGraphicsImageRenderer` use dynamic bitmap info from your draw code to generate CGContext, not always use ARGB8888, which is more performant on RAM usage. + For usage, See more in Apple's documentation: https://developer.apple.com/documentation/uikit/uigraphicsimagerenderer + For UIKit on iOS/tvOS 10+, these method just use the same `UIGraphicsImageRenderer` API. + For others (macOS/watchOS or iOS/tvOS 10-), these method use the `SDImageGraphics.h` to implements the same behavior. +*/ +typedef void (^SDGraphicsImageDrawingActions)(CGContextRef _Nonnull context); typedef NS_ENUM(NSInteger, SDGraphicsImageRendererFormatRange) { SDGraphicsImageRendererFormatRangeUnspecified = -1, SDGraphicsImageRendererFormatRangeAutomatic = 0, @@ -17,28 +24,49 @@ typedef NS_ENUM(NSInteger, SDGraphicsImageRendererFormatRange) { SDGraphicsImageRendererFormatRangeStandard }; +/// A set of drawing attributes that represent the configuration of an image renderer context. @interface SDGraphicsImageRendererFormat : NSObject +/// The display scale of the image renderer context. +/// The default value is equal to the scale of the main screen. @property (nonatomic) CGFloat scale; + +/// A Boolean value indicating whether the underlying Core Graphics context has an alpha channel. +/// The default value is NO. @property (nonatomic) BOOL opaque; -/** - For iOS 12+, the value is from system API - For iOS 10-11, the value is from `prefersExtendedRange` property - For iOS 9, the value is `.unspecified` - */ +/// Specifying whether the bitmap context should use extended color. +/// For iOS 12+, the value is from system `preferredRange` property +/// For iOS 10-11, the value is from system `prefersExtendedRange` property +/// For iOS 9-, the value is `.standard` @property (nonatomic) SDGraphicsImageRendererFormatRange preferredRange; +/// Init the default format. See each properties's default value. - (nonnull instancetype)init; + +/// Returns a new format best suited for the main screen’s current configuration. + (nonnull instancetype)preferredFormat; @end +/// A graphics renderer for creating Core Graphics-backed images. @interface SDGraphicsImageRenderer : NSObject +/// Creates an image renderer for drawing images of a given size. +/// @param size The size of images output from the renderer, specified in points. +/// @return An initialized image renderer. - (nonnull instancetype)initWithSize:(CGSize)size; + +/// Creates a new image renderer with a given size and format. +/// @param size The size of images output from the renderer, specified in points. +/// @param format A SDGraphicsImageRendererFormat object that encapsulates the format used to create the renderer context. +/// @return An initialized image renderer. - (nonnull instancetype)initWithSize:(CGSize)size format:(nonnull SDGraphicsImageRendererFormat *)format; +/// Creates an image by following a set of drawing instructions. +/// @param actions A SDGraphicsImageDrawingActions block that, when invoked by the renderer, executes a set of drawing instructions to create the output image. +/// @note You should not retain or use the context outside the block, it's non-escaping. +/// @return A UIImage object created by the supplied drawing actions. - (nonnull UIImage *)imageWithActions:(nonnull NS_NOESCAPE SDGraphicsImageDrawingActions)actions; @end diff --git a/SDWebImage/Core/SDGraphicsImageRenderer.m b/SDWebImage/Core/SDGraphicsImageRenderer.m index 141da2ff..869de2ca 100644 --- a/SDWebImage/Core/SDGraphicsImageRenderer.m +++ b/SDWebImage/Core/SDGraphicsImageRenderer.m @@ -124,9 +124,16 @@ self.uiformat = uiformat; } else { #endif - self.scale = 1.0; +#if SD_WATCH + CGFloat screenScale = [WKInterfaceDevice currentDevice].screenScale; +#elif SD_UIKIT + CGFloat screenScale = [UIScreen mainScreen].scale; +#elif SD_MAC + CGFloat screenScale = [NSScreen mainScreen].backingScaleFactor; +#endif + self.scale = screenScale; self.opaque = NO; - self.preferredRange = SDGraphicsImageRendererFormatRangeUnspecified; + self.preferredRange = SDGraphicsImageRendererFormatRangeStandard; #if SD_UIKIT } #endif @@ -160,7 +167,7 @@ #endif self.scale = screenScale; self.opaque = NO; - self.preferredRange = SDGraphicsImageRendererFormatRangeUnspecified; + self.preferredRange = SDGraphicsImageRendererFormatRangeStandard; #if SD_UIKIT } #endif diff --git a/SDWebImage/Core/SDImageGraphics.h b/SDWebImage/Core/SDImageGraphics.h index 67019c5b..131d6850 100644 --- a/SDWebImage/Core/SDImageGraphics.h +++ b/SDWebImage/Core/SDImageGraphics.h @@ -13,6 +13,7 @@ These following graphics context method are provided to easily write cross-platform(AppKit/UIKit) code. For UIKit, these methods just call the same method in `UIGraphics.h`. See the documentation for usage. For AppKit, these methods use `NSGraphicsContext` to create image context and match the behavior like UIKit. + @note If you don't care bitmap format (ARGB8888) and just draw image, use `SDGraphicsImageRenderer` instead. It's more performant on RAM usage.` */ /// Returns the current graphics context. From 5cb365ece9351c6d50931699837c8739e1aa65d6 Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Sat, 21 Dec 2019 17:20:19 +0800 Subject: [PATCH 11/12] Revert "Replace the background decode core function CGImageCreateDecoded with UIGraphicsRenderer, this one should use the perferred format" This reverts commit 3929f2cd2a354f2fc23b1be819c73451f9ed35e5. --- SDWebImage/Core/SDImageCoderHelper.m | 35 ++++++++++++---------------- 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/SDWebImage/Core/SDImageCoderHelper.m b/SDWebImage/Core/SDImageCoderHelper.m index 1a3391e1..3cc0c7ea 100644 --- a/SDWebImage/Core/SDImageCoderHelper.m +++ b/SDWebImage/Core/SDImageCoderHelper.m @@ -12,7 +12,6 @@ #import "NSData+ImageContentType.h" #import "SDAnimatedImageRep.h" #import "UIImage+ForceDecode.h" -#import "SDGraphicsImageRenderer.h" #import "SDAssociatedObject.h" #if SD_UIKIT || SD_WATCH @@ -258,26 +257,22 @@ static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to over } BOOL hasAlpha = [self CGImageContainsAlpha:cgImage]; + // iOS prefer BGRA8888 (premultiplied) or BGRX8888 bitmapInfo for screen rendering, which is same as `UIGraphicsBeginImageContext()` or `- [CALayer drawInContext:]` + // Though you can use any supported bitmapInfo (see: https://developer.apple.com/library/content/documentation/GraphicsImaging/Conceptual/drawingwithquartz2d/dq_context/dq_context.html#//apple_ref/doc/uid/TP30001066-CH203-BCIBHHBB ) and let Core Graphics reorder it when you call `CGContextDrawImage` + // But since our build-in coders use this bitmapInfo, this can have a little performance benefit + CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Host; + bitmapInfo |= hasAlpha ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaNoneSkipFirst; + CGContextRef context = CGBitmapContextCreate(NULL, newWidth, newHeight, 8, 0, [self colorSpaceGetDeviceRGB], bitmapInfo); + if (!context) { + return NULL; + } - // Using UIGraphicsRenderer on supported platforms, and use the main screeen's perferred format (like Wide Color) - SDGraphicsImageRendererFormat *format = [SDGraphicsImageRendererFormat preferredFormat]; - format.scale = 1; // use pixel instead of point - format.opaque = !hasAlpha; - SDGraphicsImageRenderer *renderer = [[SDGraphicsImageRenderer alloc] initWithSize:CGSizeMake(newWidth, newHeight) format:format]; - UIImage *newImage = [renderer imageWithActions:^(CGContextRef _Nonnull context) { - // Apply transform - CGAffineTransform transform = SDCGContextTransformFromOrientation(orientation, CGSizeMake(newWidth, newHeight)); - CGContextConcatCTM(context, transform); -#if SD_MAC - UIImage *drawImage = [[UIImage alloc] initWithCGImage:cgImage scale:1 orientation:kCGImagePropertyOrientationUp]; -#else - UIImage *drawImage = [[UIImage alloc] initWithCGImage:cgImage scale:1 orientation:UIImageOrientationUp]; -#endif - [drawImage drawInRect:CGRectMake(0, 0, width, height)]; // The rect is bounding box of CGImage, don't swap width & height - }]; - CGImageRef newImageRef = newImage.CGImage; - // Retain the CGImage because this temp `newImage` will release after function return - CGImageRetain(newImageRef); + // Apply transform + CGAffineTransform transform = SDCGContextTransformFromOrientation(orientation, CGSizeMake(newWidth, newHeight)); + CGContextConcatCTM(context, transform); + CGContextDrawImage(context, CGRectMake(0, 0, width, height), cgImage); // The rect is bounding box of CGImage, don't swap width & height + CGImageRef newImageRef = CGBitmapContextCreateImage(context); + CGContextRelease(context); return newImageRef; } From a7682d58b45970bd41727acfd9a04919292f13ba Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Sat, 21 Dec 2019 20:02:13 +0800 Subject: [PATCH 12/12] Add the test case testSDGraphicsImageRenderer, update the documentation --- SDWebImage/Core/SDGraphicsImageRenderer.h | 5 +++-- Tests/Tests/SDUtilsTests.m | 27 +++++++++++++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/SDWebImage/Core/SDGraphicsImageRenderer.h b/SDWebImage/Core/SDGraphicsImageRenderer.h index e450d639..900acd76 100644 --- a/SDWebImage/Core/SDGraphicsImageRenderer.h +++ b/SDWebImage/Core/SDGraphicsImageRenderer.h @@ -10,10 +10,11 @@ /** These following class are provided to use `UIGraphicsImageRenderer` with polyfill, which allows write cross-platform(AppKit/UIKit) code and avoid runtime version check. - Compared to `UIGraphicsBeginImageContext`, `UIGraphicsImageRenderer` use dynamic bitmap info from your draw code to generate CGContext, not always use ARGB8888, which is more performant on RAM usage. + Compared to `UIGraphicsBeginImageContext`, `UIGraphicsImageRenderer` use dynamic bitmap from your draw code to generate CGContext, not always use ARGB8888, which is more performant on RAM usage. + Which means, if you draw CGImage/CIImage which contains grayscale only, the underlaying bitmap context use grayscale, it's managed by system and not a fixed type. (actually, the `kCGContextTypeAutomatic`) For usage, See more in Apple's documentation: https://developer.apple.com/documentation/uikit/uigraphicsimagerenderer For UIKit on iOS/tvOS 10+, these method just use the same `UIGraphicsImageRenderer` API. - For others (macOS/watchOS or iOS/tvOS 10-), these method use the `SDImageGraphics.h` to implements the same behavior. + For others (macOS/watchOS or iOS/tvOS 10-), these method use the `SDImageGraphics.h` to implements the same behavior (but without dynamic bitmap support) */ typedef void (^SDGraphicsImageDrawingActions)(CGContextRef _Nonnull context); diff --git a/Tests/Tests/SDUtilsTests.m b/Tests/Tests/SDUtilsTests.m index 89012e20..4d37f9c4 100644 --- a/Tests/Tests/SDUtilsTests.m +++ b/Tests/Tests/SDUtilsTests.m @@ -12,6 +12,7 @@ #import "SDDisplayLink.h" #import "SDInternalMacros.h" #import "SDFileAttributeHelper.h" +#import "UIColor+HexString.h" @interface SDUtilsTests : SDTestCase @@ -107,6 +108,32 @@ expect(hasAttr).beFalsy(); } +- (void)testSDGraphicsImageRenderer { + // Main Screen + SDGraphicsImageRendererFormat *format = SDGraphicsImageRendererFormat.preferredFormat; +#if SD_UIKIT + CGFloat screenScale = [UIScreen mainScreen].scale; +#elif SD_MAC + CGFloat screenScale = [NSScreen mainScreen].backingScaleFactor; +#endif + expect(format.scale).equal(screenScale); + expect(format.opaque).beFalsy(); +#if SD_UIKIT + expect(format.preferredRange).equal(SDGraphicsImageRendererFormatRangeAutomatic); +#elif SD_MAC + expect(format.preferredRange).equal(SDGraphicsImageRendererFormatRangeStandard); +#endif + CGSize size = CGSizeMake(100, 100); + SDGraphicsImageRenderer *renderer = [[SDGraphicsImageRenderer alloc] initWithSize:size format:format]; + UIColor *color = UIColor.redColor; + UIImage *image = [renderer imageWithActions:^(CGContextRef _Nonnull context) { + [color setFill]; + CGContextFillRect(context, CGRectMake(0, 0, size.width, size.height)); + }]; + expect(image.scale).equal(format.scale); + expect([[image sd_colorAtPoint:CGPointMake(50, 50)].sd_hexString isEqualToString:color.sd_hexString]).beTruthy(); +} + - (void)testSDScaledImageForKey { // Test nil expect(SDScaledImageForKey(nil, nil)).beNil();