diff --git a/SDWebImage/Core/SDImageCoderHelper.h b/SDWebImage/Core/SDImageCoderHelper.h index af30c5f6..a9156902 100644 --- a/SDWebImage/Core/SDImageCoderHelper.h +++ b/SDWebImage/Core/SDImageCoderHelper.h @@ -41,6 +41,15 @@ static inline size_t SDByteAlign(size_t size, size_t alignment) { return ((size + (alignment - 1)) / alignment) * alignment; } +/// The pixel format about the information to call `CGImageCreate` suitable for current hardware rendering +/// +typedef struct SDImagePixelFormat { + /// Typically is pre-multiplied RGBA8888 for alpha image, RGBX8888 for non-alpha image. + CGBitmapInfo bitmapInfo; + /// Typically is 32, the 8 pixels bytesPerRow. + size_t alignment; +} SDImagePixelFormat; + /** Provide some common helper methods for building the image decoder/encoder. */ @@ -79,23 +88,15 @@ static inline size_t SDByteAlign(size_t size, size_t alignment) { + (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 32, the 8 pixels bytesPerRow. - @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. + Tthis returns the pixel format **Preferred from current hardward && OS using runtime detection** @param containsAlpha Whether the image to render contains alpha channel */ -+ (CGBitmapInfo)preferredBitmapInfo:(BOOL)containsAlpha; ++ (SDImagePixelFormat)preferredPixelFormat:(BOOL)containsAlpha; /** Check whether CGImage is hardware supported to rendering on screen, without the trigger of `CA::Render::copy_image` You can debug the copied image by using Xcode's `Color Copied Image`, the copied image will turn Cyan and occupy double RAM for bitmap buffer. - Typically, when the CGImage's using the method above (`colorspace` / `byteAlignment` / `bitmapInfo`) can render withtout the copy. + Typically, when the CGImage's using the method above (`colorspace` / `alignment` / `bitmapInfo`) can render withtout the copy. */ + (BOOL)CGImageIsHardwareSupported:(_Nonnull CGImageRef)cgImage; diff --git a/SDWebImage/Core/SDImageCoderHelper.m b/SDWebImage/Core/SDImageCoderHelper.m index e1bba0cd..6b9d650b 100644 --- a/SDWebImage/Core/SDImageCoderHelper.m +++ b/SDWebImage/Core/SDImageCoderHelper.m @@ -308,27 +308,36 @@ static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to over return colorSpace; } -+ (size_t)preferredByteAlignment { - // https://github.com/path/FastImageCache#byte-alignment - // A properly aligned bytes-per-row value must be a multiple of 8 pixels × bytes per pixel. - return 32; -} - -+ (CGBitmapInfo)preferredBitmapInfo:(BOOL)containsAlpha { ++ (SDImagePixelFormat)preferredPixelFormat:(BOOL)containsAlpha { CGImageRef cgImage; if (containsAlpha) { cgImage = SDImageGetAlphaDummyImage().CGImage; } else { cgImage = SDImageGetNonAlphaDummyImage().CGImage; } - return CGImageGetBitmapInfo(cgImage); + CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(cgImage); + size_t bitsPerPixel = 8; + if (SD_OPTIONS_CONTAINS(bitmapInfo, kCGBitmapFloatComponents)) { + bitsPerPixel = 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; + SDImagePixelFormat pixelFormat = { + .bitmapInfo = bitmapInfo, + .alignment = alignment + }; + return pixelFormat; } + (BOOL)CGImageIsHardwareSupported:(CGImageRef)cgImage { BOOL supported = YES; // 1. Check byte alignment size_t bytesPerRow = CGImageGetBytesPerRow(cgImage); - if (SDByteAlign(bytesPerRow, [SDImageCoderHelper preferredByteAlignment]) == bytesPerRow) { + BOOL hasAlpha = [self CGImageContainsAlpha:cgImage]; + SDImagePixelFormat pixelFormat = [self preferredPixelFormat:hasAlpha]; + if (SDByteAlign(bytesPerRow, pixelFormat.alignment) == bytesPerRow) { // byte aligned, OK supported &= YES; } else { @@ -405,7 +414,7 @@ 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. // From v5.17.0, use runtime detection of bitmap info instead of hardcode. - CGBitmapInfo bitmapInfo = [SDImageCoderHelper preferredBitmapInfo:hasAlpha]; + CGBitmapInfo bitmapInfo = [SDImageCoderHelper preferredPixelFormat:hasAlpha].bitmapInfo; CGContextRef context = CGBitmapContextCreate(NULL, newWidth, newHeight, 8, 0, [self colorSpaceGetDeviceRGB], bitmapInfo); if (!context) { return NULL; @@ -441,7 +450,7 @@ 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. // From v5.17.0, use runtime detection of bitmap info instead of hardcode. - CGBitmapInfo bitmapInfo = [SDImageCoderHelper preferredBitmapInfo:hasAlpha]; + CGBitmapInfo bitmapInfo = [SDImageCoderHelper preferredPixelFormat:hasAlpha].bitmapInfo; vImage_CGImageFormat format = (vImage_CGImageFormat) { .bitsPerComponent = 8, .bitsPerPixel = 32, @@ -653,7 +662,7 @@ 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. // From v5.17.0, use runtime detection of bitmap info instead of hardcode. - CGBitmapInfo bitmapInfo = [SDImageCoderHelper preferredBitmapInfo:hasAlpha]; + CGBitmapInfo bitmapInfo = [SDImageCoderHelper preferredPixelFormat:hasAlpha].bitmapInfo; CGContextRef destContext = CGBitmapContextCreate(NULL, destResolution.width, destResolution.height, diff --git a/SDWebImage/Core/SDImageGraphics.m b/SDWebImage/Core/SDImageGraphics.m index 43fc3dbf..2e877f3d 100644 --- a/SDWebImage/Core/SDImageGraphics.m +++ b/SDWebImage/Core/SDImageGraphics.m @@ -34,7 +34,6 @@ static CGContextRef SDCGContextCreateBitmapContext(CGSize size, BOOL opaque, CGF // 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) { // [NSImage imageWithSize:flipped:drawingHandler:] returns float(16-bits) RGBA8888 on alpha image, which we don't need diff --git a/SDWebImage/Core/UIImage+ForceDecode.h b/SDWebImage/Core/UIImage+ForceDecode.h index 9d48a82f..658659ad 100644 --- a/SDWebImage/Core/UIImage+ForceDecode.h +++ b/SDWebImage/Core/UIImage+ForceDecode.h @@ -18,7 +18,7 @@ Force decode is used for 2 cases: -- 1. for ImageIO created image (via `CGImageCreateWithImageSource` SPI), it's lazy and we trigger the decode before rendering -- 2. for non-ImageIO created image (via `CGImageCreate` API), we can ensure it's alignment is suitable to render on screen without copy by CoreAnimation - @note For coder plugin developer, always use the SDImageCoderHelper's `colorSpaceGetDeviceRGB`/`preferredByteAlignment`/`preferredBitmapInfo:` to create CGImage. + @note For coder plugin developer, always use the SDImageCoderHelper's `colorSpaceGetDeviceRGB`/`preferredPixelFormat` to create CGImage. @note For more information why force decode, see: https://github.com/path/FastImageCache#byte-alignment @note From v5.17.0, the default value is always NO. Use `SDImageForceDecodePolicy` to control complicated policy. */ diff --git a/Tests/Tests/SDImageCoderTests.m b/Tests/Tests/SDImageCoderTests.m index aa8373be..55364cdc 100644 --- a/Tests/Tests/SDImageCoderTests.m +++ b/Tests/Tests/SDImageCoderTests.m @@ -455,19 +455,21 @@ size_t bitsPerComponent = 8; size_t components = 4; size_t bitsPerPixel = bitsPerComponent * components; - size_t bytesPerRow = SDByteAlign(bitsPerPixel / 8 * width, SDImageCoderHelper.preferredByteAlignment); + size_t bytesPerRow = SDByteAlign(bitsPerPixel / 8 * width, [SDImageCoderHelper preferredPixelFormat:YES].alignment); size_t size = bytesPerRow * height; uint8_t bitmap[size]; for (size_t i = 0; i < size; i++) { bitmap[i] = 255; } CGColorSpaceRef colorspace = [SDImageCoderHelper colorSpaceGetDeviceRGB]; - CGBitmapInfo bitmapInfo = [SDImageCoderHelper preferredBitmapInfo:YES]; - CFDataRef data = CFDataCreateWithBytesNoCopy(NULL, bitmap, size, NULL); + CGBitmapInfo bitmapInfo = [SDImageCoderHelper preferredPixelFormat:YES].bitmapInfo; + CFDataRef data = CFDataCreate(NULL, bitmap, size); CGDataProviderRef provider = CGDataProviderCreateWithCFData(data); + CFRelease(data); BOOL shouldInterpolate = YES; CGColorRenderingIntent intent = kCGRenderingIntentDefault; CGImageRef cgImage = CGImageCreate(width, height, bitsPerComponent, bitsPerPixel, bytesPerRow, colorspace, bitmapInfo, provider, NULL, shouldInterpolate, intent); + CGDataProviderRelease(provider); XCTAssert(cgImage); BOOL result = [SDImageCoderHelper CGImageIsHardwareSupported:cgImage]; // Since it's 32 bytes aligned, return true @@ -500,12 +502,14 @@ bitmap[i] = 255; } CGColorSpaceRef colorspace = [SDImageCoderHelper colorSpaceGetDeviceRGB]; - CGBitmapInfo bitmapInfo = [SDImageCoderHelper preferredBitmapInfo:YES]; - CFDataRef data = CFDataCreateWithBytesNoCopy(NULL, bitmap, size, NULL); + CGBitmapInfo bitmapInfo = [SDImageCoderHelper preferredPixelFormat:YES].bitmapInfo; + CFDataRef data = CFDataCreate(NULL, bitmap, size); CGDataProviderRef provider = CGDataProviderCreateWithCFData(data); + CFRelease(data); BOOL shouldInterpolate = YES; CGColorRenderingIntent intent = kCGRenderingIntentDefault; CGImageRef cgImage = CGImageCreate(width, height, bitsPerComponent, bitsPerPixel, bytesPerRow, colorspace, bitmapInfo, provider, NULL, shouldInterpolate, intent); + CGDataProviderRelease(provider); XCTAssert(cgImage); BOOL result = [SDImageCoderHelper CGImageIsHardwareSupported:cgImage]; // Since it's not 32 bytes aligned, return false diff --git a/Tests/Tests/SDWebImageManagerTests.m b/Tests/Tests/SDWebImageManagerTests.m index 9720181b..a7fd1b3c 100644 --- a/Tests/Tests/SDWebImageManagerTests.m +++ b/Tests/Tests/SDWebImageManagerTests.m @@ -298,8 +298,7 @@ NSUInteger defaultLimitBytes = SDImageCoderHelper.defaultScaleDownLimitBytes; SDImageCoderHelper.defaultScaleDownLimitBytes = 1000 * 1000 * 4; // Limit 1000x1000 pixel // From v5.5.0, the `SDWebImageScaleDownLargeImages` translate to `SDWebImageContextImageThumbnailPixelSize`, and works for progressive loading - [SDImageCache.sharedImageCache removeImageFromDiskForKey:originalImageURL.absoluteString]; - [SDWebImageManager.sharedManager loadImageWithURL:originalImageURL options:SDWebImageScaleDownLargeImages | SDWebImageProgressiveLoad progress:nil completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, SDImageCacheType cacheType, BOOL finished, NSURL * _Nullable imageURL) { + [SDWebImageManager.sharedManager loadImageWithURL:originalImageURL options:SDWebImageFromLoaderOnly | SDWebImageScaleDownLargeImages | SDWebImageProgressiveLoad 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.size).equal(CGSizeMake(1000, 1000)); if (finished) { @@ -310,7 +309,7 @@ } }]; - [self waitForExpectationsWithCommonTimeoutUsingHandler:^(NSError * _Nullable error) { + [self waitForExpectationsWithTimeout:100 handler:^(NSError * _Nullable error) { SDImageCoderHelper.defaultScaleDownLimitBytes = defaultLimitBytes; }]; }