diff --git a/SDWebImageWebPCoder/Classes/SDImageWebPCoder.m b/SDWebImageWebPCoder/Classes/SDImageWebPCoder.m index 5675170..9aec9ff 100644 --- a/SDWebImageWebPCoder/Classes/SDImageWebPCoder.m +++ b/SDWebImageWebPCoder/Classes/SDImageWebPCoder.m @@ -736,6 +736,9 @@ static CGSize SDCalculateThumbnailSize(CGSize fullSize, BOOL preserveAspectRatio } size_t bytesPerRow = CGImageGetBytesPerRow(imageRef); + size_t bitsPerComponent = CGImageGetBitsPerComponent(imageRef); + size_t bitsPerPixel = CGImageGetBitsPerPixel(imageRef); + size_t components = bitsPerPixel / bitsPerComponent; CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(imageRef); CGImageAlphaInfo alphaInfo = bitmapInfo & kCGBitmapAlphaInfoMask; CGBitmapInfo byteOrderInfo = bitmapInfo & kCGBitmapByteOrderMask; @@ -763,10 +766,15 @@ static CGSize SDCalculateThumbnailSize(CGSize fullSize, BOOL preserveAspectRatio if (!dataRef) { return nil; } + // Check colorSpace is RGB/RGBA + CGColorSpaceRef colorSpace = CGImageGetColorSpace(imageRef); + BOOL isRGB = CGColorSpaceGetModel(colorSpace) == kCGColorSpaceModelRGB; uint8_t *rgba = NULL; // RGBA Buffer managed by CFData, don't call `free` on it, instead call `CFRelease` on `dataRef` // We could not assume that input CGImage's color mode is always RGB888/RGBA8888. Convert all other cases to target color mode using vImage - if (byteOrderNormal && ((alphaInfo == kCGImageAlphaNone) || (alphaInfo == kCGImageAlphaLast))) { + BOOL isRGB888 = isRGB && byteOrderNormal && alphaInfo == kCGImageAlphaNone && components == 3; + BOOL isRGBA8888 = isRGB && byteOrderNormal && alphaInfo == kCGImageAlphaLast && components == 4; + if (isRGB888 || isRGBA8888) { // If the input CGImage is already RGB888/RGBA8888 rgba = (uint8_t *)CFDataGetBytePtr(dataRef); } else { @@ -775,10 +783,11 @@ static CGSize SDCalculateThumbnailSize(CGSize fullSize, BOOL preserveAspectRatio vImage_Error error = kvImageNoError; vImage_CGImageFormat srcFormat = { - .bitsPerComponent = (uint32_t)CGImageGetBitsPerComponent(imageRef), - .bitsPerPixel = (uint32_t)CGImageGetBitsPerPixel(imageRef), - .colorSpace = CGImageGetColorSpace(imageRef), - .bitmapInfo = bitmapInfo + .bitsPerComponent = (uint32_t)bitsPerComponent, + .bitsPerPixel = (uint32_t)bitsPerPixel, + .colorSpace = colorSpace, + .bitmapInfo = bitmapInfo, + .renderingIntent = CGImageGetRenderingIntent(imageRef) }; vImage_CGImageFormat destFormat = { .bitsPerComponent = 8, @@ -793,14 +802,15 @@ static CGSize SDCalculateThumbnailSize(CGSize fullSize, BOOL preserveAspectRatio return nil; } - vImage_Buffer src = { - .data = (uint8_t *)CFDataGetBytePtr(dataRef), - .width = width, - .height = height, - .rowBytes = bytesPerRow - }; - vImage_Buffer dest; + vImage_Buffer src; + error = vImageBuffer_InitWithCGImage(&src, &srcFormat, nil, imageRef, kvImageNoFlags); + if (error != kvImageNoError) { + vImageConverter_Release(convertor); + CFRelease(dataRef); + return nil; + } + vImage_Buffer dest; error = vImageBuffer_Init(&dest, height, width, destFormat.bitsPerPixel, kvImageNoFlags); if (error != kvImageNoError) { vImageConverter_Release(convertor); diff --git a/Tests/Images/TestImageGrayscale.jpg b/Tests/Images/TestImageGrayscale.jpg new file mode 100644 index 0000000..9f630ea Binary files /dev/null and b/Tests/Images/TestImageGrayscale.jpg differ diff --git a/Tests/SDWebImageWebPCoderTests.m b/Tests/SDWebImageWebPCoderTests.m index e47b5a5..ef1de63 100644 --- a/Tests/SDWebImageWebPCoderTests.m +++ b/Tests/SDWebImageWebPCoderTests.m @@ -292,6 +292,35 @@ const int64_t kAsyncTestTimeout = 5; expect(config.method).to.equal(4); } + +- (void)testEncodingGrayscaleImage { + NSURL *grayscaleImageURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"TestImageGrayscale" withExtension:@"jpg"]; + NSData *grayscaleImageData = [NSData dataWithContentsOfURL:grayscaleImageURL]; + UIImage *grayscaleImage = [[UIImage alloc] initWithData:grayscaleImageData]; + expect(grayscaleImage).notTo.beNil(); + + NSData *webpData = [SDImageWebPCoder.sharedCoder encodedDataWithImage:grayscaleImage format:SDImageFormatWebP options:nil]; + expect(webpData).notTo.beNil(); + + UIImage *decodedImage = [UIImage sd_imageWithData:webpData]; + expect(decodedImage).notTo.beNil(); + + // Sample to verify that encoded WebP image's color is correct. + // The wrong case before bugfix is that each column color will repeats 3 times. + CGPoint point1 = CGPointMake(271, 764); + CGPoint point2 = CGPointMake(round(point1.x + decodedImage.size.width / 3), point1.y); + UIColor *color1 = [decodedImage sd_colorAtPoint:point1]; + UIColor *color2 = [decodedImage sd_colorAtPoint:point2]; + CGFloat r1, r2; + CGFloat g1, g2; + CGFloat b1, b2; + [color1 getRed:&r1 green:&g1 blue:&b1 alpha:nil]; + [color2 getRed:&r2 green:&g2 blue:&b2 alpha:nil]; + expect(255 * r1).notTo.equal(255 * r2); + expect(255 * g1).notTo.equal(255 * g2); + expect(255 * b1).notTo.equal(255 * b2); +} + @end @implementation SDWebImageWebPCoderTests (Helpers) diff --git a/Tests/SDWebImageWebPCoderTests.xcodeproj/project.pbxproj b/Tests/SDWebImageWebPCoderTests.xcodeproj/project.pbxproj index 976c098..2b15956 100644 --- a/Tests/SDWebImageWebPCoderTests.xcodeproj/project.pbxproj +++ b/Tests/SDWebImageWebPCoderTests.xcodeproj/project.pbxproj @@ -9,6 +9,7 @@ /* Begin PBXBuildFile section */ 0EF5B6264833B7BC20894578 /* Pods_SDWebImageWebPCoderTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 46F21AD7D1692EBAC4D0FF33 /* Pods_SDWebImageWebPCoderTests.framework */; }; 3219F3B2228B0453003822A6 /* TestImageBlendAnimated.webp in Resources */ = {isa = PBXBuildFile; fileRef = 3219F3B1228B0453003822A6 /* TestImageBlendAnimated.webp */; }; + 325E268E25C82BE1000B807B /* TestImageGrayscale.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 325E268D25C82BE1000B807B /* TestImageGrayscale.jpg */; }; 808C918E213FD131004B0F7C /* SDWebImageWebPCoderTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 808C918D213FD131004B0F7C /* SDWebImageWebPCoderTests.m */; }; 808C919C213FD2B2004B0F7C /* TestImageStatic.webp in Resources */ = {isa = PBXBuildFile; fileRef = 808C919A213FD2B2004B0F7C /* TestImageStatic.webp */; }; 808C919D213FD2B2004B0F7C /* TestImageAnimated.webp in Resources */ = {isa = PBXBuildFile; fileRef = 808C919B213FD2B2004B0F7C /* TestImageAnimated.webp */; }; @@ -17,6 +18,7 @@ /* Begin PBXFileReference section */ 28D8AA3D3015E075692FD3E3 /* Pods-SDWebImageWebPCoderTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SDWebImageWebPCoderTests.debug.xcconfig"; path = "../Pods/Target Support Files/Pods-SDWebImageWebPCoderTests/Pods-SDWebImageWebPCoderTests.debug.xcconfig"; sourceTree = ""; }; 3219F3B1228B0453003822A6 /* TestImageBlendAnimated.webp */ = {isa = PBXFileReference; lastKnownFileType = file; path = TestImageBlendAnimated.webp; sourceTree = ""; }; + 325E268D25C82BE1000B807B /* TestImageGrayscale.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = TestImageGrayscale.jpg; sourceTree = ""; }; 46F21AD7D1692EBAC4D0FF33 /* Pods_SDWebImageWebPCoderTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SDWebImageWebPCoderTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 808C918B213FD130004B0F7C /* SDWebImageWebPCoderTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SDWebImageWebPCoderTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 808C918D213FD131004B0F7C /* SDWebImageWebPCoderTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDWebImageWebPCoderTests.m; sourceTree = ""; }; @@ -78,6 +80,7 @@ 808C9199213FD2B2004B0F7C /* Images */ = { isa = PBXGroup; children = ( + 325E268D25C82BE1000B807B /* TestImageGrayscale.jpg */, 808C919A213FD2B2004B0F7C /* TestImageStatic.webp */, 808C919B213FD2B2004B0F7C /* TestImageAnimated.webp */, 3219F3B1228B0453003822A6 /* TestImageBlendAnimated.webp */, @@ -154,6 +157,7 @@ 3219F3B2228B0453003822A6 /* TestImageBlendAnimated.webp in Resources */, 808C919D213FD2B2004B0F7C /* TestImageAnimated.webp in Resources */, 808C919C213FD2B2004B0F7C /* TestImageStatic.webp in Resources */, + 325E268E25C82BE1000B807B /* TestImageGrayscale.jpg in Resources */, ); runOnlyForDeploymentPostprocessing = 0; };