Merge pull request #3332 from dreampiggy/workaround_prefer_input_cgimage_bitmap_info

Workaround the iOS 15+ Force Decode may return black image because of CoreGraphics
This commit is contained in:
DreamPiggy 2022-03-08 15:17:04 +08:00 committed by GitHub
commit ba66c41fd6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 81 additions and 24 deletions

View File

@ -13,7 +13,7 @@ jobs:
name: Cocoapods Lint
runs-on: macos-11
env:
DEVELOPER_DIR: /Applications/Xcode_13.0.app
DEVELOPER_DIR: /Applications/Xcode_13.2.1.app
steps:
- name: Checkout
uses: actions/checkout@v2
@ -39,7 +39,7 @@ jobs:
name: Run Demo
runs-on: macos-11
env:
DEVELOPER_DIR: /Applications/Xcode_13.0.app
DEVELOPER_DIR: /Applications/Xcode_13.2.1.app
WORKSPACE_NAME: SDWebImage.xcworkspace
OSXSCHEME: SDWebImage OSX Demo
iOSSCHEME: SDWebImage iOS Demo
@ -92,7 +92,7 @@ jobs:
name: Unit Test
runs-on: macos-11
env:
DEVELOPER_DIR: /Applications/Xcode_13.0.app
DEVELOPER_DIR: /Applications/Xcode_13.2.1.app
WORKSPACE_NAME: SDWebImage.xcworkspace
strategy:
matrix:
@ -151,7 +151,7 @@ jobs:
name: Build Library
runs-on: macos-11
env:
DEVELOPER_DIR: /Applications/Xcode_13.0.app
DEVELOPER_DIR: /Applications/Xcode_13.2.1.app
PROJECT_NAME: SDWebImage.xcodeproj
SCHEME_NAME: SDWebImage
steps:

View File

@ -255,9 +255,19 @@ static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to over
// 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);
CGBitmapInfo bitmapInfo;
CGContextRef context = NULL;
if (@available(iOS 15, tvOS 15, macOS 12, watchOS 8, *)) {
// Update for iOS 15: CoreGraphics's draw image will fail to transcode and draw some special CGImage on BGRX8888
// We prefer to use the input CGImage's bitmap firstly, then fallback to BGRAX8888. See #3330
bitmapInfo = CGImageGetBitmapInfo(cgImage);
context = CGBitmapContextCreate(NULL, newWidth, newHeight, 8, 0, [self colorSpaceGetDeviceRGB], bitmapInfo);
}
if (!context) {
bitmapInfo = kCGBitmapByteOrder32Host;
bitmapInfo |= hasAlpha ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaNoneSkipFirst;
context = CGBitmapContextCreate(NULL, newWidth, newHeight, 8, 0, [self colorSpaceGetDeviceRGB], bitmapInfo);
}
if (!context) {
return NULL;
}
@ -358,7 +368,7 @@ static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to over
}
destTotalPixels = bytes / kBytesPerPixel;
tileTotalPixels = destTotalPixels / 3;
CGContextRef destContext;
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];
@ -380,20 +390,35 @@ static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to over
// device color space
CGColorSpaceRef colorspaceRef = [self colorSpaceGetDeviceRGB];
BOOL hasAlpha = [self CGImageContainsAlpha:sourceImageRef];
// iOS display alpha info (BGRA8888/BGRX8888)
CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Host;
bitmapInfo |= hasAlpha ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaNoneSkipFirst;
// kCGImageAlphaNone is not supported in CGBitmapContextCreate.
// Since the original image here has no alpha info, use kCGImageAlphaNoneSkipFirst
// to create bitmap graphics contexts without alpha info.
destContext = CGBitmapContextCreate(NULL,
destResolution.width,
destResolution.height,
kBitsPerComponent,
0,
colorspaceRef,
bitmapInfo);
CGBitmapInfo bitmapInfo;
if (@available(iOS 15, tvOS 15, macOS 12, watchOS 8, *)) {
// Update for iOS 15: CoreGraphics's draw image will fail to transcode some special CGImage on BGRX8888
// We prefer to use the input CGImage's bitmap firstly, then fallback to BGRAX8888. See #3330
bitmapInfo = CGImageGetBitmapInfo(sourceImageRef);
destContext = CGBitmapContextCreate(NULL,
destResolution.width,
destResolution.height,
kBitsPerComponent,
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);
}
if (destContext == NULL) {
return image;

View File

@ -13,6 +13,9 @@
320224F82440C39B00E5B29D /* TestImageLarge.png in Resources */ = {isa = PBXBuildFile; fileRef = 320224F62440C39B00E5B29D /* TestImageLarge.png */; };
320224F92440C39B00E5B29D /* TestImageLarge.png in Resources */ = {isa = PBXBuildFile; fileRef = 320224F62440C39B00E5B29D /* TestImageLarge.png */; };
320630412085A37C006E0FA4 /* SDAnimatedImageTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 32A571552037DB2D002EDAAE /* SDAnimatedImageTest.m */; };
321F310E27D0DC490042B274 /* TestImage.bmp in Resources */ = {isa = PBXBuildFile; fileRef = 321F310D27D0DC490042B274 /* TestImage.bmp */; };
321F310F27D0DC490042B274 /* TestImage.bmp in Resources */ = {isa = PBXBuildFile; fileRef = 321F310D27D0DC490042B274 /* TestImage.bmp */; };
321F311027D0DC490042B274 /* TestImage.bmp in Resources */ = {isa = PBXBuildFile; fileRef = 321F310D27D0DC490042B274 /* TestImage.bmp */; };
3222417F2272F808002429DB /* SDUtilsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3222417E2272F808002429DB /* SDUtilsTests.m */; };
322241802272F808002429DB /* SDUtilsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3222417E2272F808002429DB /* SDUtilsTests.m */; };
3226ECBB20754F7700FAFACF /* SDWebImageTestDownloadOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 3226ECBA20754F7700FAFACF /* SDWebImageTestDownloadOperation.m */; };
@ -123,6 +126,7 @@
2D7AF05E1F329763000083C2 /* SDTestCase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDTestCase.h; sourceTree = "<group>"; };
2D7AF05F1F329763000083C2 /* SDTestCase.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDTestCase.m; sourceTree = "<group>"; };
320224F62440C39B00E5B29D /* TestImageLarge.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = TestImageLarge.png; sourceTree = "<group>"; };
321F310D27D0DC490042B274 /* TestImage.bmp */ = {isa = PBXFileReference; lastKnownFileType = image.bmp; path = TestImage.bmp; sourceTree = "<group>"; };
3222417E2272F808002429DB /* SDUtilsTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDUtilsTests.m; sourceTree = "<group>"; };
3226ECB920754F7700FAFACF /* SDWebImageTestDownloadOperation.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDWebImageTestDownloadOperation.h; sourceTree = "<group>"; };
3226ECBA20754F7700FAFACF /* SDWebImageTestDownloadOperation.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDWebImageTestDownloadOperation.m; sourceTree = "<group>"; };
@ -256,6 +260,7 @@
32648066250232F7004FA0FC /* 1@2x.gif */,
433BBBBA1D7EFA8B0086B6E9 /* MonochromeTestImage.jpg */,
324047432271956F007C53E1 /* TestEXIF.png */,
321F310D27D0DC490042B274 /* TestImage.bmp */,
433BBBB61D7EF8200086B6E9 /* TestImage.gif */,
326E69462334C0C200B7252C /* TestLoopCount.gif */,
5F7F38AC1AE2A77A00B0E330 /* TestImage.jpg */,
@ -476,6 +481,7 @@
3299228C2365DC6C00EAFD97 /* TestImage.heif in Resources */,
3234306423E2BAC800C290C8 /* TestImage.pdf in Resources */,
320224F92440C39B00E5B29D /* TestImageLarge.png in Resources */,
321F311027D0DC490042B274 /* TestImage.bmp in Resources */,
329922892365DC6C00EAFD97 /* TestImageLarge.jpg in Resources */,
32648069250232F7004FA0FC /* 1@2x.gif in Resources */,
3299228A2365DC6C00EAFD97 /* TestImage.png in Resources */,
@ -500,6 +506,7 @@
32B99EA4203B31360017FD66 /* TestImage.jpg in Resources */,
3234306323E2BAC800C290C8 /* TestImage.pdf in Resources */,
320224F82440C39B00E5B29D /* TestImageLarge.png in Resources */,
321F310F27D0DC490042B274 /* TestImage.bmp in Resources */,
32B99EA6203B31360017FD66 /* TestImage.png in Resources */,
32648068250232F7004FA0FC /* 1@2x.gif in Resources */,
3297A0A023374D1700814590 /* TestImageAnimated.heic in Resources */,
@ -524,6 +531,7 @@
43828A451DA67F9900000E62 /* TestImageLarge.jpg in Resources */,
3234306223E2BAC800C290C8 /* TestImage.pdf in Resources */,
320224F72440C39B00E5B29D /* TestImageLarge.png in Resources */,
321F310E27D0DC490042B274 /* TestImage.bmp in Resources */,
433BBBB71D7EF8200086B6E9 /* TestImage.gif in Resources */,
32648067250232F7004FA0FC /* 1@2x.gif in Resources */,
433BBBB91D7EF8260086B6E9 /* TestImage.png in Resources */,

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 MiB

View File

@ -154,10 +154,10 @@
expect(CGSizeEqualToSize(tintedImage.size, testImage.size)).beTruthy();
// Check center color, should keep clear
UIColor *centerColor = [tintedImage sd_colorAtPoint:CGPointMake(150, 150)];
expect([centerColor.sd_hexString isEqualToString:[UIColor clearColor].sd_hexString]);
expect([centerColor.sd_hexString isEqualToString:[UIColor clearColor].sd_hexString]).beTruthy();
// Check left color, should be tinted
UIColor *leftColor = [tintedImage sd_colorAtPoint:CGPointMake(80, 150)];
expect([leftColor.sd_hexString isEqualToString:tintColor.sd_hexString]);
expect([leftColor.sd_hexString isEqualToString:tintColor.sd_hexString]).beTruthy();
// Check rounded corner operation not inversion the image
UIColor *topCenterColor = [tintedImage sd_colorAtPoint:CGPointMake(150, 20)];
expect([topCenterColor.sd_hexString isEqualToString:[UIColor blackColor].sd_hexString]).beTruthy();
@ -177,9 +177,17 @@
expect(CGSizeEqualToSize(blurredImage.size, testImage.size)).beTruthy();
// Check left color, should be blurred
UIColor *leftColor = [blurredImage sd_colorAtPoint:CGPointMake(80, 150)];
// Hard-code from the output
UIColor *expectedColor = [UIColor colorWithRed:0.431373 green:0.101961 blue:0.0901961 alpha:0.729412];
expect([leftColor.sd_hexString isEqualToString:expectedColor.sd_hexString]);
// Hard-code from the output, allows a little deviation because of blur diffs between OS versions :)
// rgba(114, 27, 23, 0.75)
UIColor *expectedColor = [UIColor colorWithRed:114.0/255.0 green:27.0/255.0 blue:23.0/255.0 alpha:0.75];
CGFloat r1, g1, b1, a1;
CGFloat r2, g2, b2, a2;
[leftColor getRed:&r1 green:&g1 blue:&b1 alpha:&a1];
[expectedColor getRed:&r2 green:&g2 blue:&b2 alpha:&a2];
expect(r1).beCloseToWithin(r2, 2.0/255.0);
expect(g1).beCloseToWithin(g2, 2.0/255.0);
expect(b1).beCloseToWithin(b2, 2.0/255.0);
expect(a1).beCloseToWithin(a2, 2.0/255.0);
// Check rounded corner operation not inversion the image
UIColor *topCenterColor = [blurredImage sd_colorAtPoint:CGPointMake(150, 20)];
UIColor *bottomCenterColor = [blurredImage sd_colorAtPoint:CGPointMake(150, 280)];
@ -203,7 +211,7 @@
UIColor *leftColor = [filteredImage sd_colorAtPoint:CGPointMake(80, 150)];
// Hard-code from the output
UIColor *expectedColor = [UIColor colorWithRed:0.85098 green:0.992157 blue:0.992157 alpha:1];
expect([leftColor.sd_hexString isEqualToString:expectedColor.sd_hexString]);
expect([leftColor.sd_hexString isEqualToString:expectedColor.sd_hexString]).beTruthy();
// Check rounded corner operation not inversion the image
UIColor *topCenterColor = [filteredImage sd_colorAtPoint:CGPointMake(150, 20)];
expect([topCenterColor.sd_hexString isEqualToString:[UIColor whiteColor].sd_hexString]).beTruthy();
@ -398,6 +406,17 @@
CGImageRelease(leftCGImage);
}
- (void)test21BMPImageCreateDecodedShouldNotBlank {
UIImage *testImage = [[UIImage alloc] initWithContentsOfFile:[self testBMPPathForName:@"TestImage"]];
CGImageRef cgImage = testImage.CGImage;
expect(cgImage).notTo.beNil();
UIImage *decodedImage = [SDImageCoderHelper decodedImageWithImage:testImage];
expect(decodedImage).notTo.beNil();
UIColor *testColor = [decodedImage sd_colorAtPoint:CGPointMake(100, 100)];
// Should not be black color
expect([[testColor sd_hexString] isEqualToString:UIColor.blackColor.sd_hexString]).beFalsy();
}
#pragma mark - Helper
- (UIImage *)testImageCG {
@ -424,4 +443,9 @@
return [testBundle pathForResource:name ofType:@"png"];
}
- (NSString *)testBMPPathForName:(NSString *)name {
NSBundle *testBundle = [NSBundle bundleForClass:[self class]];
return [testBundle pathForResource:name ofType:@"bmp"];
}
@end