Merge pull request #3334 from dreampiggy/bugfix_force_decode_use_image_renderer_and_argb8888
Workaround iOS 15+ force decode again using Image Renderer(preferred) and RGB888
This commit is contained in:
commit
4974524a47
|
@ -34,7 +34,7 @@
|
||||||
|
|
||||||
// NSImageView + Animated Image
|
// NSImageView + Animated Image
|
||||||
self.imageView2.sd_imageIndicator = SDWebImageActivityIndicator.largeIndicator;
|
self.imageView2.sd_imageIndicator = SDWebImageActivityIndicator.largeIndicator;
|
||||||
[self.imageView2 sd_setImageWithURL:[NSURL URLWithString:@"https:raw.githubusercontent.com/onevcat/APNGKit/master/TestImages/APNG-cube.apng"]];
|
[self.imageView2 sd_setImageWithURL:[NSURL URLWithString:@"https://raw.githubusercontent.com/onevcat/APNGKit/2.2.0/Tests/APNGKitTests/Resources/General/APNG-cube.apng"]];
|
||||||
NSMenu *menu1 = [[NSMenu alloc] initWithTitle:@"Toggle Animation"];
|
NSMenu *menu1 = [[NSMenu alloc] initWithTitle:@"Toggle Animation"];
|
||||||
NSMenuItem *item1 = [menu1 addItemWithTitle:@"Toggle Animation" action:@selector(toggleAnimation:) keyEquivalent:@""];
|
NSMenuItem *item1 = [menu1 addItemWithTitle:@"Toggle Animation" action:@selector(toggleAnimation:) keyEquivalent:@""];
|
||||||
item1.tag = 1;
|
item1.tag = 1;
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
#import "SDAssociatedObject.h"
|
#import "SDAssociatedObject.h"
|
||||||
#import "UIImage+Metadata.h"
|
#import "UIImage+Metadata.h"
|
||||||
#import "SDInternalMacros.h"
|
#import "SDInternalMacros.h"
|
||||||
|
#import "SDGraphicsImageRenderer.h"
|
||||||
#import <Accelerate/Accelerate.h>
|
#import <Accelerate/Accelerate.h>
|
||||||
|
|
||||||
static inline size_t SDByteAlign(size_t size, size_t alignment) {
|
static inline size_t SDByteAlign(size_t size, size_t alignment) {
|
||||||
|
@ -188,24 +189,10 @@ static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to over
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (CGColorSpaceRef)colorSpaceGetDeviceRGB {
|
+ (CGColorSpaceRef)colorSpaceGetDeviceRGB {
|
||||||
#if SD_MAC
|
|
||||||
CGColorSpaceRef screenColorSpace = NSScreen.mainScreen.colorSpace.CGColorSpace;
|
|
||||||
if (screenColorSpace) {
|
|
||||||
return screenColorSpace;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
static CGColorSpaceRef colorSpace;
|
static CGColorSpaceRef colorSpace;
|
||||||
static dispatch_once_t onceToken;
|
static dispatch_once_t onceToken;
|
||||||
dispatch_once(&onceToken, ^{
|
dispatch_once(&onceToken, ^{
|
||||||
#if SD_UIKIT
|
colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
|
||||||
if (@available(iOS 9.0, tvOS 9.0, *)) {
|
|
||||||
colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
|
|
||||||
} else {
|
|
||||||
colorSpace = CGColorSpaceCreateDeviceRGB();
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
colorSpace = CGColorSpaceCreateDeviceRGB();
|
|
||||||
#endif
|
|
||||||
});
|
});
|
||||||
return colorSpace;
|
return colorSpace;
|
||||||
}
|
}
|
||||||
|
@ -252,22 +239,19 @@ static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to over
|
||||||
}
|
}
|
||||||
|
|
||||||
BOOL hasAlpha = [self CGImageContainsAlpha:cgImage];
|
BOOL hasAlpha = [self CGImageContainsAlpha:cgImage];
|
||||||
// iOS prefer BGRA8888 (premultiplied) or BGRX8888 bitmapInfo for screen rendering, which is same as `UIGraphicsBeginImageContext()` or `- [CALayer drawInContext:]`
|
// kCGImageAlphaNone is not supported in CGBitmapContextCreate.
|
||||||
// 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`
|
// Check #3330 for more detail about why this bitmap is choosen.
|
||||||
// But since our build-in coders use this bitmapInfo, this can have a little performance benefit
|
|
||||||
CGBitmapInfo bitmapInfo;
|
CGBitmapInfo bitmapInfo;
|
||||||
CGContextRef context = NULL;
|
if (hasAlpha) {
|
||||||
if (@available(iOS 15, tvOS 15, macOS 12, watchOS 8, *)) {
|
// iPhone GPU prefer to use BGRA8888, see: https://forums.raywenderlich.com/t/why-mtlpixelformat-bgra8unorm/53489
|
||||||
// Update for iOS 15: CoreGraphics's draw image will fail to transcode and draw some special CGImage on BGRX8888
|
// BGRA8888
|
||||||
// We prefer to use the input CGImage's bitmap firstly, then fallback to BGRAX8888. See #3330
|
bitmapInfo = kCGBitmapByteOrder32Host | kCGImageAlphaPremultipliedFirst;
|
||||||
bitmapInfo = CGImageGetBitmapInfo(cgImage);
|
} else {
|
||||||
context = CGBitmapContextCreate(NULL, newWidth, newHeight, 8, 0, [self colorSpaceGetDeviceRGB], bitmapInfo);
|
// BGR888 previously works on iOS 8~iOS 14, however, iOS 15+ will result a black image. FB9958017
|
||||||
}
|
// RGB888
|
||||||
if (!context) {
|
bitmapInfo = kCGBitmapByteOrderDefault | kCGImageAlphaNoneSkipLast;
|
||||||
bitmapInfo = kCGBitmapByteOrder32Host;
|
|
||||||
bitmapInfo |= hasAlpha ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaNoneSkipFirst;
|
|
||||||
context = CGBitmapContextCreate(NULL, newWidth, newHeight, 8, 0, [self colorSpaceGetDeviceRGB], bitmapInfo);
|
|
||||||
}
|
}
|
||||||
|
CGContextRef context = CGBitmapContextCreate(NULL, newWidth, newHeight, 8, 0, [self colorSpaceGetDeviceRGB], bitmapInfo);
|
||||||
if (!context) {
|
if (!context) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
@ -299,9 +283,18 @@ static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to over
|
||||||
if (output_buffer.data) free(output_buffer.data);
|
if (output_buffer.data) free(output_buffer.data);
|
||||||
};
|
};
|
||||||
BOOL hasAlpha = [self CGImageContainsAlpha:cgImage];
|
BOOL hasAlpha = [self CGImageContainsAlpha:cgImage];
|
||||||
// iOS display alpha info (BGRA8888/BGRX8888)
|
// kCGImageAlphaNone is not supported in CGBitmapContextCreate.
|
||||||
CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Host;
|
// Check #3330 for more detail about why this bitmap is choosen.
|
||||||
bitmapInfo |= hasAlpha ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaNoneSkipFirst;
|
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;
|
||||||
|
}
|
||||||
vImage_CGImageFormat format = (vImage_CGImageFormat) {
|
vImage_CGImageFormat format = (vImage_CGImageFormat) {
|
||||||
.bitsPerComponent = 8,
|
.bitsPerComponent = 8,
|
||||||
.bitsPerPixel = 32,
|
.bitsPerPixel = 32,
|
||||||
|
@ -309,7 +302,7 @@ static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to over
|
||||||
.bitmapInfo = bitmapInfo,
|
.bitmapInfo = bitmapInfo,
|
||||||
.version = 0,
|
.version = 0,
|
||||||
.decode = NULL,
|
.decode = NULL,
|
||||||
.renderingIntent = kCGRenderingIntentDefault,
|
.renderingIntent = CGImageGetRenderingIntent(cgImage)
|
||||||
};
|
};
|
||||||
|
|
||||||
vImage_Error a_ret = vImageBuffer_InitWithCGImage(&input_buffer, &format, NULL, cgImage, kvImageNoFlags);
|
vImage_Error a_ret = vImageBuffer_InitWithCGImage(&input_buffer, &format, NULL, cgImage, kvImageNoFlags);
|
||||||
|
@ -337,16 +330,21 @@ static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to over
|
||||||
return image;
|
return image;
|
||||||
}
|
}
|
||||||
|
|
||||||
CGImageRef imageRef = [self CGImageCreateDecoded:image.CGImage];
|
CGImageRef imageRef = image.CGImage;
|
||||||
if (!imageRef) {
|
if (!imageRef) {
|
||||||
return image;
|
return image;
|
||||||
}
|
}
|
||||||
#if SD_MAC
|
BOOL hasAlpha = [self CGImageContainsAlpha:imageRef];
|
||||||
UIImage *decodedImage = [[UIImage alloc] initWithCGImage:imageRef scale:image.scale orientation:kCGImagePropertyOrientationUp];
|
// Prefer to use new Image Renderer to re-draw image, instead of low-level CGBitmapContext and CGContextDrawImage
|
||||||
#else
|
// This can keep both OS compatible and don't fight with Apple's performance optimization
|
||||||
UIImage *decodedImage = [[UIImage alloc] initWithCGImage:imageRef scale:image.scale orientation:image.imageOrientation];
|
SDGraphicsImageRendererFormat *format = [[SDGraphicsImageRendererFormat alloc] init];
|
||||||
#endif
|
format.opaque = !hasAlpha;
|
||||||
CGImageRelease(imageRef);
|
format.scale = image.scale;
|
||||||
|
CGSize imageSize = image.size;
|
||||||
|
SDGraphicsImageRenderer *renderer = [[SDGraphicsImageRenderer alloc] initWithSize:imageSize format:format];
|
||||||
|
UIImage *decodedImage = [renderer imageWithActions:^(CGContextRef _Nonnull context) {
|
||||||
|
[image drawInRect:CGRectMake(0, 0, imageSize.width, imageSize.height)];
|
||||||
|
}];
|
||||||
SDImageCopyAssociatedObject(image, decodedImage);
|
SDImageCopyAssociatedObject(image, decodedImage);
|
||||||
decodedImage.sd_isDecoded = YES;
|
decodedImage.sd_isDecoded = YES;
|
||||||
return decodedImage;
|
return decodedImage;
|
||||||
|
@ -392,33 +390,24 @@ static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to over
|
||||||
BOOL hasAlpha = [self CGImageContainsAlpha:sourceImageRef];
|
BOOL hasAlpha = [self CGImageContainsAlpha:sourceImageRef];
|
||||||
|
|
||||||
// kCGImageAlphaNone is not supported in CGBitmapContextCreate.
|
// kCGImageAlphaNone is not supported in CGBitmapContextCreate.
|
||||||
// Since the original image here has no alpha info, use kCGImageAlphaNoneSkipFirst
|
// Check #3330 for more detail about why this bitmap is choosen.
|
||||||
// to create bitmap graphics contexts without alpha info.
|
|
||||||
CGBitmapInfo bitmapInfo;
|
CGBitmapInfo bitmapInfo;
|
||||||
if (@available(iOS 15, tvOS 15, macOS 12, watchOS 8, *)) {
|
if (hasAlpha) {
|
||||||
// Update for iOS 15: CoreGraphics's draw image will fail to transcode some special CGImage on BGRX8888
|
// iPhone GPU prefer to use BGRA8888, see: https://forums.raywenderlich.com/t/why-mtlpixelformat-bgra8unorm/53489
|
||||||
// We prefer to use the input CGImage's bitmap firstly, then fallback to BGRAX8888. See #3330
|
// BGRA8888
|
||||||
bitmapInfo = CGImageGetBitmapInfo(sourceImageRef);
|
bitmapInfo = kCGBitmapByteOrder32Host | kCGImageAlphaPremultipliedFirst;
|
||||||
destContext = CGBitmapContextCreate(NULL,
|
} else {
|
||||||
destResolution.width,
|
// BGR888 previously works on iOS 8~iOS 14, however, iOS 15+ will result a black image. FB9958017
|
||||||
destResolution.height,
|
// RGB888
|
||||||
kBitsPerComponent,
|
bitmapInfo = kCGBitmapByteOrderDefault | kCGImageAlphaNoneSkipLast;
|
||||||
0,
|
|
||||||
colorspaceRef,
|
|
||||||
bitmapInfo);
|
|
||||||
}
|
|
||||||
if (!destContext) {
|
|
||||||
// iOS display alpha info (BGRA8888/BGRX8888)
|
|
||||||
bitmapInfo = kCGBitmapByteOrder32Host;
|
|
||||||
bitmapInfo |= hasAlpha ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaNoneSkipFirst;
|
|
||||||
destContext = CGBitmapContextCreate(NULL,
|
|
||||||
destResolution.width,
|
|
||||||
destResolution.height,
|
|
||||||
kBitsPerComponent,
|
|
||||||
0,
|
|
||||||
colorspaceRef,
|
|
||||||
bitmapInfo);
|
|
||||||
}
|
}
|
||||||
|
destContext = CGBitmapContextCreate(NULL,
|
||||||
|
destResolution.width,
|
||||||
|
destResolution.height,
|
||||||
|
kBitsPerComponent,
|
||||||
|
0,
|
||||||
|
colorspaceRef,
|
||||||
|
bitmapInfo);
|
||||||
|
|
||||||
if (destContext == NULL) {
|
if (destContext == NULL) {
|
||||||
return image;
|
return image;
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
|
|
||||||
#import "SDImageGraphics.h"
|
#import "SDImageGraphics.h"
|
||||||
#import "NSImage+Compatibility.h"
|
#import "NSImage+Compatibility.h"
|
||||||
|
#import "SDImageCoderHelper.h"
|
||||||
#import "objc/runtime.h"
|
#import "objc/runtime.h"
|
||||||
|
|
||||||
#if SD_MAC
|
#if SD_MAC
|
||||||
|
@ -22,11 +23,20 @@ static CGContextRef SDCGContextCreateBitmapContext(CGSize size, BOOL opaque, CGF
|
||||||
size_t height = ceil(size.height * scale);
|
size_t height = ceil(size.height * scale);
|
||||||
if (width < 1 || height < 1) return NULL;
|
if (width < 1 || height < 1) return NULL;
|
||||||
|
|
||||||
//pre-multiplied BGRA for non-opaque, BGRX for opaque, 8-bits per component, as Apple's doc
|
CGColorSpaceRef space = [SDImageCoderHelper colorSpaceGetDeviceRGB];
|
||||||
CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB();
|
// kCGImageAlphaNone is not supported in CGBitmapContextCreate.
|
||||||
CGImageAlphaInfo alphaInfo = kCGBitmapByteOrder32Host | (opaque ? kCGImageAlphaNoneSkipFirst : kCGImageAlphaPremultipliedFirst);
|
// Check #3330 for more detail about why this bitmap is choosen.
|
||||||
CGContextRef context = CGBitmapContextCreate(NULL, width, height, 8, 0, space, kCGBitmapByteOrderDefault | alphaInfo);
|
CGBitmapInfo bitmapInfo;
|
||||||
CGColorSpaceRelease(space);
|
if (!opaque) {
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
CGContextRef context = CGBitmapContextCreate(NULL, width, height, 8, 0, space, bitmapInfo);
|
||||||
if (!context) {
|
if (!context) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
|
@ -609,7 +609,7 @@ static inline CGImageRef _Nullable SDCreateCGImageFromCIImage(CIImage * _Nonnull
|
||||||
.bitmapInfo = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host, //requests a BGRA buffer.
|
.bitmapInfo = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host, //requests a BGRA buffer.
|
||||||
.version = 0,
|
.version = 0,
|
||||||
.decode = NULL,
|
.decode = NULL,
|
||||||
.renderingIntent = kCGRenderingIntentDefault
|
.renderingIntent = CGImageGetRenderingIntent(imageRef)
|
||||||
};
|
};
|
||||||
|
|
||||||
vImage_Error err;
|
vImage_Error err;
|
||||||
|
|
|
@ -284,7 +284,7 @@
|
||||||
- (void)test13ThatScaleDownLargeImageEXIFOrientationImage {
|
- (void)test13ThatScaleDownLargeImageEXIFOrientationImage {
|
||||||
XCTestExpectation *expectation = [self expectationWithDescription:@"SDWebImageScaleDownLargeImages works on EXIF orientation image"];
|
XCTestExpectation *expectation = [self expectationWithDescription:@"SDWebImageScaleDownLargeImages works on EXIF orientation image"];
|
||||||
NSURL *originalImageURL = [NSURL URLWithString:@"https://raw.githubusercontent.com/recurser/exif-orientation-examples/master/Landscape_2.jpg"];
|
NSURL *originalImageURL = [NSURL URLWithString:@"https://raw.githubusercontent.com/recurser/exif-orientation-examples/master/Landscape_2.jpg"];
|
||||||
[SDWebImageManager.sharedManager loadImageWithURL:originalImageURL options:SDWebImageScaleDownLargeImages progress:nil completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, SDImageCacheType cacheType, BOOL finished, NSURL * _Nullable imageURL) {
|
[SDWebImageManager.sharedManager loadImageWithURL:originalImageURL options:SDWebImageScaleDownLargeImages | SDWebImageAvoidDecodeImage progress:nil completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, SDImageCacheType cacheType, BOOL finished, NSURL * _Nullable imageURL) {
|
||||||
expect(image).notTo.beNil();
|
expect(image).notTo.beNil();
|
||||||
#if SD_UIKIT
|
#if SD_UIKIT
|
||||||
UIImageOrientation orientation = [SDImageCoderHelper imageOrientationFromEXIFOrientation:kCGImagePropertyOrientationUpMirrored];
|
UIImageOrientation orientation = [SDImageCoderHelper imageOrientationFromEXIFOrientation:kCGImagePropertyOrientationUpMirrored];
|
||||||
|
|
Loading…
Reference in New Issue