diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 2ed220cd..93a864c1 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -99,12 +99,24 @@ jobs: DEVELOPER_DIR: /Applications/Xcode_15.2.app WORKSPACE_NAME: SDWebImage.xcworkspace CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} - iosDestination: platform=iOS Simulator,name=iPhone 15 Pro - macOSDestination: platform=macOS,arch=x86_64 - macCatalystDestination: platform=macOS,arch=x86_64,variant=Mac Catalyst - tvOSDestination: platform=tvOS Simulator,name=Apple TV 4K (3rd generation) - watchOSDestination: platform=watchOS Simulator,name=Apple Watch Series 9 (45mm) - visionOSDestination: platform=visionOS Simulator,name=Apple Vision Pro + # use matrix to generate jobs for each platform + strategy: + fail-fast: false + matrix: + platform: [iOS, macOS, tvOS, visionOS] + include: + - platform: iOS + destination: platform=iOS Simulator,name=iPhone 15 Pro + scheme: iOS + - platform: macOS + destination: platform=macOS,arch=x86_64 + scheme: Mac + - platform: tvOS + destination: platform=tvOS Simulator,name=Apple TV 4K (3rd generation) + scheme: TV + - platform: visionOS + destination: platform=visionOS Simulator,name=Apple Vision Pro + scheme: Vision steps: - name: Checkout uses: actions/checkout@v3 @@ -126,39 +138,18 @@ jobs: rm -rf ~/Library/Developer/Xcode/DerivedData/ mkdir DerivedData - - name: Test for iOS + - name: Run test run: | set -o pipefail - xcodebuild test -workspace "${{ env.WORKSPACE_NAME }}" -scheme "Tests iOS" -destination "${{ env.iosDestination }}" -configuration Debug CODE_SIGNING_ALLOWED=NO - mv ~/Library/Developer/Xcode/DerivedData/ ./DerivedData/iOS - - - name: Test for macOS - run: | - set -o pipefail - xcodebuild test -workspace "${{ env.WORKSPACE_NAME }}" -scheme "Tests Mac" -destination "${{ env.macOSDestination }}" -configuration Debug CODE_SIGNING_ALLOWED=NO - mv ~/Library/Developer/Xcode/DerivedData/ ./DerivedData/macOS - - - name: Test for tvOS - run: | - set -o pipefail - xcodebuild test -workspace "${{ env.WORKSPACE_NAME }}" -scheme "Tests TV" -destination "${{ env.tvOSDestination }}" -configuration Debug CODE_SIGNING_ALLOWED=NO - mv ~/Library/Developer/Xcode/DerivedData/ ./DerivedData/tvOS - - - name: Test for visionOS - run: | - set -o pipefail - xcodebuild test -workspace "${{ env.WORKSPACE_NAME }}" -scheme "Tests Vision" -destination "${{ env.visionOSDestination }}" -configuration Debug CODE_SIGNING_ALLOWED=NO - mv ~/Library/Developer/Xcode/DerivedData/ ./DerivedData/visionOS + xcodebuild test -workspace "${{ env.WORKSPACE_NAME }}" -scheme "Tests ${{ matrix.scheme }}" -destination "${{ matrix.destination }}" -configuration Debug CODE_SIGNING_ALLOWED=NO + mv ~/Library/Developer/Xcode/DerivedData/ "./DerivedData/{{ matrix.platform }}" - name: Code Coverage run: | set -o pipefail export PATH="/usr/local/opt/curl/bin:$PATH" curl --version - bash <(curl -s https://codecov.io/bash) -v -D './DerivedData/macOS' -J '^SDWebImage$' -c -X gcov -F macos - bash <(curl -s https://codecov.io/bash) -v -D './DerivedData/iOS' -J '^SDWebImage$' -c -X gcov -F ios - bash <(curl -s https://codecov.io/bash) -v -D './DerivedData/tvOS' -J '^SDWebImage$' -c -X gcov -F tvos - bash <(curl -s https://codecov.io/bash) -v -D './DerivedData/visionOS' -J '^SDWebImage$' -c -X gcov -F visionos + bash <(curl -s https://codecov.io/bash) -v -D "./DerivedData/{{ matrix.platform }}" -J '^SDWebImage$' -c -X gcov -F "{{ matrix.platform }}" Build: name: Build Library diff --git a/SDWebImage/Core/SDImageCoderHelper.m b/SDWebImage/Core/SDImageCoderHelper.m index a2b81587..a2148116 100644 --- a/SDWebImage/Core/SDImageCoderHelper.m +++ b/SDWebImage/Core/SDImageCoderHelper.m @@ -316,14 +316,14 @@ static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to over cgImage = SDImageGetNonAlphaDummyImage().CGImage; } CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(cgImage); - size_t bitsPerPixel = 8; + size_t bitsPerComponent = 8; if (SD_OPTIONS_CONTAINS(bitmapInfo, kCGBitmapFloatComponents)) { - bitsPerPixel = 16; + bitsPerComponent = 16; } size_t components = 4; // Hardcode now // https://github.com/path/FastImageCache#byte-alignment // A properly aligned bytes-per-row value must be a multiple of 8 pixels × bytes per pixel. - size_t alignment = (bitsPerPixel / 8) * components * 8; + size_t alignment = (bitsPerComponent / 8) * components * 8; SDImagePixelFormat pixelFormat = { .bitmapInfo = bitmapInfo, .alignment = alignment diff --git a/SDWebImage/Core/SDImageIOAnimatedCoder.m b/SDWebImage/Core/SDImageIOAnimatedCoder.m index dca44f18..b529efbc 100644 --- a/SDWebImage/Core/SDImageIOAnimatedCoder.m +++ b/SDWebImage/Core/SDImageIOAnimatedCoder.m @@ -48,6 +48,10 @@ static CGImageRef __nullable SDCGImageCreateMutableCopy(CGImageRef cg_nullable i return newImage; } +static inline BOOL SDCGImageIs8Bit(CGImageRef cg_nullable image) { + return CGImageGetBitsPerComponent(image) == 8; +} + static inline CGImageRef __nullable SDCGImageCreateCopy(CGImageRef cg_nullable image) { if (!image) return nil; return SDCGImageCreateMutableCopy(image, CGImageGetBitmapInfo(image)); @@ -209,6 +213,7 @@ static BOOL SDImageIOPNGPluginBuggyNeedWorkaround(void) { // See: #3605 FB13322459 // ImageIO on iOS 17 (17.0~17.2), there is one serious problem on ImageIO PNG plugin. The decode result for indexed color PNG use the wrong CGImageAlphaInfo // The returned CGImageAlphaInfo is alpha last, but the actual bitmap data is premultiplied alpha last, which cause many runtime render bug. + // The bug only exists on 8-bits indexed color, not about 16-bits // So, we do a hack workaround: // 1. Decode a indexed color PNG in runtime // 2. If the bitmap is premultiplied alpha, then assume it's buggy @@ -527,7 +532,7 @@ static BOOL SDImageIOPNGPluginBuggyNeedWorkaround(void) { // :) CFStringRef uttype = CGImageSourceGetType(source); SDImageFormat imageFormat = [NSData sd_imageFormatFromUTType:uttype]; - if (imageFormat == SDImageFormatPNG && SDImageIOPNGPluginBuggyNeedWorkaround()) { + if (imageFormat == SDImageFormatPNG && SDCGImageIs8Bit(imageRef) && SDImageIOPNGPluginBuggyNeedWorkaround()) { CGImageRef newImageRef = SDImageIOPNGPluginBuggyCreateWorkaround(imageRef); CGImageRelease(imageRef); imageRef = newImageRef; diff --git a/Tests/SDWebImage Tests.xcodeproj/project.pbxproj b/Tests/SDWebImage Tests.xcodeproj/project.pbxproj index 2b2db1ed..a12699cb 100644 --- a/Tests/SDWebImage Tests.xcodeproj/project.pbxproj +++ b/Tests/SDWebImage Tests.xcodeproj/project.pbxproj @@ -141,6 +141,10 @@ 32B99EAC203B36650017FD66 /* SDWebImageDownloaderTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 1E3C51E819B46E370092B5E6 /* SDWebImageDownloaderTests.m */; }; 32B99EAD203B36690017FD66 /* SDWebImagePrefetcherTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 4369C1D01D97F80F007E863A /* SDWebImagePrefetcherTests.m */; }; 32B99EAE203B366C0017FD66 /* SDWebCacheCategoriesTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 4369C2731D9804B1007E863A /* SDWebCacheCategoriesTests.m */; }; + 32C268282B888A4100CA29AE /* RGBA16PNG.png in Resources */ = {isa = PBXBuildFile; fileRef = 32C268272B888A4100CA29AE /* RGBA16PNG.png */; }; + 32C268292B888A4100CA29AE /* RGBA16PNG.png in Resources */ = {isa = PBXBuildFile; fileRef = 32C268272B888A4100CA29AE /* RGBA16PNG.png */; }; + 32C2682A2B888A4100CA29AE /* RGBA16PNG.png in Resources */ = {isa = PBXBuildFile; fileRef = 32C268272B888A4100CA29AE /* RGBA16PNG.png */; }; + 32C2682B2B888A4100CA29AE /* RGBA16PNG.png in Resources */ = {isa = PBXBuildFile; fileRef = 32C268272B888A4100CA29AE /* RGBA16PNG.png */; }; 32E6F0321F3A1B4700A945E6 /* SDWebImageTestCoder.m in Sources */ = {isa = PBXBuildFile; fileRef = 32E6F0311F3A1B4700A945E6 /* SDWebImageTestCoder.m */; }; 32F788A3290D252200B57A1C /* TestImage.nef in Resources */ = {isa = PBXBuildFile; fileRef = 32F788A2290D252200B57A1C /* TestImage.nef */; }; 32F788A4290D252200B57A1C /* TestImage.nef in Resources */ = {isa = PBXBuildFile; fileRef = 32F788A2290D252200B57A1C /* TestImage.nef */; }; @@ -207,6 +211,7 @@ 32B99E8A203AF8690017FD66 /* SDCategoriesTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDCategoriesTests.m; sourceTree = ""; }; 32B99E92203B2DF90017FD66 /* Tests Mac.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Tests Mac.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 32B99E96203B2DF90017FD66 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 32C268272B888A4100CA29AE /* RGBA16PNG.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = RGBA16PNG.png; sourceTree = ""; }; 32E6F0301F3A1B4700A945E6 /* SDWebImageTestCoder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDWebImageTestCoder.h; sourceTree = ""; }; 32E6F0311F3A1B4700A945E6 /* SDWebImageTestCoder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDWebImageTestCoder.m; sourceTree = ""; }; 32F788A2290D252200B57A1C /* TestImage.nef */ = {isa = PBXFileReference; lastKnownFileType = file; path = TestImage.nef; sourceTree = ""; }; @@ -331,6 +336,7 @@ children = ( 32648066250232F7004FA0FC /* 1@2x.gif */, 3278F5E12B04C1AC0004A6EE /* IndexedPNG.png */, + 32C268272B888A4100CA29AE /* RGBA16PNG.png */, 433BBBBA1D7EFA8B0086B6E9 /* MonochromeTestImage.jpg */, 324047432271956F007C53E1 /* TestEXIF.png */, 3264CD162AAB1E23001E338B /* TestJFIF.jpg */, @@ -580,6 +586,7 @@ 32464AA22B7B1833006BE70E /* TestImageLarge.jpg in Resources */, 32464A912B7B1833006BE70E /* TestImage.nef in Resources */, 32464A942B7B1833006BE70E /* TestJFIF.jpg in Resources */, + 32C2682B2B888A4100CA29AE /* RGBA16PNG.png in Resources */, 32464A9B2B7B1833006BE70E /* TestAnimatedImageMemory.webp in Resources */, 32464A992B7B1833006BE70E /* TestEXIF.png in Resources */, 32464A922B7B1833006BE70E /* TestImage.heic in Resources */, @@ -608,6 +615,7 @@ 6BC1C210270F073A003FFAB1 /* TestAnimatedImageMemory.webp in Resources */, 3264CD192AAB1E23001E338B /* TestJFIF.jpg in Resources */, 3299228D2365DC6C00EAFD97 /* TestImageAnimated.apng in Resources */, + 32C2682A2B888A4100CA29AE /* RGBA16PNG.png in Resources */, 3299228B2365DC6C00EAFD97 /* TestImage.heic in Resources */, 329922872365DC6C00EAFD97 /* TestLoopCount.gif in Resources */, 3299228C2365DC6C00EAFD97 /* TestImage.heif in Resources */, @@ -636,6 +644,7 @@ 6BC1C20F270F0193003FFAB1 /* TestAnimatedImageMemory.webp in Resources */, 3264CD182AAB1E23001E338B /* TestJFIF.jpg in Resources */, 327054E3206CEFF3006EA328 /* TestImageAnimated.apng in Resources */, + 32C268292B888A4100CA29AE /* RGBA16PNG.png in Resources */, 32B99EA3203B31360017FD66 /* TestImage.gif in Resources */, 324047452271956F007C53E1 /* TestEXIF.png in Resources */, 32B99EA4203B31360017FD66 /* TestImage.jpg in Resources */, @@ -664,6 +673,7 @@ 327A418C211D660600495442 /* TestImage.heic in Resources */, 3264CD172AAB1E23001E338B /* TestJFIF.jpg in Resources */, 6B181A1B265757ED00BD06B3 /* TestAnimatedImageMemory.webp in Resources */, + 32C268282B888A4100CA29AE /* RGBA16PNG.png in Resources */, 5F7F38AD1AE2A77A00B0E330 /* TestImage.jpg in Resources */, 32905E64211D786E00460FCF /* TestImage.heif in Resources */, 43828A451DA67F9900000E62 /* TestImageLarge.jpg in Resources */, diff --git a/Tests/Tests/Images/RGBA16PNG.png b/Tests/Tests/Images/RGBA16PNG.png new file mode 100644 index 00000000..1fde4ea1 Binary files /dev/null and b/Tests/Tests/Images/RGBA16PNG.png differ diff --git a/Tests/Tests/SDAnimatedImageTest.m b/Tests/Tests/SDAnimatedImageTest.m index 37c6c431..3917a0c6 100644 --- a/Tests/Tests/SDAnimatedImageTest.m +++ b/Tests/Tests/SDAnimatedImageTest.m @@ -764,6 +764,7 @@ static BOOL _isCalled; [self waitForExpectationsWithTimeout:15 handler:nil]; } +#if !SD_TV - (void)test36AnimatedImageMemoryCost { if (@available(iOS 14, tvOS 14, macOS 11, watchOS 7, *)) { [[SDImageCodersManager sharedManager] addCoder:[SDImageAWebPCoder sharedCoder]]; @@ -783,6 +784,7 @@ static BOOL _isCalled; [[SDImageCodersManager sharedManager] removeCoder:[SDImageAWebPCoder sharedCoder]]; } } +#endif #pragma mark - Helper diff --git a/Tests/Tests/SDImageCoderTests.m b/Tests/Tests/SDImageCoderTests.m index 4342caf8..75b6a992 100644 --- a/Tests/Tests/SDImageCoderTests.m +++ b/Tests/Tests/SDImageCoderTests.m @@ -9,9 +9,6 @@ #import "SDTestCase.h" #import "UIColor+SDHexString.h" -#if __has_include() -#import -#endif @interface SDWebImageDecoderTests : SDTestCase @@ -269,15 +266,11 @@ isVectorImage:YES]; } +#if !SD_TV - (void)test18ThatStaticWebPWorks { if (@available(iOS 14, tvOS 14, macOS 11, *)) { NSURL *staticWebPURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"TestImageStatic" withExtension:@"webp"]; -#if SD_TV - /// TV OS does not support ImageIO's webp. - [self verifyCoder:[SDImageWebPCoder sharedCoder] -#else [self verifyCoder:[SDImageAWebPCoder sharedCoder] -#endif withLocalImageURL:staticWebPURL supportsEncoding:NO // Currently (iOS 14.0) seems no encoding support encodingFormat:SDImageFormatWebP @@ -285,16 +278,13 @@ isVectorImage:NO]; } } +#endif +#if !SD_TV - (void)test19ThatAnimatedWebPWorks { if (@available(iOS 14, tvOS 14, macOS 11, *)) { NSURL *staticWebPURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"TestImageAnimated" withExtension:@"webp"]; -#if SD_TV - /// TV OS does not support ImageIO's webp. - [self verifyCoder:[SDImageWebPCoder sharedCoder] -#else [self verifyCoder:[SDImageAWebPCoder sharedCoder] -#endif withLocalImageURL:staticWebPURL supportsEncoding:NO // Currently (iOS 14.0) seems no encoding support encodingFormat:SDImageFormatWebP @@ -302,6 +292,7 @@ isVectorImage:NO]; } } +#endif - (void)test20ThatImageIOAnimatedCoderAbstractClass { SDImageIOAnimatedCoder *coder = [[SDImageIOAnimatedCoder alloc] init]; @@ -573,6 +564,17 @@ expect(g1).beCloseToWithin(0.91, 0.01); expect(b1).beCloseToWithin(0.91, 0.01); expect(a1).beCloseToWithin(0.20, 0.01); + + // RGBA 16 bits PNG should not workaround + url = [[NSBundle bundleForClass:[self class]] URLForResource:@"RGBA16PNG" withExtension:@"png"]; + data = [NSData dataWithContentsOfURL:url]; + decodedImage = [SDImageIOCoder.sharedCoder decodedImageWithData:data options:nil]; + testColor1 = [decodedImage sd_colorAtPoint:CGPointMake(100, 1)]; + [testColor1 getRed:&r1 green:&g1 blue:&b1 alpha:&a1]; + expect(r1).beCloseToWithin(0.60, 0.01); + expect(g1).beCloseToWithin(0.60, 0.01); + expect(b1).beCloseToWithin(0.33, 0.01); + expect(a1).beCloseToWithin(0.33, 0.01); } #pragma mark - Utils diff --git a/Tests/Tests/SDTestCase.m b/Tests/Tests/SDTestCase.m index 64ebd2fb..06a085ec 100644 --- a/Tests/Tests/SDTestCase.m +++ b/Tests/Tests/SDTestCase.m @@ -39,7 +39,9 @@ NSString *const kTestAPNGPURL = @"https://upload.wikimedia.org/wikipedia/commons CGRect screenFrame = mainScreen.bounds; #endif _window = [[UIWindow alloc] initWithFrame:screenFrame]; -#else +#endif // UIKit +#if SD_MAC + UIScreen *mainScreen = [UIScreen mainScreen]; _window = [[NSWindow alloc] initWithContentRect:mainScreen.frame styleMask:0 backing:NSBackingStoreBuffered defer:NO screen:mainScreen]; #endif } diff --git a/Tests/Tests/SDWebImageManagerTests.m b/Tests/Tests/SDWebImageManagerTests.m index 205a4167..ab808626 100644 --- a/Tests/Tests/SDWebImageManagerTests.m +++ b/Tests/Tests/SDWebImageManagerTests.m @@ -10,9 +10,6 @@ #import "SDWebImageTestTransformer.h" #import "SDWebImageTestCache.h" #import "SDWebImageTestLoader.h" -#if __has_include() -#import -#endif // Keep strong references for object @interface SDObjectContainer : NSObject @@ -646,23 +643,6 @@ [self waitForExpectationsWithCommonTimeout]; } -#if __has_include() -- (void)test22ThatForceDecodePolicyAlways { - XCTestExpectation *expectation = [self expectationWithDescription:@"Always policy with WebP image (libwebp) should force-decode"]; - NSURL *url = [NSURL URLWithString:@"https://www.gstatic.com/webp/gallery/4.webp"]; - [SDWebImageManager.sharedManager loadImageWithURL:url options:SDWebImageFromLoaderOnly context:@{SDWebImageContextImageCoder : SDImageWebPCoder.sharedCoder, SDWebImageContextImageForceDecodePolicy : @(SDImageForceDecodePolicyAlways)} 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.sd_isDecoded).beTruthy(); - CGImageRef cgImage = image.CGImage; - CGColorSpaceRef colorspace = CGImageGetColorSpace(cgImage); - expect(colorspace).equal([SDImageCoderHelper colorSpaceGetDeviceRGB]); - - [expectation fulfill]; - }]; - [self waitForExpectationsWithCommonTimeout]; -} -#endif - - (NSString *)testJPEGPath { NSBundle *testBundle = [NSBundle bundleForClass:[self class]]; return [testBundle pathForResource:@"TestImage" ofType:@"jpg"];