Merge pull request #3461 from dreampiggy/bugfix/yuv420_jpeg_hevc
Fix the iOS 15+ force-decode hack break Apple's HEIF and JPEG YUV420 optimization
This commit is contained in:
commit
8be9f2c3f9
|
@ -183,12 +183,8 @@ static CGImageRef __nullable SDCGImageCreateCopy(CGImageRef cg_nullable image) {
|
|||
}
|
||||
|
||||
+ (NSTimeInterval)frameDurationAtIndex:(NSUInteger)index source:(CGImageSourceRef)source {
|
||||
NSDictionary *options = @{
|
||||
(__bridge NSString *)kCGImageSourceShouldCacheImmediately : @(YES),
|
||||
(__bridge NSString *)kCGImageSourceShouldCache : @(YES) // Always cache to reduce CPU usage
|
||||
};
|
||||
NSTimeInterval frameDuration = 0.1;
|
||||
CFDictionaryRef cfFrameProperties = CGImageSourceCopyPropertiesAtIndex(source, index, (__bridge CFDictionaryRef)options);
|
||||
CFDictionaryRef cfFrameProperties = CGImageSourceCopyPropertiesAtIndex(source, index, NULL);
|
||||
if (!cfFrameProperties) {
|
||||
return frameDuration;
|
||||
}
|
||||
|
@ -218,10 +214,24 @@ static CGImageRef __nullable SDCGImageCreateCopy(CGImageRef cg_nullable image) {
|
|||
return frameDuration;
|
||||
}
|
||||
|
||||
+ (UIImage *)createFrameAtIndex:(NSUInteger)index source:(CGImageSourceRef)source scale:(CGFloat)scale preserveAspectRatio:(BOOL)preserveAspectRatio thumbnailSize:(CGSize)thumbnailSize lazyDecode:(BOOL)lazyDecode options:(NSDictionary *)options {
|
||||
// Some options need to pass to `CGImageSourceCopyPropertiesAtIndex` before `CGImageSourceCreateImageAtIndex`, or ImageIO will ignore them because they parse once :)
|
||||
+ (UIImage *)createFrameAtIndex:(NSUInteger)index source:(CGImageSourceRef)source scale:(CGFloat)scale preserveAspectRatio:(BOOL)preserveAspectRatio thumbnailSize:(CGSize)thumbnailSize lazyDecode:(BOOL)lazyDecode animatedImage:(BOOL)animatedImage {
|
||||
// `animatedImage` means called from `SDAnimatedImageProvider.animatedImageFrameAtIndex`
|
||||
NSDictionary *options;
|
||||
if (animatedImage) {
|
||||
if (!lazyDecode) {
|
||||
options = @{
|
||||
// image decoding and caching should happen at image creation time.
|
||||
(__bridge NSString *)kCGImageSourceShouldCacheImmediately : @(YES),
|
||||
};
|
||||
} else {
|
||||
options = @{
|
||||
// image decoding will happen at rendering time
|
||||
(__bridge NSString *)kCGImageSourceShouldCacheImmediately : @(NO),
|
||||
};
|
||||
}
|
||||
}
|
||||
// Parse the image properties
|
||||
NSDictionary *properties = (__bridge_transfer NSDictionary *)CGImageSourceCopyPropertiesAtIndex(source, index, (__bridge CFDictionaryRef)options);
|
||||
NSDictionary *properties = (__bridge_transfer NSDictionary *)CGImageSourceCopyPropertiesAtIndex(source, index, NULL);
|
||||
CGFloat pixelWidth = [properties[(__bridge NSString *)kCGImagePropertyPixelWidth] doubleValue];
|
||||
CGFloat pixelHeight = [properties[(__bridge NSString *)kCGImagePropertyPixelHeight] doubleValue];
|
||||
CGImagePropertyOrientation exifOrientation = (CGImagePropertyOrientation)[properties[(__bridge NSString *)kCGImagePropertyOrientation] unsignedIntegerValue];
|
||||
|
@ -287,7 +297,7 @@ static CGImageRef __nullable SDCGImageCreateCopy(CGImageRef cg_nullable image) {
|
|||
isDecoded = YES;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
} else if (animatedImage) {
|
||||
// iOS 15+, CGImageRef now retains CGImageSourceRef internally. To workaround its thread-safe issue, we have to strip CGImageSourceRef, using Force-Decode (or have to use SPI `CGImageSetImageSource`), See: https://github.com/SDWebImage/SDWebImage/issues/3273
|
||||
if (@available(iOS 15, tvOS 15, *)) {
|
||||
// User pass `lazyDecode == YES`, but we still have to strip the CGImageSourceRef
|
||||
|
@ -297,21 +307,19 @@ static CGImageRef __nullable SDCGImageCreateCopy(CGImageRef cg_nullable image) {
|
|||
CGImageRelease(imageRef);
|
||||
imageRef = newImageRef;
|
||||
}
|
||||
}
|
||||
}
|
||||
#if SD_CHECK_CGIMAGE_RETAIN_SOURCE
|
||||
if (@available(iOS 15, tvOS 15, *)) {
|
||||
// Assert here to check CGImageRef should not retain the CGImageSourceRef and has possible thread-safe issue (this is behavior on iOS 15+)
|
||||
// If assert hit, fire issue to https://github.com/SDWebImage/SDWebImage/issues and we update the condition for this behavior check
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
SDCGImageGetImageSource = dlsym(RTLD_DEFAULT, "CGImageGetImageSource");
|
||||
});
|
||||
if (SDCGImageGetImageSource) {
|
||||
NSCAssert(!SDCGImageGetImageSource(imageRef), @"Animated Coder created CGImageRef should not retain CGImageSourceRef, which may cause thread-safe issue without lock");
|
||||
// Assert here to check CGImageRef should not retain the CGImageSourceRef and has possible thread-safe issue (this is behavior on iOS 15+)
|
||||
// If assert hit, fire issue to https://github.com/SDWebImage/SDWebImage/issues and we update the condition for this behavior check
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
SDCGImageGetImageSource = dlsym(RTLD_DEFAULT, "CGImageGetImageSource");
|
||||
});
|
||||
if (SDCGImageGetImageSource) {
|
||||
NSCAssert(!SDCGImageGetImageSource(imageRef), @"Animated Coder created CGImageRef should not retain CGImageSourceRef, which may cause thread-safe issue without lock");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if SD_UIKIT || SD_WATCH
|
||||
UIImageOrientation imageOrientation = [SDImageCoderHelper imageOrientationFromEXIFOrientation:exifOrientation];
|
||||
|
@ -412,12 +420,12 @@ static CGImageRef __nullable SDCGImageCreateCopy(CGImageRef cg_nullable image) {
|
|||
|
||||
BOOL decodeFirstFrame = [options[SDImageCoderDecodeFirstFrameOnly] boolValue];
|
||||
if (decodeFirstFrame || count <= 1) {
|
||||
animatedImage = [self.class createFrameAtIndex:0 source:source scale:scale preserveAspectRatio:preserveAspectRatio thumbnailSize:thumbnailSize lazyDecode:lazyDecode options:nil];
|
||||
animatedImage = [self.class createFrameAtIndex:0 source:source scale:scale preserveAspectRatio:preserveAspectRatio thumbnailSize:thumbnailSize lazyDecode:lazyDecode animatedImage:NO];
|
||||
} else {
|
||||
NSMutableArray<SDImageFrame *> *frames = [NSMutableArray arrayWithCapacity:count];
|
||||
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
UIImage *image = [self.class createFrameAtIndex:i source:source scale:scale preserveAspectRatio:preserveAspectRatio thumbnailSize:thumbnailSize lazyDecode:lazyDecode options:nil];
|
||||
UIImage *image = [self.class createFrameAtIndex:i source:source scale:scale preserveAspectRatio:preserveAspectRatio thumbnailSize:thumbnailSize lazyDecode:lazyDecode animatedImage:NO];
|
||||
if (!image) {
|
||||
continue;
|
||||
}
|
||||
|
@ -473,7 +481,7 @@ static CGImageRef __nullable SDCGImageCreateCopy(CGImageRef cg_nullable image) {
|
|||
preserveAspectRatio = preserveAspectRatioValue.boolValue;
|
||||
}
|
||||
_preserveAspectRatio = preserveAspectRatio;
|
||||
BOOL lazyDecode = YES; // Defaults YES for static image coder
|
||||
BOOL lazyDecode = NO; // Defaults NO for animated image coder
|
||||
NSNumber *lazyDecodeValue = options[SDImageCoderDecodeUseLazyDecoding];
|
||||
if (lazyDecodeValue != nil) {
|
||||
lazyDecode = lazyDecodeValue.boolValue;
|
||||
|
@ -502,11 +510,7 @@ static CGImageRef __nullable SDCGImageCreateCopy(CGImageRef cg_nullable image) {
|
|||
CGImageSourceUpdateData(_imageSource, (__bridge CFDataRef)data, finished);
|
||||
|
||||
if (_width + _height == 0) {
|
||||
NSDictionary *options = @{
|
||||
(__bridge NSString *)kCGImageSourceShouldCacheImmediately : @(YES),
|
||||
(__bridge NSString *)kCGImageSourceShouldCache : @(YES) // Always cache to reduce CPU usage
|
||||
};
|
||||
CFDictionaryRef properties = CGImageSourceCopyPropertiesAtIndex(_imageSource, 0, (__bridge CFDictionaryRef)options);
|
||||
CFDictionaryRef properties = CGImageSourceCopyPropertiesAtIndex(_imageSource, 0, NULL);
|
||||
if (properties) {
|
||||
CFTypeRef val = CFDictionaryGetValue(properties, kCGImagePropertyPixelHeight);
|
||||
if (val) CFNumberGetValue(val, kCFNumberLongType, &_height);
|
||||
|
@ -533,7 +537,7 @@ static CGImageRef __nullable SDCGImageCreateCopy(CGImageRef cg_nullable image) {
|
|||
if (scaleFactor != nil) {
|
||||
scale = MAX([scaleFactor doubleValue], 1);
|
||||
}
|
||||
image = [self.class createFrameAtIndex:0 source:_imageSource scale:scale preserveAspectRatio:_preserveAspectRatio thumbnailSize:_thumbnailSize lazyDecode:_lazyDecode options:nil];
|
||||
image = [self.class createFrameAtIndex:0 source:_imageSource scale:scale preserveAspectRatio:_preserveAspectRatio thumbnailSize:_thumbnailSize lazyDecode:_lazyDecode animatedImage:NO];
|
||||
if (image) {
|
||||
image.sd_imageFormat = self.class.imageFormat;
|
||||
}
|
||||
|
@ -695,6 +699,12 @@ static CGImageRef __nullable SDCGImageCreateCopy(CGImageRef cg_nullable image) {
|
|||
preserveAspectRatio = preserveAspectRatioValue.boolValue;
|
||||
}
|
||||
_preserveAspectRatio = preserveAspectRatio;
|
||||
BOOL lazyDecode = NO; // Defaults NO for animated image coder
|
||||
NSNumber *lazyDecodeValue = options[SDImageCoderDecodeUseLazyDecoding];
|
||||
if (lazyDecodeValue != nil) {
|
||||
lazyDecode = lazyDecodeValue.boolValue;
|
||||
}
|
||||
_lazyDecode = lazyDecode;
|
||||
_imageSource = imageSource;
|
||||
_imageData = data;
|
||||
#if SD_UIKIT
|
||||
|
@ -785,24 +795,7 @@ static CGImageRef __nullable SDCGImageCreateCopy(CGImageRef cg_nullable image) {
|
|||
}
|
||||
|
||||
- (UIImage *)safeAnimatedImageFrameAtIndex:(NSUInteger)index {
|
||||
NSDictionary *options;
|
||||
BOOL lazyDecode = NO; // Defaults NO for animated image coder
|
||||
NSNumber *lazyDecodeValue = options[SDImageCoderDecodeUseLazyDecoding];
|
||||
if (lazyDecodeValue != nil) {
|
||||
lazyDecode = lazyDecodeValue.boolValue;
|
||||
}
|
||||
if (!lazyDecode) {
|
||||
options = @{
|
||||
(__bridge NSString *)kCGImageSourceShouldCacheImmediately : @(NO),
|
||||
(__bridge NSString *)kCGImageSourceShouldCache : @(NO)
|
||||
};
|
||||
} else {
|
||||
options = @{
|
||||
(__bridge NSString *)kCGImageSourceShouldCacheImmediately : @(YES),
|
||||
(__bridge NSString *)kCGImageSourceShouldCache : @(YES) // Always cache to reduce CPU usage
|
||||
};
|
||||
}
|
||||
UIImage *image = [self.class createFrameAtIndex:index source:_imageSource scale:_scale preserveAspectRatio:_preserveAspectRatio thumbnailSize:_thumbnailSize lazyDecode:lazyDecode options:options];
|
||||
UIImage *image = [self.class createFrameAtIndex:index source:_imageSource scale:_scale preserveAspectRatio:_preserveAspectRatio thumbnailSize:_thumbnailSize lazyDecode:_lazyDecode animatedImage:YES];
|
||||
if (!image) {
|
||||
return nil;
|
||||
}
|
||||
|
|
|
@ -211,7 +211,7 @@ static NSString * kSDCGImageDestinationRequestedFileSize = @"kCGImageDestination
|
|||
CFStringRef uttype = CGImageSourceGetType(source);
|
||||
SDImageFormat imageFormat = [NSData sd_imageFormatFromUTType:uttype];
|
||||
|
||||
UIImage *image = [SDImageIOAnimatedCoder createFrameAtIndex:0 source:source scale:scale preserveAspectRatio:preserveAspectRatio thumbnailSize:thumbnailSize lazyDecode:lazyDecode options:nil];
|
||||
UIImage *image = [SDImageIOAnimatedCoder createFrameAtIndex:0 source:source scale:scale preserveAspectRatio:preserveAspectRatio thumbnailSize:thumbnailSize lazyDecode:lazyDecode animatedImage:NO];
|
||||
CFRelease(source);
|
||||
|
||||
image.sd_imageFormat = imageFormat;
|
||||
|
@ -306,7 +306,7 @@ static NSString * kSDCGImageDestinationRequestedFileSize = @"kCGImageDestination
|
|||
if (scaleFactor != nil) {
|
||||
scale = MAX([scaleFactor doubleValue], 1);
|
||||
}
|
||||
image = [SDImageIOAnimatedCoder createFrameAtIndex:0 source:_imageSource scale:scale preserveAspectRatio:_preserveAspectRatio thumbnailSize:_thumbnailSize lazyDecode:_lazyDecode options:nil];
|
||||
image = [SDImageIOAnimatedCoder createFrameAtIndex:0 source:_imageSource scale:scale preserveAspectRatio:_preserveAspectRatio thumbnailSize:_thumbnailSize lazyDecode:_lazyDecode animatedImage:NO];
|
||||
if (image) {
|
||||
CFStringRef uttype = CGImageSourceGetType(_imageSource);
|
||||
image.sd_imageFormat = [NSData sd_imageFormatFromUTType:uttype];
|
||||
|
|
|
@ -32,7 +32,7 @@
|
|||
|
||||
+ (NSTimeInterval)frameDurationAtIndex:(NSUInteger)index source:(nonnull CGImageSourceRef)source;
|
||||
+ (NSUInteger)imageLoopCountWithSource:(nonnull CGImageSourceRef)source;
|
||||
+ (nullable UIImage *)createFrameAtIndex:(NSUInteger)index source:(nonnull CGImageSourceRef)source scale:(CGFloat)scale preserveAspectRatio:(BOOL)preserveAspectRatio thumbnailSize:(CGSize)thumbnailSize lazyDecode:(BOOL)lazyDecode options:(nullable NSDictionary *)options;
|
||||
+ (nullable UIImage *)createFrameAtIndex:(NSUInteger)index source:(nonnull CGImageSourceRef)source scale:(CGFloat)scale preserveAspectRatio:(BOOL)preserveAspectRatio thumbnailSize:(CGSize)thumbnailSize lazyDecode:(BOOL)lazyDecode animatedImage:(BOOL)animatedImage;
|
||||
+ (BOOL)canEncodeToFormat:(SDImageFormat)format;
|
||||
+ (BOOL)canDecodeFromFormat:(SDImageFormat)format;
|
||||
|
||||
|
|
|
@ -363,14 +363,7 @@
|
|||
SDImageCoderEncodeMaxPixelSize: @(thumbnailSize)
|
||||
}];
|
||||
UIImage *encodedImage = [UIImage sd_imageWithData:encodedData];
|
||||
// Encode keep aspect ratio, but will use scale down instead of scale up if we strip the image-io related info (to fix some Apple's bug)
|
||||
// See more in `SDCGImageCreateCopy`
|
||||
expect(image.sd_isDecoded).beFalsy();
|
||||
if (@available(iOS 15, tvOS 15, *)) {
|
||||
expect(encodedImage.size).equal(CGSizeMake(4000, 2628));
|
||||
} else {
|
||||
expect(encodedImage.size).equal(CGSizeMake(4000, 2629));
|
||||
}
|
||||
expect(encodedImage.size).equal(CGSizeMake(4000, 2629));
|
||||
}
|
||||
|
||||
- (void)test24ThatScaleSizeCalculation {
|
||||
|
|
Loading…
Reference in New Issue