Merge pull request #2907 from dreampiggy/performance_UIGraphicsImageRenderer

Performance - Using UIGraphicsImageRenderer on iOS 10+, save memory when image bitmap is RGB(-25%) or Grayscale(-75%)
This commit is contained in:
DreamPiggy 2019-12-23 19:55:05 +08:00 committed by GitHub
commit 69a62ff39d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 442 additions and 107 deletions

View File

@ -52,6 +52,9 @@
3244062C2296C5F400A36084 /* SDWebImageOptionsProcessor.h in Headers */ = {isa = PBXBuildFile; fileRef = 324406292296C5F400A36084 /* SDWebImageOptionsProcessor.h */; settings = {ATTRIBUTES = (Public, ); }; }; 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 */; }; 3244062D2296C5F400A36084 /* SDWebImageOptionsProcessor.m in Sources */ = {isa = PBXBuildFile; fileRef = 3244062A2296C5F400A36084 /* SDWebImageOptionsProcessor.m */; };
3244062E2296C5F400A36084 /* 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 */; }; 3248475D201775F600AF9E5A /* SDAnimatedImageView.m in Sources */ = {isa = PBXBuildFile; fileRef = 32484757201775F600AF9E5A /* SDAnimatedImageView.m */; };
3248475F201775F600AF9E5A /* 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, ); }; }; 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, ); }; }; 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 */; }; 328BB6D32082581100760D6C /* SDMemoryCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 328BB6C02082581100760D6C /* SDMemoryCache.m */; };
328BB6D52082581100760D6C /* 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, ); }; }; 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 */; }; 3290FA0A1FA478AF0047D20C /* SDImageFrame.m in Sources */ = {isa = PBXBuildFile; fileRef = 3290FA031FA478AF0047D20C /* SDImageFrame.m */; };
3290FA0C1FA478AF0047D20C /* 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; dstPath = include/SDWebImage;
dstSubfolderSpec = 16; dstSubfolderSpec = 16;
files = ( files = (
328E9DE523A61DD30051C893 /* SDGraphicsImageRenderer.h in Copy Headers */,
325F7CCD2389467800AEDFCC /* UIImage+ExtendedCacheData.h in Copy Headers */, 325F7CCD2389467800AEDFCC /* UIImage+ExtendedCacheData.h in Copy Headers */,
326E2F36236F1E30006F847F /* SDAnimatedImagePlayer.h in Copy Headers */, 326E2F36236F1E30006F847F /* SDAnimatedImagePlayer.h in Copy Headers */,
3250C9F12355E3DF0093A896 /* SDWebImageDownloaderDecryptor.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 = "<group>"; }; 3240BB6723968FE6003BA07D /* SDAssociatedObject.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDAssociatedObject.m; sourceTree = "<group>"; };
324406292296C5F400A36084 /* SDWebImageOptionsProcessor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SDWebImageOptionsProcessor.h; path = Core/SDWebImageOptionsProcessor.h; sourceTree = "<group>"; }; 324406292296C5F400A36084 /* SDWebImageOptionsProcessor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SDWebImageOptionsProcessor.h; path = Core/SDWebImageOptionsProcessor.h; sourceTree = "<group>"; };
3244062A2296C5F400A36084 /* SDWebImageOptionsProcessor.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = SDWebImageOptionsProcessor.m; path = Core/SDWebImageOptionsProcessor.m; sourceTree = "<group>"; }; 3244062A2296C5F400A36084 /* SDWebImageOptionsProcessor.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = SDWebImageOptionsProcessor.m; path = Core/SDWebImageOptionsProcessor.m; sourceTree = "<group>"; };
3246A70123A567AC00FBEA10 /* SDGraphicsImageRenderer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SDGraphicsImageRenderer.h; path = Core/SDGraphicsImageRenderer.h; sourceTree = "<group>"; };
3246A70223A567AC00FBEA10 /* SDGraphicsImageRenderer.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = SDGraphicsImageRenderer.m; path = Core/SDGraphicsImageRenderer.m; sourceTree = "<group>"; };
32484757201775F600AF9E5A /* SDAnimatedImageView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SDAnimatedImageView.m; path = Core/SDAnimatedImageView.m; sourceTree = "<group>"; }; 32484757201775F600AF9E5A /* SDAnimatedImageView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SDAnimatedImageView.m; path = Core/SDAnimatedImageView.m; sourceTree = "<group>"; };
32484758201775F600AF9E5A /* SDAnimatedImageView+WebCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "SDAnimatedImageView+WebCache.h"; path = "Core/SDAnimatedImageView+WebCache.h"; sourceTree = "<group>"; }; 32484758201775F600AF9E5A /* SDAnimatedImageView+WebCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "SDAnimatedImageView+WebCache.h"; path = "Core/SDAnimatedImageView+WebCache.h"; sourceTree = "<group>"; };
32484759201775F600AF9E5A /* SDAnimatedImageView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SDAnimatedImageView.h; path = Core/SDAnimatedImageView.h; sourceTree = "<group>"; }; 32484759201775F600AF9E5A /* SDAnimatedImageView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SDAnimatedImageView.h; path = Core/SDAnimatedImageView.h; sourceTree = "<group>"; };
@ -577,6 +584,8 @@
32CF1C061FA496B000004BD1 /* SDImageCoderHelper.m */, 32CF1C061FA496B000004BD1 /* SDImageCoderHelper.m */,
3257EAF721898AED0097B271 /* SDImageGraphics.h */, 3257EAF721898AED0097B271 /* SDImageGraphics.h */,
3257EAF821898AED0097B271 /* SDImageGraphics.m */, 3257EAF821898AED0097B271 /* SDImageGraphics.m */,
3246A70123A567AC00FBEA10 /* SDGraphicsImageRenderer.h */,
3246A70223A567AC00FBEA10 /* SDGraphicsImageRenderer.m */,
); );
name = Decoder; name = Decoder;
sourceTree = "<group>"; sourceTree = "<group>";
@ -905,6 +914,7 @@
4A2CAE1D1AB4BB6800B6BC39 /* SDWebImageDownloaderOperation.h in Headers */, 4A2CAE1D1AB4BB6800B6BC39 /* SDWebImageDownloaderOperation.h in Headers */,
4A2CAE2B1AB4BB7500B6BC39 /* UIButton+WebCache.h in Headers */, 4A2CAE2B1AB4BB7500B6BC39 /* UIButton+WebCache.h in Headers */,
4A2CAE251AB4BB7000B6BC39 /* SDWebImagePrefetcher.h in Headers */, 4A2CAE251AB4BB7000B6BC39 /* SDWebImagePrefetcher.h in Headers */,
3246A70323A567AC00FBEA10 /* SDGraphicsImageRenderer.h in Headers */,
328BB6CF2082581100760D6C /* SDMemoryCache.h in Headers */, 328BB6CF2082581100760D6C /* SDMemoryCache.h in Headers */,
325C460F223394D8004CAE11 /* SDImageCachesManagerOperation.h in Headers */, 325C460F223394D8004CAE11 /* SDImageCachesManagerOperation.h in Headers */,
321E60881F38E8C800405457 /* SDImageCoder.h in Headers */, 321E60881F38E8C800405457 /* SDImageCoder.h in Headers */,
@ -1125,6 +1135,7 @@
3290FA0C1FA478AF0047D20C /* SDImageFrame.m in Sources */, 3290FA0C1FA478AF0047D20C /* SDImageFrame.m in Sources */,
325C46232233A02E004CAE11 /* UIColor+HexString.m in Sources */, 325C46232233A02E004CAE11 /* UIColor+HexString.m in Sources */,
325F7CCB238942AB00AEDFCC /* UIImage+ExtendedCacheData.m in Sources */, 325F7CCB238942AB00AEDFCC /* UIImage+ExtendedCacheData.m in Sources */,
3246A70523A567AC00FBEA10 /* SDGraphicsImageRenderer.m in Sources */,
321E60C61F38E91700405457 /* UIImage+ForceDecode.m in Sources */, 321E60C61F38E91700405457 /* UIImage+ForceDecode.m in Sources */,
3244062E2296C5F400A36084 /* SDWebImageOptionsProcessor.m in Sources */, 3244062E2296C5F400A36084 /* SDWebImageOptionsProcessor.m in Sources */,
3250C9F02355D9DA0093A896 /* SDWebImageDownloaderDecryptor.m in Sources */, 3250C9F02355D9DA0093A896 /* SDWebImageDownloaderDecryptor.m in Sources */,
@ -1198,6 +1209,7 @@
3290FA0A1FA478AF0047D20C /* SDImageFrame.m in Sources */, 3290FA0A1FA478AF0047D20C /* SDImageFrame.m in Sources */,
325C46222233A02E004CAE11 /* UIColor+HexString.m in Sources */, 325C46222233A02E004CAE11 /* UIColor+HexString.m in Sources */,
321E60C41F38E91700405457 /* UIImage+ForceDecode.m in Sources */, 321E60C41F38E91700405457 /* UIImage+ForceDecode.m in Sources */,
3246A70423A567AC00FBEA10 /* SDGraphicsImageRenderer.m in Sources */,
3244062D2296C5F400A36084 /* SDWebImageOptionsProcessor.m in Sources */, 3244062D2296C5F400A36084 /* SDWebImageOptionsProcessor.m in Sources */,
3250C9EF2355D9DA0093A896 /* SDWebImageDownloaderDecryptor.m in Sources */, 3250C9EF2355D9DA0093A896 /* SDWebImageDownloaderDecryptor.m in Sources */,
3240BB6523968FA1003BA07D /* SDFileAttributeHelper.m in Sources */, 3240BB6523968FA1003BA07D /* SDFileAttributeHelper.m in Sources */,

View File

@ -0,0 +1,73 @@
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import "SDWebImageCompat.h"
/**
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 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 (but without dynamic bitmap support)
*/
typedef void (^SDGraphicsImageDrawingActions)(CGContextRef _Nonnull context);
typedef NS_ENUM(NSInteger, SDGraphicsImageRendererFormatRange) {
SDGraphicsImageRendererFormatRangeUnspecified = -1,
SDGraphicsImageRendererFormatRangeAutomatic = 0,
SDGraphicsImageRendererFormatRangeExtended,
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;
/// 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 screens 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

View File

@ -0,0 +1,241 @@
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import "SDGraphicsImageRenderer.h"
#import "SDImageGraphics.h"
@interface SDGraphicsImageRendererFormat ()
#if SD_UIKIT
@property (nonatomic, strong) UIGraphicsImageRendererFormat *uiformat API_AVAILABLE(ios(10.0), tvos(10.0));
#endif
@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];
if (self) {
#if SD_UIKIT
if (@available(iOS 10.0, tvOS 10.10, *)) {
UIGraphicsImageRendererFormat *uiformat = [[UIGraphicsImageRendererFormat alloc] init];
self.uiformat = uiformat;
} else {
#endif
#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 = SDGraphicsImageRendererFormatRangeStandard;
#if SD_UIKIT
}
#endif
}
return self;
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunguarded-availability"
- (instancetype)initForMainScreen {
self = [super init];
if (self) {
#if SD_UIKIT
if (@available(iOS 10.0, tvOS 10.0, *)) {
UIGraphicsImageRendererFormat *uiformat;
// 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;
} else {
#endif
#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 = SDGraphicsImageRendererFormatRangeStandard;
#if SD_UIKIT
}
#endif
}
return self;
}
#pragma clang diagnostic pop
+ (instancetype)preferredFormat {
SDGraphicsImageRendererFormat *format = [[SDGraphicsImageRendererFormat alloc] initForMainScreen];
return format;
}
@end
@interface SDGraphicsImageRenderer ()
@property (nonatomic, assign) CGSize size;
@property (nonatomic, strong) SDGraphicsImageRendererFormat *format;
#if SD_UIKIT
@property (nonatomic, strong) UIGraphicsImageRenderer *uirenderer API_AVAILABLE(ios(10.0), tvos(10.0));
#endif
@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 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 SD_UIKIT
if (@available(iOS 10.0, tvOS 10.0, *)) {
UIGraphicsImageDrawingActions uiactions = ^(UIGraphicsImageRendererContext *rendererContext) {
if (actions) {
actions(rendererContext.CGContext);
}
};
return [self.uirenderer imageWithActions:uiactions];
} else {
#endif
SDGraphicsBeginImageContextWithOptions(self.size, self.format.opaque, self.format.scale);
CGContextRef context = SDGraphicsGetCurrentContext();
if (actions) {
actions(context);
}
UIImage *image = SDGraphicsGetImageFromCurrentImageContext();
SDGraphicsEndImageContext();
return image;
#if SD_UIKIT
}
#endif
}
@end

View File

@ -13,6 +13,7 @@
These following graphics context method are provided to easily write cross-platform(AppKit/UIKit) code. 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 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. 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. /// Returns the current graphics context.

View File

@ -9,6 +9,7 @@
#import "UIImage+Transform.h" #import "UIImage+Transform.h"
#import "NSImage+Compatibility.h" #import "NSImage+Compatibility.h"
#import "SDImageGraphics.h" #import "SDImageGraphics.h"
#import "SDGraphicsImageRenderer.h"
#import "NSBezierPath+RoundedCorners.h" #import "NSBezierPath+RoundedCorners.h"
#import <Accelerate/Accelerate.h> #import <Accelerate/Accelerate.h>
#if SD_UIKIT || SD_MAC #if SD_UIKIT || SD_MAC
@ -165,11 +166,10 @@ static inline UIColor * SDGetColorFromPixel(Pixel_8888 pixel, CGBitmapInfo bitma
@implementation UIImage (Transform) @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); CGRect drawRect = SDCGRectFitWithScaleMode(rect, self.size, scaleMode);
if (drawRect.size.width == 0 || drawRect.size.height == 0) return; if (drawRect.size.width == 0 || drawRect.size.height == 0) return;
if (clips) { if (clips) {
CGContextRef context = SDGraphicsGetCurrentContext();
if (context) { if (context) {
CGContextSaveGState(context); CGContextSaveGState(context);
CGContextAddRect(context, rect); 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 { - (nullable UIImage *)sd_resizedImageWithSize:(CGSize)size scaleMode:(SDImageScaleMode)scaleMode {
if (size.width <= 0 || size.height <= 0) return nil; if (size.width <= 0 || size.height <= 0) return nil;
SDGraphicsBeginImageContextWithOptions(size, NO, self.scale); SDGraphicsImageRendererFormat *format = [[SDGraphicsImageRendererFormat alloc] init];
[self sd_drawInRect:CGRectMake(0, 0, size.width, size.height) withScaleMode:scaleMode clipsToBounds:NO]; format.scale = self.scale;
UIImage *image = SDGraphicsGetImageFromCurrentImageContext(); SDGraphicsImageRenderer *renderer = [[SDGraphicsImageRenderer alloc] initWithSize:size format:format];
SDGraphicsEndImageContext(); 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; return image;
} }
@ -213,116 +215,94 @@ 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 { - (nullable UIImage *)sd_roundedCornerImageWithRadius:(CGFloat)cornerRadius corners:(SDRectCorner)corners borderWidth:(CGFloat)borderWidth borderColor:(nullable UIColor *)borderColor {
if (!self.CGImage) return nil; if (!self.CGImage) return nil;
SDGraphicsBeginImageContextWithOptions(self.size, NO, self.scale); SDGraphicsImageRendererFormat *format = [[SDGraphicsImageRendererFormat alloc] init];
CGContextRef context = SDGraphicsGetCurrentContext(); format.scale = self.scale;
CGRect rect = CGRectMake(0, 0, self.size.width, self.size.height); SDGraphicsImageRenderer *renderer = [[SDGraphicsImageRenderer alloc] initWithSize:self.size format:format];
UIImage *image = [renderer imageWithActions:^(CGContextRef _Nonnull context) {
CGFloat minSize = MIN(self.size.width, self.size.height); CGRect rect = CGRectMake(0, 0, 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];
CGContextSaveGState(context); CGFloat minSize = MIN(self.size.width, self.size.height);
[path addClip]; if (borderWidth < minSize / 2) {
[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;
#if SD_UIKIT || SD_WATCH #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 #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 #endif
[path closePath]; [path closePath];
CGContextSaveGState(context);
[path addClip];
[self drawInRect:rect];
CGContextRestoreGState(context);
}
path.lineWidth = borderWidth; if (borderColor && borderWidth < minSize / 2 && borderWidth > 0) {
[borderColor setStroke]; CGFloat strokeInset = (floor(borderWidth * self.scale) + 0.5) / self.scale;
[path stroke]; CGRect strokeRect = CGRectInset(rect, strokeInset, strokeInset);
} CGFloat strokeRadius = cornerRadius > self.scale / 2 ? cornerRadius - self.scale / 2 : 0;
#if SD_UIKIT || SD_WATCH
UIImage *image = SDGraphicsGetImageFromCurrentImageContext(); UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:strokeRect byRoundingCorners:corners cornerRadii:CGSizeMake(strokeRadius, strokeRadius)];
SDGraphicsEndImageContext(); #else
NSBezierPath *path = [NSBezierPath sd_bezierPathWithRoundedRect:strokeRect byRoundingCorners:corners cornerRadius:strokeRadius];
#endif
[path closePath];
path.lineWidth = borderWidth;
[borderColor setStroke];
[path stroke];
}
}];
return image; return image;
} }
- (nullable UIImage *)sd_rotatedImageWithAngle:(CGFloat)angle fitSize:(BOOL)fitSize { - (nullable UIImage *)sd_rotatedImageWithAngle:(CGFloat)angle fitSize:(BOOL)fitSize {
if (!self.CGImage) return nil; if (!self.CGImage) return nil;
size_t width = (size_t)CGImageGetWidth(self.CGImage); size_t width = self.size.width;
size_t height = (size_t)CGImageGetHeight(self.CGImage); size_t height = self.size.height;
CGRect newRect = CGRectApplyAffineTransform(CGRectMake(0., 0., width, height), CGRect newRect = CGRectApplyAffineTransform(CGRectMake(0, 0, width, height),
fitSize ? CGAffineTransformMakeRotation(angle) : CGAffineTransformIdentity); fitSize ? CGAffineTransformMakeRotation(angle) : CGAffineTransformIdentity);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); SDGraphicsImageRendererFormat *format = [[SDGraphicsImageRendererFormat alloc] init];
CGContextRef context = CGBitmapContextCreate(NULL, format.scale = self.scale;
(size_t)newRect.size.width, SDGraphicsImageRenderer *renderer = [[SDGraphicsImageRenderer alloc] initWithSize:newRect.size format:format];
(size_t)newRect.size.height, UIImage *image = [renderer imageWithActions:^(CGContextRef _Nonnull context) {
8, CGContextSetShouldAntialias(context, true);
(size_t)newRect.size.width * 4, CGContextSetAllowsAntialiasing(context, true);
colorSpace, CGContextSetInterpolationQuality(context, kCGInterpolationHigh);
kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedFirst); CGContextTranslateCTM(context, +(newRect.size.width * 0.5), +(newRect.size.height * 0.5));
CGColorSpaceRelease(colorSpace); #if SD_UIKIT
if (!context) return nil; // Use UIKit coordinate system counterclockwise ()
CGContextRotateCTM(context, -angle);
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 #else
UIImage *img = [[UIImage alloc] initWithCGImage:imgRef scale:self.scale orientation:kCGImagePropertyOrientationUp]; CGContextRotateCTM(context, angle);
#endif #endif
CGImageRelease(imgRef);
CGContextRelease(context); [self drawInRect:CGRectMake(-(width * 0.5), -(height * 0.5), width, height)];
return img; }];
return image;
} }
- (nullable UIImage *)sd_flippedImageWithHorizontal:(BOOL)horizontal vertical:(BOOL)vertical { - (nullable UIImage *)sd_flippedImageWithHorizontal:(BOOL)horizontal vertical:(BOOL)vertical {
if (!self.CGImage) return nil; if (!self.CGImage) return nil;
size_t width = (size_t)CGImageGetWidth(self.CGImage); size_t width = self.size.width;
size_t height = (size_t)CGImageGetHeight(self.CGImage); size_t height = self.size.height;
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); SDGraphicsImageRendererFormat *format = [[SDGraphicsImageRendererFormat alloc] init];
UInt8 *data = (UInt8 *)CGBitmapContextGetData(context); format.scale = self.scale;
if (!data) { SDGraphicsImageRenderer *renderer = [[SDGraphicsImageRenderer alloc] initWithSize:self.size format:format];
CGContextRelease(context); UIImage *image = [renderer imageWithActions:^(CGContextRef _Nonnull context) {
return nil; // Use UIKit coordinate system
} if (horizontal) {
vImage_Buffer src = { data, height, width, bytesPerRow }; CGAffineTransform flipHorizontal = CGAffineTransformMake(-1, 0, 0, 1, width, 0);
vImage_Buffer dest = { data, height, width, bytesPerRow }; CGContextConcatCTM(context, flipHorizontal);
if (vertical) { }
vImageVerticalReflect_ARGB8888(&src, &dest, kvImageBackgroundColorFill); if (vertical) {
} CGAffineTransform flipVertical = CGAffineTransformMake(1, 0, 0, -1, 0, height);
if (horizontal) { CGContextConcatCTM(context, flipVertical);
vImageHorizontalReflect_ARGB8888(&src, &dest, kvImageBackgroundColorFill); }
} [self drawInRect:CGRectMake(0, 0, width, height)];
CGImageRef imgRef = CGBitmapContextCreateImage(context); }];
CGContextRelease(context); return image;
#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;
} }
#pragma mark - Image Blending #pragma mark - Image Blending
@ -347,15 +327,15 @@ static inline UIColor * SDGetColorFromPixel(Pixel_8888 pixel, CGBitmapInfo bitma
// blend mode, see https://en.wikipedia.org/wiki/Alpha_compositing // blend mode, see https://en.wikipedia.org/wiki/Alpha_compositing
CGBlendMode blendMode = kCGBlendModeSourceAtop; CGBlendMode blendMode = kCGBlendModeSourceAtop;
SDGraphicsBeginImageContextWithOptions(size, NO, scale); SDGraphicsImageRendererFormat *format = [[SDGraphicsImageRendererFormat alloc] init];
CGContextRef context = SDGraphicsGetCurrentContext(); format.scale = scale;
[self drawInRect:rect]; SDGraphicsImageRenderer *renderer = [[SDGraphicsImageRenderer alloc] initWithSize:size format:format];
CGContextSetBlendMode(context, blendMode); UIImage *image = [renderer imageWithActions:^(CGContextRef _Nonnull context) {
CGContextSetFillColorWithColor(context, tintColor.CGColor); [self drawInRect:rect];
CGContextFillRect(context, rect); CGContextSetBlendMode(context, blendMode);
UIImage *image = SDGraphicsGetImageFromCurrentImageContext(); CGContextSetFillColorWithColor(context, tintColor.CGColor);
SDGraphicsEndImageContext(); CGContextFillRect(context, rect);
}];
return image; return image;
} }

View File

@ -73,7 +73,7 @@
expect(CGSizeEqualToSize(rotatedImage.size, self.testImage.size)).beTruthy(); expect(CGSizeEqualToSize(rotatedImage.size, self.testImage.size)).beTruthy();
// Fit size, may change size // Fit size, may change size
rotatedImage = [self.testImage sd_rotatedImageWithAngle:angle fitSize:YES]; 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(); expect(CGSizeEqualToSize(rotatedImage.size, rotatedSize)).beTruthy();
// Check image not inversion // Check image not inversion
UIColor *leftCenterColor = [rotatedImage sd_colorAtPoint:CGPointMake(60, 175)]; UIColor *leftCenterColor = [rotatedImage sd_colorAtPoint:CGPointMake(60, 175)];

View File

@ -12,6 +12,7 @@
#import "SDDisplayLink.h" #import "SDDisplayLink.h"
#import "SDInternalMacros.h" #import "SDInternalMacros.h"
#import "SDFileAttributeHelper.h" #import "SDFileAttributeHelper.h"
#import "UIColor+HexString.h"
@interface SDUtilsTests : SDTestCase @interface SDUtilsTests : SDTestCase
@ -107,6 +108,32 @@
expect(hasAttr).beFalsy(); 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 { - (void)testSDScaledImageForKey {
// Test nil // Test nil
expect(SDScaledImageForKey(nil, nil)).beNil(); expect(SDScaledImageForKey(nil, nil)).beNil();

View File

@ -65,6 +65,7 @@ FOUNDATION_EXPORT const unsigned char WebImageVersionString[];
#import <SDWebImage/SDImageFrame.h> #import <SDWebImage/SDImageFrame.h>
#import <SDWebImage/SDImageCoderHelper.h> #import <SDWebImage/SDImageCoderHelper.h>
#import <SDWebImage/SDImageGraphics.h> #import <SDWebImage/SDImageGraphics.h>
#import <SDWebImage/SDGraphicsImageRenderer.h>
#import <SDWebImage/UIImage+GIF.h> #import <SDWebImage/UIImage+GIF.h>
#import <SDWebImage/UIImage+ForceDecode.h> #import <SDWebImage/UIImage+ForceDecode.h>
#import <SDWebImage/NSData+ImageContentType.h> #import <SDWebImage/NSData+ImageContentType.h>