Merge pull request #3736 from dreampiggy/performance/better_lazy_cgimage_detection
Use the better way to detect lazy/non-lazy CGImage. Only do force decoding for lazy image (avoid thumbnail image been decoded twice)
This commit is contained in:
commit
87b7a9a45c
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -352,13 +352,19 @@ static BOOL _isCalled;
|
|||
- (void)test24AnimatedImageViewCategoryDiskCache {
|
||||
XCTestExpectation *expectation = [self expectationWithDescription:@"test SDAnimatedImageView view category disk cache"];
|
||||
SDAnimatedImageView *imageView = [SDAnimatedImageView new];
|
||||
NSURL *testURL = [NSURL URLWithString:kTestGIFURL];
|
||||
[SDImageCache.sharedImageCache removeImageFromMemoryForKey:testURL.absoluteString];
|
||||
NSURL *testURL = [NSURL URLWithString:@"https://foobar.non-exists.org/bizbuz.gif"];
|
||||
NSString *testKey = testURL.absoluteString;
|
||||
[SDImageCache.sharedImageCache removeImageFromMemoryForKey:testKey];
|
||||
[SDImageCache.sharedImageCache removeImageFromDiskForKey:testKey];
|
||||
NSData *imageData = [self testGIFData];
|
||||
[SDImageCache.sharedImageCache storeImageDataToDisk:imageData forKey:testKey];
|
||||
[imageView sd_setImageWithURL:testURL placeholderImage:nil completed:^(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL) {
|
||||
expect(error).to.beNil();
|
||||
expect(image).notTo.beNil();
|
||||
expect(cacheType).equal(SDImageCacheTypeDisk);
|
||||
expect([image isKindOfClass:[SDAnimatedImage class]]).beTruthy();
|
||||
[SDImageCache.sharedImageCache removeImageFromMemoryForKey:testKey];
|
||||
[SDImageCache.sharedImageCache removeImageFromDiskForKey:testKey];
|
||||
[expectation fulfill];
|
||||
}];
|
||||
[self waitForExpectationsWithCommonTimeout];
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -123,11 +123,7 @@
|
|||
#endif
|
||||
expect(format.scale).equal(screenScale);
|
||||
expect(format.opaque).beFalsy();
|
||||
#if SD_UIKIT
|
||||
expect(format.preferredRange).equal(SDGraphicsImageRendererFormatRangeAutomatic);
|
||||
#elif SD_MAC
|
||||
expect(format.preferredRange).equal(SDGraphicsImageRendererFormatRangeStandard);
|
||||
#endif
|
||||
CGSize size = CGSizeMake(100, 100);
|
||||
SDGraphicsImageRenderer *renderer = [[SDGraphicsImageRenderer alloc] initWithSize:size format:format];
|
||||
#if SD_MAC
|
||||
|
|
Loading…
Reference in New Issue