Use the better way to detect lazy/non-lazy CGImage. Only do force decoding for lazy image
This effect the thumbnail decoding (which produce non-lazy CGImage, but accidentally been force decoded)
This commit is contained in:
parent
600e1b68af
commit
ecedea2e06
|
@ -107,6 +107,12 @@ typedef struct SDImagePixelFormat {
|
||||||
*/
|
*/
|
||||||
+ (BOOL)CGImageContainsAlpha:(_Nonnull CGImageRef)cgImage;
|
+ (BOOL)CGImageContainsAlpha:(_Nonnull CGImageRef)cgImage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Detect whether the CGImage is lazy and not-yet decoded. (lazy means, only when the caller access the underlying bitmap buffer via provider like `CGDataProviderCopyData` or `CGDataProviderRetainBytePtr`, the decoder will allocate memory, it's a lazy allocation)
|
||||||
|
The implementation use the Core Graphics internal to check whether the CGImage is `CGImageProvider` based, or `CGDataProvider` based. The `CGDataProvider` based is treated as non-lazy.
|
||||||
|
*/
|
||||||
|
+ (BOOL)CGImageIsLazy:(_Nonnull CGImageRef)cgImage;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Create a decoded CGImage by the provided CGImage. This follows The Create Rule and you are response to call release after usage.
|
Create a decoded CGImage by the provided CGImage. This follows The Create Rule and you are response to call release after usage.
|
||||||
It will detect whether image contains alpha channel, then create a new bitmap context with the same size of image, and draw it. This can ensure that the image do not need extra decoding after been set to the imageView.
|
It will detect whether image contains alpha channel, then create a new bitmap context with the same size of image, and draw it. This can ensure that the image do not need extra decoding after been set to the imageView.
|
||||||
|
|
|
@ -381,6 +381,45 @@ static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to over
|
||||||
return hasAlpha;
|
return hasAlpha;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
+ (BOOL)CGImageIsLazy:(CGImageRef)cgImage {
|
||||||
|
if (!cgImage) {
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
// CoreGraphics use CGImage's C struct filed (offset 0xd8 on iOS 17.0)
|
||||||
|
// But since the description of `CGImageRef` always contains the `[DP]` (DataProvider) and `[IP]` (ImageProvider), we can use this as a hint
|
||||||
|
NSString *description = (__bridge_transfer NSString *)CFCopyDescription(cgImage);
|
||||||
|
if (description) {
|
||||||
|
// Solution 1: Parse the description to get provider
|
||||||
|
// <CGImage 0x10740ffe0> (IP) -> YES
|
||||||
|
// <CGImage 0x10740ffe0> (DP) -> NO
|
||||||
|
NSArray<NSString *> *lines = [description componentsSeparatedByString:@"\n"];
|
||||||
|
if (lines.count > 0) {
|
||||||
|
NSString *firstLine = lines[0];
|
||||||
|
NSRange startRange = [firstLine rangeOfString:@"("];
|
||||||
|
NSRange endRange = [firstLine rangeOfString:@")"];
|
||||||
|
if (startRange.location != NSNotFound && endRange.location != NSNotFound) {
|
||||||
|
NSRange resultRange = NSMakeRange(startRange.location + 1, endRange.location - startRange.location - 1);
|
||||||
|
NSString *providerString = [firstLine substringWithRange:resultRange];
|
||||||
|
if ([providerString isEqualToString:@"IP"]) {
|
||||||
|
return YES;
|
||||||
|
} else if ([providerString isEqualToString:@"DP"]) {
|
||||||
|
return NO;
|
||||||
|
} else {
|
||||||
|
// New cases ? fallback
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Solution 2: Use UTI metadata
|
||||||
|
CFStringRef uttype = CGImageGetUTType(cgImage);
|
||||||
|
if (uttype) {
|
||||||
|
// Only ImageIO can set `com.apple.ImageIO.imageSourceTypeIdentifier` metadata for lazy decoded CGImage
|
||||||
|
return YES;
|
||||||
|
} else {
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
+ (CGImageRef)CGImageCreateDecoded:(CGImageRef)cgImage {
|
+ (CGImageRef)CGImageCreateDecoded:(CGImageRef)cgImage {
|
||||||
return [self CGImageCreateDecoded:cgImage orientation:kCGImagePropertyOrientationUp];
|
return [self CGImageCreateDecoded:cgImage orientation:kCGImagePropertyOrientationUp];
|
||||||
}
|
}
|
||||||
|
@ -930,12 +969,13 @@ static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to over
|
||||||
// Check policy (automatic)
|
// Check policy (automatic)
|
||||||
CGImageRef cgImage = image.CGImage;
|
CGImageRef cgImage = image.CGImage;
|
||||||
if (cgImage) {
|
if (cgImage) {
|
||||||
CFStringRef uttype = CGImageGetUTType(cgImage);
|
// Check if it's lazy CGImage wrapper or not
|
||||||
if (uttype) {
|
BOOL isLazy = [SDImageCoderHelper CGImageIsLazy:cgImage];
|
||||||
// Only ImageIO can set `com.apple.ImageIO.imageSourceTypeIdentifier`
|
if (isLazy) {
|
||||||
|
// Lazy CGImage should trigger force decode before rendering
|
||||||
return YES;
|
return YES;
|
||||||
} else {
|
} else {
|
||||||
// Now, let's check if the CGImage is hardware supported (not byte-aligned will cause extra copy)
|
// Now, let's check if this non-lazy CGImage is hardware supported (not byte-aligned will cause extra copy)
|
||||||
BOOL isSupported = [SDImageCoderHelper CGImageIsHardwareSupported:cgImage];
|
BOOL isSupported = [SDImageCoderHelper CGImageIsHardwareSupported:cgImage];
|
||||||
return !isSupported;
|
return !isSupported;
|
||||||
}
|
}
|
||||||
|
|
|
@ -479,7 +479,6 @@ static BOOL SDImageIOPNGPluginBuggyNeedWorkaround(void) {
|
||||||
if (!imageRef) {
|
if (!imageRef) {
|
||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
BOOL isDecoded = NO;
|
|
||||||
// Thumbnail image post-process
|
// Thumbnail image post-process
|
||||||
if (!createFullImage) {
|
if (!createFullImage) {
|
||||||
if (preserveAspectRatio) {
|
if (preserveAspectRatio) {
|
||||||
|
@ -491,19 +490,19 @@ static BOOL SDImageIOPNGPluginBuggyNeedWorkaround(void) {
|
||||||
if (scaledImageRef) {
|
if (scaledImageRef) {
|
||||||
CGImageRelease(imageRef);
|
CGImageRelease(imageRef);
|
||||||
imageRef = scaledImageRef;
|
imageRef = scaledImageRef;
|
||||||
isDecoded = YES;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Check whether output CGImage is decoded
|
// Check whether output CGImage is decoded
|
||||||
|
BOOL isLazy = [SDImageCoderHelper CGImageIsLazy:imageRef];
|
||||||
if (!lazyDecode) {
|
if (!lazyDecode) {
|
||||||
if (!isDecoded) {
|
if (isLazy) {
|
||||||
// Use CoreGraphics to trigger immediately decode
|
// Use CoreGraphics to trigger immediately decode to drop lazy CGImage
|
||||||
CGImageRef decodedImageRef = [SDImageCoderHelper CGImageCreateDecoded:imageRef];
|
CGImageRef decodedImageRef = [SDImageCoderHelper CGImageCreateDecoded:imageRef];
|
||||||
if (decodedImageRef) {
|
if (decodedImageRef) {
|
||||||
CGImageRelease(imageRef);
|
CGImageRelease(imageRef);
|
||||||
imageRef = decodedImageRef;
|
imageRef = decodedImageRef;
|
||||||
isDecoded = YES;
|
isLazy = NO;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (animatedImage) {
|
} else if (animatedImage) {
|
||||||
|
@ -545,7 +544,7 @@ static BOOL SDImageIOPNGPluginBuggyNeedWorkaround(void) {
|
||||||
UIImage *image = [[UIImage alloc] initWithCGImage:imageRef scale:scale orientation:exifOrientation];
|
UIImage *image = [[UIImage alloc] initWithCGImage:imageRef scale:scale orientation:exifOrientation];
|
||||||
#endif
|
#endif
|
||||||
CGImageRelease(imageRef);
|
CGImageRelease(imageRef);
|
||||||
image.sd_isDecoded = isDecoded;
|
image.sd_isDecoded = !isLazy;
|
||||||
|
|
||||||
return image;
|
return image;
|
||||||
}
|
}
|
||||||
|
|
|
@ -353,6 +353,10 @@
|
||||||
CGSize imageSize = image.size;
|
CGSize imageSize = image.size;
|
||||||
expect(imageSize.width).equal(400);
|
expect(imageSize.width).equal(400);
|
||||||
expect(imageSize.height).equal(263);
|
expect(imageSize.height).equal(263);
|
||||||
|
// `CGImageSourceCreateThumbnailAtIndex` should always produce non-lazy CGImage
|
||||||
|
CGImageRef cgImage = image.CGImage;
|
||||||
|
expect([SDImageCoderHelper CGImageIsLazy:cgImage]).beFalsy();
|
||||||
|
expect(image.sd_isDecoded).beTruthy();
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)test23ThatThumbnailEncodeCalculation {
|
- (void)test23ThatThumbnailEncodeCalculation {
|
||||||
|
@ -360,6 +364,10 @@
|
||||||
NSData *testImageData = [NSData dataWithContentsOfFile:testImagePath];
|
NSData *testImageData = [NSData dataWithContentsOfFile:testImagePath];
|
||||||
UIImage *image = [SDImageIOCoder.sharedCoder decodedImageWithData:testImageData options:nil];
|
UIImage *image = [SDImageIOCoder.sharedCoder decodedImageWithData:testImageData options:nil];
|
||||||
expect(image.size).equal(CGSizeMake(5250, 3450));
|
expect(image.size).equal(CGSizeMake(5250, 3450));
|
||||||
|
// `CGImageSourceCreateImageAtIndex` should always produce lazy CGImage
|
||||||
|
CGImageRef cgImage = image.CGImage;
|
||||||
|
expect([SDImageCoderHelper CGImageIsLazy:cgImage]).beTruthy();
|
||||||
|
expect(image.sd_isDecoded).beFalsy();
|
||||||
CGSize thumbnailSize = CGSizeMake(4000, 4000); // 3450 < 4000 < 5250
|
CGSize thumbnailSize = CGSizeMake(4000, 4000); // 3450 < 4000 < 5250
|
||||||
NSData *encodedData = [SDImageIOCoder.sharedCoder encodedDataWithImage:image format:SDImageFormatJPEG options:@{
|
NSData *encodedData = [SDImageIOCoder.sharedCoder encodedDataWithImage:image format:SDImageFormatJPEG options:@{
|
||||||
SDImageCoderEncodeMaxPixelSize: @(thumbnailSize)
|
SDImageCoderEncodeMaxPixelSize: @(thumbnailSize)
|
||||||
|
|
Loading…
Reference in New Issue