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;
|
||||
|
||||
/**
|
||||
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.
|
||||
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;
|
||||
}
|
||||
|
||||
+ (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 {
|
||||
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)
|
||||
CGImageRef cgImage = image.CGImage;
|
||||
if (cgImage) {
|
||||
CFStringRef uttype = CGImageGetUTType(cgImage);
|
||||
if (uttype) {
|
||||
// Only ImageIO can set `com.apple.ImageIO.imageSourceTypeIdentifier`
|
||||
// Check if it's lazy CGImage wrapper or not
|
||||
BOOL isLazy = [SDImageCoderHelper CGImageIsLazy:cgImage];
|
||||
if (isLazy) {
|
||||
// Lazy CGImage should trigger force decode before rendering
|
||||
return YES;
|
||||
} 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];
|
||||
return !isSupported;
|
||||
}
|
||||
|
|
|
@ -479,7 +479,6 @@ static BOOL SDImageIOPNGPluginBuggyNeedWorkaround(void) {
|
|||
if (!imageRef) {
|
||||
return nil;
|
||||
}
|
||||
BOOL isDecoded = NO;
|
||||
// Thumbnail image post-process
|
||||
if (!createFullImage) {
|
||||
if (preserveAspectRatio) {
|
||||
|
@ -491,19 +490,19 @@ static BOOL SDImageIOPNGPluginBuggyNeedWorkaround(void) {
|
|||
if (scaledImageRef) {
|
||||
CGImageRelease(imageRef);
|
||||
imageRef = scaledImageRef;
|
||||
isDecoded = YES;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Check whether output CGImage is decoded
|
||||
BOOL isLazy = [SDImageCoderHelper CGImageIsLazy:imageRef];
|
||||
if (!lazyDecode) {
|
||||
if (!isDecoded) {
|
||||
// Use CoreGraphics to trigger immediately decode
|
||||
if (isLazy) {
|
||||
// Use CoreGraphics to trigger immediately decode to drop lazy CGImage
|
||||
CGImageRef decodedImageRef = [SDImageCoderHelper CGImageCreateDecoded:imageRef];
|
||||
if (decodedImageRef) {
|
||||
CGImageRelease(imageRef);
|
||||
imageRef = decodedImageRef;
|
||||
isDecoded = YES;
|
||||
isLazy = NO;
|
||||
}
|
||||
}
|
||||
} else if (animatedImage) {
|
||||
|
@ -545,7 +544,7 @@ static BOOL SDImageIOPNGPluginBuggyNeedWorkaround(void) {
|
|||
UIImage *image = [[UIImage alloc] initWithCGImage:imageRef scale:scale orientation:exifOrientation];
|
||||
#endif
|
||||
CGImageRelease(imageRef);
|
||||
image.sd_isDecoded = isDecoded;
|
||||
image.sd_isDecoded = !isLazy;
|
||||
|
||||
return image;
|
||||
}
|
||||
|
|
|
@ -353,6 +353,10 @@
|
|||
CGSize imageSize = image.size;
|
||||
expect(imageSize.width).equal(400);
|
||||
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 {
|
||||
|
@ -360,6 +364,10 @@
|
|||
NSData *testImageData = [NSData dataWithContentsOfFile:testImagePath];
|
||||
UIImage *image = [SDImageIOCoder.sharedCoder decodedImageWithData:testImageData options:nil];
|
||||
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
|
||||
NSData *encodedData = [SDImageIOCoder.sharedCoder encodedDataWithImage:image format:SDImageFormatJPEG options:@{
|
||||
SDImageCoderEncodeMaxPixelSize: @(thumbnailSize)
|
||||
|
|
Loading…
Reference in New Issue