Provide the API for custom coders (like WebPCoder) to use runtime check to handle the decode output bitmap info

Include: colorspace, byte alignment, bitmap info

Remove the exists hardcode on BGRA8888
This commit is contained in:
DreamPiggy 2023-07-09 19:32:02 +08:00
parent f5f27a9e01
commit b4559994ea
3 changed files with 93 additions and 41 deletions

View File

@ -20,6 +20,14 @@ typedef NS_ENUM(NSUInteger, SDImageCoderDecodeSolution) {
SDImageCoderDecodeSolutionUIKit
};
/// Byte alignment the bytes size with alignment
/// - Parameters:
/// - size: The bytes size
/// - alignment: The alignment, in bytes
static inline size_t SDByteAlign(size_t size, size_t alignment) {
return ((size + (alignment - 1)) / alignment) * alignment;
}
/**
Provide some common helper methods for building the image decoder/encoder.
*/
@ -45,16 +53,32 @@ typedef NS_ENUM(NSUInteger, SDImageCoderDecodeSolution) {
*/
+ (NSArray<SDImageFrame *> * _Nullable)framesFromAnimatedImage:(UIImage * _Nullable)animatedImage NS_SWIFT_NAME(frames(from:));
#pragma mark - Preferred Rendering Format
/// For coders who use `CGImageCreate`, use the information below to create an effient CGImage which can be render on GPU without Core Animation's extra copy (`CA::Render::copy_image`), which can be debugged using `Color Copied Image` in Xcode Instruments
/// `CGImageCreate`'s `bytesPerRow`, `space`, `bitmapInfo` params should use the information below.
/**
Return the shared device-dependent RGB color space. This follows The Get Rule.
On iOS, it's created with deviceRGB (if available, use sRGB).
On macOS, it's from the screen colorspace (if failed, use deviceRGB)
Because it's shared, you should not retain or release this object.
Typically is sRGB, the color space defaults to rendering on Apple's devices.
@return The device-dependent RGB color space
*/
+ (CGColorSpaceRef _Nonnull)colorSpaceGetDeviceRGB CF_RETURNS_NOT_RETAINED;
/**
From v5.17.0, this returns the byte alignment used for bytesPerRow(stride) **Preferred from current hardward && OS using runtime detection**
Typically is 64, the system page size.
@note To calculate the bytesPerRow, use the formula `SDByteAlign(width * (bitsPerPixel / 8), alignment)`
*/
+ (size_t)preferredByteAlignment;
/**
From v5.17.0, this returns the bitmap info **Preferred from current hardward && OS using runtime detection**
Typically is pre-multiplied RGBA8888 for alpha image, RGBX8888 for non-alpha image.
@param containsAlpha Whether the image to render contains alpha channel
*/
+ (CGBitmapInfo)preferredBitmapInfo:(BOOL)containsAlpha;
/**
Check whether CGImage contains alpha channel.

View File

@ -19,10 +19,6 @@
#import "SDInternalMacros.h"
#import <Accelerate/Accelerate.h>
static inline size_t SDByteAlign(size_t size, size_t alignment) {
return ((size + (alignment - 1)) / alignment) * alignment;
}
#if SD_UIKIT
static inline UIImage *SDImageDecodeUIKit(UIImage *image) {
// See: https://developer.apple.com/documentation/uikit/uiimage/3750834-imagebypreparingfordisplay
@ -72,6 +68,42 @@ static inline BOOL SDImageSupportsHardwareHEVCDecoder(void) {
}
#endif
static UIImage * _Nonnull SDImageGetAlphaDummyImage(void) {
static dispatch_once_t onceToken;
static UIImage *dummyImage;
dispatch_once(&onceToken, ^{
SDGraphicsImageRendererFormat *format = [SDGraphicsImageRendererFormat preferredFormat];
format.scale = 1;
format.opaque = NO;
CGSize size = CGSizeMake(1, 1);
SDGraphicsImageRenderer *renderer = [[SDGraphicsImageRenderer alloc] initWithSize:size format:format];
dummyImage = [renderer imageWithActions:^(CGContextRef _Nonnull context) {
CGContextSetFillColorWithColor(context, UIColor.redColor.CGColor);
CGContextFillRect(context, CGRectMake(0, 0, size.width, size.height));
}];
NSCAssert(dummyImage, @"The sample alpha image (1x1 pixels) returns nil, OS bug ?");
});
return dummyImage;
}
static UIImage * _Nonnull SDImageGetNonAlphaDummyImage(void) {
static dispatch_once_t onceToken;
static UIImage *dummyImage;
dispatch_once(&onceToken, ^{
SDGraphicsImageRendererFormat *format = [SDGraphicsImageRendererFormat preferredFormat];
format.scale = 1;
format.opaque = YES;
CGSize size = CGSizeMake(1, 1);
SDGraphicsImageRenderer *renderer = [[SDGraphicsImageRenderer alloc] initWithSize:size format:format];
dummyImage = [renderer imageWithActions:^(CGContextRef _Nonnull context) {
CGContextSetFillColorWithColor(context, UIColor.redColor.CGColor);
CGContextFillRect(context, CGRectMake(0, 0, size.width, size.height));
}];
NSCAssert(dummyImage, @"The sample non-alpha image (1x1 pixels) returns nil, OS bug ?");
});
return dummyImage;
}
static SDImageCoderDecodeSolution kDefaultDecodeSolution = SDImageCoderDecodeSolutionAutomatic;
static const size_t kBytesPerPixel = 4;
@ -263,6 +295,26 @@ static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to over
return colorSpace;
}
+ (size_t)preferredByteAlignment {
// Actually the page size of system
static int __pageSize = 0;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
__pageSize = getpagesize();
});
return __pageSize;
}
+ (CGBitmapInfo)preferredBitmapInfo:(BOOL)containsAlpha {
CGImageRef cgImage;
if (containsAlpha) {
cgImage = SDImageGetAlphaDummyImage().CGImage;
} else {
cgImage = SDImageGetNonAlphaDummyImage().CGImage;
}
return CGImageGetBitmapInfo(cgImage);
}
+ (BOOL)CGImageContainsAlpha:(CGImageRef)cgImage {
if (!cgImage) {
return NO;
@ -307,16 +359,8 @@ static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to over
BOOL hasAlpha = [self CGImageContainsAlpha:cgImage];
// kCGImageAlphaNone is not supported in CGBitmapContextCreate.
// Check #3330 for more detail about why this bitmap is choosen.
CGBitmapInfo bitmapInfo;
if (hasAlpha) {
// iPhone GPU prefer to use BGRA8888, see: https://forums.raywenderlich.com/t/why-mtlpixelformat-bgra8unorm/53489
// BGRA8888
bitmapInfo = kCGBitmapByteOrder32Host | kCGImageAlphaPremultipliedFirst;
} else {
// BGR888 previously works on iOS 8~iOS 14, however, iOS 15+ will result a black image. FB9958017
// RGB888
bitmapInfo = kCGBitmapByteOrderDefault | kCGImageAlphaNoneSkipLast;
}
// From v5.17.0, use runtime detection of bitmap info instead of hardcode.
CGBitmapInfo bitmapInfo = [SDImageCoderHelper preferredBitmapInfo:hasAlpha];
CGContextRef context = CGBitmapContextCreate(NULL, newWidth, newHeight, 8, 0, [self colorSpaceGetDeviceRGB], bitmapInfo);
if (!context) {
return NULL;
@ -351,16 +395,8 @@ static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to over
BOOL hasAlpha = [self CGImageContainsAlpha:cgImage];
// kCGImageAlphaNone is not supported in CGBitmapContextCreate.
// Check #3330 for more detail about why this bitmap is choosen.
CGBitmapInfo bitmapInfo;
if (hasAlpha) {
// iPhone GPU prefer to use BGRA8888, see: https://forums.raywenderlich.com/t/why-mtlpixelformat-bgra8unorm/53489
// BGRA8888
bitmapInfo = kCGBitmapByteOrder32Host | kCGImageAlphaPremultipliedFirst;
} else {
// BGR888 previously works on iOS 8~iOS 14, however, iOS 15+ will result a black image. FB9958017
// RGB888
bitmapInfo = kCGBitmapByteOrderDefault | kCGImageAlphaNoneSkipLast;
}
// From v5.17.0, use runtime detection of bitmap info instead of hardcode.
CGBitmapInfo bitmapInfo = [SDImageCoderHelper preferredBitmapInfo:hasAlpha];
vImage_CGImageFormat format = (vImage_CGImageFormat) {
.bitsPerComponent = 8,
.bitsPerPixel = 32,
@ -563,16 +599,8 @@ static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to over
// kCGImageAlphaNone is not supported in CGBitmapContextCreate.
// Check #3330 for more detail about why this bitmap is choosen.
CGBitmapInfo bitmapInfo;
if (hasAlpha) {
// iPhone GPU prefer to use BGRA8888, see: https://forums.raywenderlich.com/t/why-mtlpixelformat-bgra8unorm/53489
// BGRA8888
bitmapInfo = kCGBitmapByteOrder32Host | kCGImageAlphaPremultipliedFirst;
} else {
// BGR888 previously works on iOS 8~iOS 14, however, iOS 15+ will result a black image. FB9958017
// RGB888
bitmapInfo = kCGBitmapByteOrderDefault | kCGImageAlphaNoneSkipLast;
}
// From v5.17.0, use runtime detection of bitmap info instead of hardcode.
CGBitmapInfo bitmapInfo = [SDImageCoderHelper preferredBitmapInfo:hasAlpha];
CGContextRef destContext = CGBitmapContextCreate(NULL,
destResolution.width,
destResolution.height,

View File

@ -32,14 +32,14 @@ static CGContextRef SDCGContextCreateBitmapContext(CGSize size, BOOL opaque, CGF
CGColorSpaceRef space = [SDImageCoderHelper colorSpaceGetDeviceRGB];
// kCGImageAlphaNone is not supported in CGBitmapContextCreate.
// Check #3330 for more detail about why this bitmap is choosen.
// From v5.17.0, use runtime detection of bitmap info instead of hardcode.
// However, macOS's runtime detection will also call this function, cause recursive, so still hardcode here
// CGBitmapInfo bitmapInfo = [SDImageCoderHelper preferredBitmapInfo:hasAlpha];
CGBitmapInfo bitmapInfo;
if (!opaque) {
// iPhone GPU prefer to use BGRA8888, see: https://forums.raywenderlich.com/t/why-mtlpixelformat-bgra8unorm/53489
// BGRA8888
bitmapInfo = kCGBitmapByteOrder32Host | kCGImageAlphaPremultipliedFirst;
// [NSImage imageWithSize:flipped:drawingHandler:] returns float(16-bits) RGBA8888 on alpha image, which we don't need
bitmapInfo = kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedLast;
} else {
// BGR888 previously works on iOS 8~iOS 14, however, iOS 15+ will result a black image. FB9958017
// RGB888
bitmapInfo = kCGBitmapByteOrderDefault | kCGImageAlphaNoneSkipLast;
}
CGContextRef context = CGBitmapContextCreate(NULL, width, height, 8, 0, space, bitmapInfo);