From a88e6694229b627ef32c2cfdee956a8999eb1159 Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Sat, 16 Jul 2022 16:27:30 +0800 Subject: [PATCH] Added SDImageCoderDecodeSolutionAutomatic, which check image format as well This avoid the unwanted CMPhoto log --- SDWebImage/Core/SDImageCoderHelper.h | 12 ++- SDWebImage/Core/SDImageCoderHelper.m | 136 ++++++++++++++++++-------- SDWebImage/Core/UIImage+ForceDecode.m | 1 + 3 files changed, 102 insertions(+), 47 deletions(-) diff --git a/SDWebImage/Core/SDImageCoderHelper.h b/SDWebImage/Core/SDImageCoderHelper.h index b808f63b..28e12401 100644 --- a/SDWebImage/Core/SDImageCoderHelper.h +++ b/SDWebImage/Core/SDImageCoderHelper.h @@ -11,9 +11,11 @@ #import "SDImageFrame.h" typedef NS_ENUM(NSUInteger, SDImageCoderDecodeSolution) { - /// always use the CoreGraphics to redraw on bitmap context, best compatibility + /// automatically choose the solution based on image format, hardware, OS version. This keep balance for compatibility and performance. Default after SDWebImage 5.13.0 + SDImageCoderDecodeSolutionAutomatic, + /// always use CoreGraphics to draw on bitmap context and trigger decode. Best compatibility. Default before SDWebImage 5.13.0 SDImageCoderDecodeSolutionCoreGraphics, - /// available on iOS/tvOS 15+, use UIKit's new CGImageDecompressor, best performance. If failed, will fallback to CoreGraphics as well + /// available on iOS/tvOS 15+, use UIKit's new CGImageDecompressor/CMPhoto to decode. Best performance. If failed, will fallback to CoreGraphics as well SDImageCoderDecodeSolutionUIKit }; @@ -118,9 +120,9 @@ typedef NS_ENUM(NSUInteger, SDImageCoderDecodeSolution) { */ + (UIImage * _Nullable)decodedAndScaledDownImageWithImage:(UIImage * _Nullable)image limitBytes:(NSUInteger)bytes; -/** Control the default force decode solution. Available solutions in `SDImageCoderDecodeSolution`. - @note Defaults to `SDImageCoderDecodeSolutionCoreGraphics` - @note You can opt-in to use `SDImageCoderDecodeSolutionUIKit`, which will prefer to use UIKit on iOS 15, and fallback to CoreGraphics instead. +/** + Control the default force decode solution. Available solutions in `SDImageCoderDecodeSolution`. + @note Defaults to `SDImageCoderDecodeSolutionAutomatic`, which prefers to use UIKit for JPEG/HEIF, and fallback on CoreGraphics. If you want control on your hand, set the other solution. */ @property (class, readwrite) SDImageCoderDecodeSolution defaultDecodeSolution; diff --git a/SDWebImage/Core/SDImageCoderHelper.m b/SDWebImage/Core/SDImageCoderHelper.m index 0a17adca..da30b65a 100644 --- a/SDWebImage/Core/SDImageCoderHelper.m +++ b/SDWebImage/Core/SDImageCoderHelper.m @@ -16,13 +16,63 @@ #import "UIImage+Metadata.h" #import "SDInternalMacros.h" #import "SDGraphicsImageRenderer.h" +#import "SDInternalMacros.h" #import static inline size_t SDByteAlign(size_t size, size_t alignment) { return ((size + (alignment - 1)) / alignment) * alignment; } -static SDImageCoderDecodeSolution kDefaultDecodeSolution = SDImageCoderDecodeSolutionCoreGraphics; +#if SD_UIKIT +static inline UIImage *SDImageDecodeUIKit(UIImage *image) { + // See: https://developer.apple.com/documentation/uikit/uiimage/3750834-imagebypreparingfordisplay + // Need CGImage-based + if (@available(iOS 15, tvOS 15, *)) { + UIImage *decodedImage = [image imageByPreparingForDisplay]; + if (decodedImage) { + SDImageCopyAssociatedObject(image, decodedImage); + decodedImage.sd_isDecoded = YES; + return decodedImage; + } + } + return nil; +} + +static inline UIImage *SDImageDecodeAndScaleDownUIKit(UIImage *image, CGSize destResolution) { + // See: https://developer.apple.com/documentation/uikit/uiimage/3750835-imagebypreparingthumbnailofsize + // Need CGImage-based + if (@available(iOS 15, tvOS 15, *)) { + // Calculate thumbnail point size + CGFloat scale = image.scale ?: 1; + CGSize thumbnailSize = CGSizeMake(destResolution.width / scale, destResolution.height / scale); + UIImage *decodedImage = [image imageByPreparingThumbnailOfSize:thumbnailSize]; + if (decodedImage) { + SDImageCopyAssociatedObject(image, decodedImage); + decodedImage.sd_isDecoded = YES; + return decodedImage; + } + } + return nil; +} + +static inline BOOL SDImageSupportsHardwareHEVCDecoder(void) { + static dispatch_once_t onceToken; + static BOOL supportsHardware = NO; + dispatch_once(&onceToken, ^{ + SEL DeviceInfoSelector = SD_SEL_SPI(deviceInfoForKey:); + NSString *HEVCDecoder8bitSupported = @"N8lZxRgC7lfdRS3dRLn+Ag"; +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Warc-performSelector-leaks" + if ([UIDevice.currentDevice respondsToSelector:DeviceInfoSelector] && [UIDevice.currentDevice performSelector:DeviceInfoSelector withObject:HEVCDecoder8bitSupported]) { + supportsHardware = YES; + } +#pragma clang diagnostic pop + }); + return supportsHardware; +} +#endif + +static SDImageCoderDecodeSolution kDefaultDecodeSolution = SDImageCoderDecodeSolutionAutomatic; static const size_t kBytesPerPixel = 4; static const size_t kBitsPerComponent = 8; @@ -369,18 +419,23 @@ static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to over return image; } + UIImage *decodedImage; #if SD_UIKIT - // See: https://developer.apple.com/documentation/uikit/uiimage/3750834-imagebypreparingfordisplay - // Need CGImage-based - if (self.defaultDecodeSolution == SDImageCoderDecodeSolutionUIKit) { - if (@available(iOS 15, tvOS 15, *)) { - UIImage *decodedImage = [image imageByPreparingForDisplay]; - if (decodedImage) { - SDImageCopyAssociatedObject(image, decodedImage); - decodedImage.sd_isDecoded = YES; - return decodedImage; - } + SDImageCoderDecodeSolution decodeSolution = self.defaultDecodeSolution; + if (decodeSolution == SDImageCoderDecodeSolutionAutomatic) { + // See #3365, CMPhoto iOS 15 only supports JPEG/HEIF format, or it will print an error log :( + SDImageFormat format = image.sd_imageFormat; + if ((format == SDImageFormatHEIC || format == SDImageFormatHEIF) && SDImageSupportsHardwareHEVCDecoder()) { + decodedImage = SDImageDecodeUIKit(image); + } else if (format == SDImageFormatJPEG) { + decodedImage = SDImageDecodeUIKit(image); } + } else if (decodeSolution == SDImageCoderDecodeSolutionUIKit) { + // Arbitrarily call CMPhoto + decodedImage = SDImageDecodeUIKit(image); + } + if (decodedImage) { + return decodedImage; } #endif @@ -396,7 +451,7 @@ static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to over format.scale = image.scale; CGSize imageSize = image.size; SDGraphicsImageRenderer *renderer = [[SDGraphicsImageRenderer alloc] initWithSize:imageSize format:format]; - UIImage *decodedImage = [renderer imageWithActions:^(CGContextRef _Nonnull context) { + decodedImage = [renderer imageWithActions:^(CGContextRef _Nonnull context) { [image drawInRect:CGRectMake(0, 0, imageSize.width, imageSize.height)]; }]; SDImageCopyAssociatedObject(image, decodedImage); @@ -436,26 +491,26 @@ static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to over destResolution.width = MAX(1, (int)(sourceResolution.width * imageScale)); destResolution.height = MAX(1, (int)(sourceResolution.height * imageScale)); + UIImage *decodedImage; #if SD_UIKIT - // See: https://developer.apple.com/documentation/uikit/uiimage/3750835-imagebypreparingthumbnailofsize - // Need CGImage-based - if (self.defaultDecodeSolution == SDImageCoderDecodeSolutionUIKit) { - if (@available(iOS 15, tvOS 15, *)) { - // Calculate thumbnail point size - CGFloat scale = image.scale ?: 1; - CGSize thumbnailSize = CGSizeMake(destResolution.width / scale, destResolution.height / scale); - UIImage *decodedImage = [image imageByPreparingThumbnailOfSize:thumbnailSize]; - if (decodedImage) { - SDImageCopyAssociatedObject(image, decodedImage); - decodedImage.sd_isDecoded = YES; - return decodedImage; - } + SDImageCoderDecodeSolution decodeSolution = self.defaultDecodeSolution; + if (decodeSolution == SDImageCoderDecodeSolutionAutomatic) { + // See #3365, CMPhoto iOS 15 only supports JPEG/HEIF format, or it will print an error log :( + SDImageFormat format = image.sd_imageFormat; + if ((format == SDImageFormatHEIC || format == SDImageFormatHEIF) && SDImageSupportsHardwareHEVCDecoder()) { + decodedImage = SDImageDecodeAndScaleDownUIKit(image, destResolution); + } else if (format == SDImageFormatJPEG) { + decodedImage = SDImageDecodeAndScaleDownUIKit(image, destResolution); } + } else if (decodeSolution == SDImageCoderDecodeSolutionUIKit) { + // Arbitrarily call CMPhoto + decodedImage = SDImageDecodeAndScaleDownUIKit(image, destResolution); + } + if (decodedImage) { + return decodedImage; } #endif - CGContextRef destContext = NULL; - // autorelease the bitmap context and all vars to help system to free memory when there are memory warning. // on iOS7, do not forget to call [[SDImageCache sharedImageCache] clearMemory]; @autoreleasepool { @@ -475,13 +530,13 @@ static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to over // RGB888 bitmapInfo = kCGBitmapByteOrderDefault | kCGImageAlphaNoneSkipLast; } - destContext = CGBitmapContextCreate(NULL, - destResolution.width, - destResolution.height, - kBitsPerComponent, - 0, - colorspaceRef, - bitmapInfo); + CGContextRef destContext = CGBitmapContextCreate(NULL, + destResolution.width, + destResolution.height, + kBitsPerComponent, + 0, + colorspaceRef, + bitmapInfo); if (destContext == NULL) { return image; @@ -549,17 +604,14 @@ static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to over return image; } #if SD_MAC - UIImage *destImage = [[UIImage alloc] initWithCGImage:destImageRef scale:image.scale orientation:kCGImagePropertyOrientationUp]; + decodedImage = [[UIImage alloc] initWithCGImage:destImageRef scale:image.scale orientation:kCGImagePropertyOrientationUp]; #else - UIImage *destImage = [[UIImage alloc] initWithCGImage:destImageRef scale:image.scale orientation:image.imageOrientation]; + decodedImage = [[UIImage alloc] initWithCGImage:destImageRef scale:image.scale orientation:image.imageOrientation]; #endif CGImageRelease(destImageRef); - if (destImage == nil) { - return image; - } - SDImageCopyAssociatedObject(image, destImage); - destImage.sd_isDecoded = YES; - return destImage; + SDImageCopyAssociatedObject(image, decodedImage); + decodedImage.sd_isDecoded = YES; + return decodedImage; } } diff --git a/SDWebImage/Core/UIImage+ForceDecode.m b/SDWebImage/Core/UIImage+ForceDecode.m index d4fbff66..9fc72588 100644 --- a/SDWebImage/Core/UIImage+ForceDecode.m +++ b/SDWebImage/Core/UIImage+ForceDecode.m @@ -9,6 +9,7 @@ #import "UIImage+ForceDecode.h" #import "SDImageCoderHelper.h" #import "objc/runtime.h" +#import "NSImage+Compatibility.h" @implementation UIImage (ForceDecode)