Skip to content

Commit 8be9f2c

Browse files
authored
Merge pull request SDWebImage#3461 from dreampiggy/bugfix/yuv420_jpeg_hevc
Fix the iOS 15+ force-decode hack break Apple's HEIF and JPEG YUV420 optimization
2 parents 7bc087b + d222140 commit 8be9f2c

File tree

4 files changed

+45
-59
lines changed

4 files changed

+45
-59
lines changed

SDWebImage/Core/SDImageIOAnimatedCoder.m

Lines changed: 41 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -183,12 +183,8 @@ + (NSUInteger)imageLoopCountWithSource:(CGImageSourceRef)source {
183183
}
184184

185185
+ (NSTimeInterval)frameDurationAtIndex:(NSUInteger)index source:(CGImageSourceRef)source {
186-
NSDictionary *options = @{
187-
(__bridge NSString *)kCGImageSourceShouldCacheImmediately : @(YES),
188-
(__bridge NSString *)kCGImageSourceShouldCache : @(YES) // Always cache to reduce CPU usage
189-
};
190186
NSTimeInterval frameDuration = 0.1;
191-
CFDictionaryRef cfFrameProperties = CGImageSourceCopyPropertiesAtIndex(source, index, (__bridge CFDictionaryRef)options);
187+
CFDictionaryRef cfFrameProperties = CGImageSourceCopyPropertiesAtIndex(source, index, NULL);
192188
if (!cfFrameProperties) {
193189
return frameDuration;
194190
}
@@ -218,10 +214,24 @@ + (NSTimeInterval)frameDurationAtIndex:(NSUInteger)index source:(CGImageSourceRe
218214
return frameDuration;
219215
}
220216

221-
+ (UIImage *)createFrameAtIndex:(NSUInteger)index source:(CGImageSourceRef)source scale:(CGFloat)scale preserveAspectRatio:(BOOL)preserveAspectRatio thumbnailSize:(CGSize)thumbnailSize lazyDecode:(BOOL)lazyDecode options:(NSDictionary *)options {
222-
// Some options need to pass to `CGImageSourceCopyPropertiesAtIndex` before `CGImageSourceCreateImageAtIndex`, or ImageIO will ignore them because they parse once :)
217+
+ (UIImage *)createFrameAtIndex:(NSUInteger)index source:(CGImageSourceRef)source scale:(CGFloat)scale preserveAspectRatio:(BOOL)preserveAspectRatio thumbnailSize:(CGSize)thumbnailSize lazyDecode:(BOOL)lazyDecode animatedImage:(BOOL)animatedImage {
218+
// `animatedImage` means called from `SDAnimatedImageProvider.animatedImageFrameAtIndex`
219+
NSDictionary *options;
220+
if (animatedImage) {
221+
if (!lazyDecode) {
222+
options = @{
223+
// image decoding and caching should happen at image creation time.
224+
(__bridge NSString *)kCGImageSourceShouldCacheImmediately : @(YES),
225+
};
226+
} else {
227+
options = @{
228+
// image decoding will happen at rendering time
229+
(__bridge NSString *)kCGImageSourceShouldCacheImmediately : @(NO),
230+
};
231+
}
232+
}
223233
// Parse the image properties
224-
NSDictionary *properties = (__bridge_transfer NSDictionary *)CGImageSourceCopyPropertiesAtIndex(source, index, (__bridge CFDictionaryRef)options);
234+
NSDictionary *properties = (__bridge_transfer NSDictionary *)CGImageSourceCopyPropertiesAtIndex(source, index, NULL);
225235
CGFloat pixelWidth = [properties[(__bridge NSString *)kCGImagePropertyPixelWidth] doubleValue];
226236
CGFloat pixelHeight = [properties[(__bridge NSString *)kCGImagePropertyPixelHeight] doubleValue];
227237
CGImagePropertyOrientation exifOrientation = (CGImagePropertyOrientation)[properties[(__bridge NSString *)kCGImagePropertyOrientation] unsignedIntegerValue];
@@ -287,7 +297,7 @@ + (UIImage *)createFrameAtIndex:(NSUInteger)index source:(CGImageSourceRef)sourc
287297
isDecoded = YES;
288298
}
289299
}
290-
} else {
300+
} else if (animatedImage) {
291301
// 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
292302
if (@available(iOS 15, tvOS 15, *)) {
293303
// User pass `lazyDecode == YES`, but we still have to strip the CGImageSourceRef
@@ -297,21 +307,19 @@ + (UIImage *)createFrameAtIndex:(NSUInteger)index source:(CGImageSourceRef)sourc
297307
CGImageRelease(imageRef);
298308
imageRef = newImageRef;
299309
}
300-
}
301-
}
302310
#if SD_CHECK_CGIMAGE_RETAIN_SOURCE
303-
if (@available(iOS 15, tvOS 15, *)) {
304-
// Assert here to check CGImageRef should not retain the CGImageSourceRef and has possible thread-safe issue (this is behavior on iOS 15+)
305-
// If assert hit, fire issue to https://github.com/SDWebImage/SDWebImage/issues and we update the condition for this behavior check
306-
static dispatch_once_t onceToken;
307-
dispatch_once(&onceToken, ^{
308-
SDCGImageGetImageSource = dlsym(RTLD_DEFAULT, "CGImageGetImageSource");
309-
});
310-
if (SDCGImageGetImageSource) {
311-
NSCAssert(!SDCGImageGetImageSource(imageRef), @"Animated Coder created CGImageRef should not retain CGImageSourceRef, which may cause thread-safe issue without lock");
311+
// Assert here to check CGImageRef should not retain the CGImageSourceRef and has possible thread-safe issue (this is behavior on iOS 15+)
312+
// If assert hit, fire issue to https://github.com/SDWebImage/SDWebImage/issues and we update the condition for this behavior check
313+
static dispatch_once_t onceToken;
314+
dispatch_once(&onceToken, ^{
315+
SDCGImageGetImageSource = dlsym(RTLD_DEFAULT, "CGImageGetImageSource");
316+
});
317+
if (SDCGImageGetImageSource) {
318+
NSCAssert(!SDCGImageGetImageSource(imageRef), @"Animated Coder created CGImageRef should not retain CGImageSourceRef, which may cause thread-safe issue without lock");
319+
}
320+
#endif
312321
}
313322
}
314-
#endif
315323

316324
#if SD_UIKIT || SD_WATCH
317325
UIImageOrientation imageOrientation = [SDImageCoderHelper imageOrientationFromEXIFOrientation:exifOrientation];
@@ -412,12 +420,12 @@ - (UIImage *)decodedImageWithData:(NSData *)data options:(nullable SDImageCoderO
412420

413421
BOOL decodeFirstFrame = [options[SDImageCoderDecodeFirstFrameOnly] boolValue];
414422
if (decodeFirstFrame || count <= 1) {
415-
animatedImage = [self.class createFrameAtIndex:0 source:source scale:scale preserveAspectRatio:preserveAspectRatio thumbnailSize:thumbnailSize lazyDecode:lazyDecode options:nil];
423+
animatedImage = [self.class createFrameAtIndex:0 source:source scale:scale preserveAspectRatio:preserveAspectRatio thumbnailSize:thumbnailSize lazyDecode:lazyDecode animatedImage:NO];
416424
} else {
417425
NSMutableArray<SDImageFrame *> *frames = [NSMutableArray arrayWithCapacity:count];
418426

419427
for (size_t i = 0; i < count; i++) {
420-
UIImage *image = [self.class createFrameAtIndex:i source:source scale:scale preserveAspectRatio:preserveAspectRatio thumbnailSize:thumbnailSize lazyDecode:lazyDecode options:nil];
428+
UIImage *image = [self.class createFrameAtIndex:i source:source scale:scale preserveAspectRatio:preserveAspectRatio thumbnailSize:thumbnailSize lazyDecode:lazyDecode animatedImage:NO];
421429
if (!image) {
422430
continue;
423431
}
@@ -473,7 +481,7 @@ - (instancetype)initIncrementalWithOptions:(nullable SDImageCoderOptions *)optio
473481
preserveAspectRatio = preserveAspectRatioValue.boolValue;
474482
}
475483
_preserveAspectRatio = preserveAspectRatio;
476-
BOOL lazyDecode = YES; // Defaults YES for static image coder
484+
BOOL lazyDecode = NO; // Defaults NO for animated image coder
477485
NSNumber *lazyDecodeValue = options[SDImageCoderDecodeUseLazyDecoding];
478486
if (lazyDecodeValue != nil) {
479487
lazyDecode = lazyDecodeValue.boolValue;
@@ -502,11 +510,7 @@ - (void)updateIncrementalData:(NSData *)data finished:(BOOL)finished {
502510
CGImageSourceUpdateData(_imageSource, (__bridge CFDataRef)data, finished);
503511

504512
if (_width + _height == 0) {
505-
NSDictionary *options = @{
506-
(__bridge NSString *)kCGImageSourceShouldCacheImmediately : @(YES),
507-
(__bridge NSString *)kCGImageSourceShouldCache : @(YES) // Always cache to reduce CPU usage
508-
};
509-
CFDictionaryRef properties = CGImageSourceCopyPropertiesAtIndex(_imageSource, 0, (__bridge CFDictionaryRef)options);
513+
CFDictionaryRef properties = CGImageSourceCopyPropertiesAtIndex(_imageSource, 0, NULL);
510514
if (properties) {
511515
CFTypeRef val = CFDictionaryGetValue(properties, kCGImagePropertyPixelHeight);
512516
if (val) CFNumberGetValue(val, kCFNumberLongType, &_height);
@@ -533,7 +537,7 @@ - (UIImage *)incrementalDecodedImageWithOptions:(SDImageCoderOptions *)options {
533537
if (scaleFactor != nil) {
534538
scale = MAX([scaleFactor doubleValue], 1);
535539
}
536-
image = [self.class createFrameAtIndex:0 source:_imageSource scale:scale preserveAspectRatio:_preserveAspectRatio thumbnailSize:_thumbnailSize lazyDecode:_lazyDecode options:nil];
540+
image = [self.class createFrameAtIndex:0 source:_imageSource scale:scale preserveAspectRatio:_preserveAspectRatio thumbnailSize:_thumbnailSize lazyDecode:_lazyDecode animatedImage:NO];
537541
if (image) {
538542
image.sd_imageFormat = self.class.imageFormat;
539543
}
@@ -695,6 +699,12 @@ - (nullable instancetype)initWithAnimatedImageData:(nullable NSData *)data optio
695699
preserveAspectRatio = preserveAspectRatioValue.boolValue;
696700
}
697701
_preserveAspectRatio = preserveAspectRatio;
702+
BOOL lazyDecode = NO; // Defaults NO for animated image coder
703+
NSNumber *lazyDecodeValue = options[SDImageCoderDecodeUseLazyDecoding];
704+
if (lazyDecodeValue != nil) {
705+
lazyDecode = lazyDecodeValue.boolValue;
706+
}
707+
_lazyDecode = lazyDecode;
698708
_imageSource = imageSource;
699709
_imageData = data;
700710
#if SD_UIKIT
@@ -785,24 +795,7 @@ - (UIImage *)animatedImageFrameAtIndex:(NSUInteger)index {
785795
}
786796

787797
- (UIImage *)safeAnimatedImageFrameAtIndex:(NSUInteger)index {
788-
NSDictionary *options;
789-
BOOL lazyDecode = NO; // Defaults NO for animated image coder
790-
NSNumber *lazyDecodeValue = options[SDImageCoderDecodeUseLazyDecoding];
791-
if (lazyDecodeValue != nil) {
792-
lazyDecode = lazyDecodeValue.boolValue;
793-
}
794-
if (!lazyDecode) {
795-
options = @{
796-
(__bridge NSString *)kCGImageSourceShouldCacheImmediately : @(NO),
797-
(__bridge NSString *)kCGImageSourceShouldCache : @(NO)
798-
};
799-
} else {
800-
options = @{
801-
(__bridge NSString *)kCGImageSourceShouldCacheImmediately : @(YES),
802-
(__bridge NSString *)kCGImageSourceShouldCache : @(YES) // Always cache to reduce CPU usage
803-
};
804-
}
805-
UIImage *image = [self.class createFrameAtIndex:index source:_imageSource scale:_scale preserveAspectRatio:_preserveAspectRatio thumbnailSize:_thumbnailSize lazyDecode:lazyDecode options:options];
798+
UIImage *image = [self.class createFrameAtIndex:index source:_imageSource scale:_scale preserveAspectRatio:_preserveAspectRatio thumbnailSize:_thumbnailSize lazyDecode:_lazyDecode animatedImage:YES];
806799
if (!image) {
807800
return nil;
808801
}

SDWebImage/Core/SDImageIOCoder.m

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,7 @@ - (UIImage *)decodedImageWithData:(NSData *)data options:(nullable SDImageCoderO
211211
CFStringRef uttype = CGImageSourceGetType(source);
212212
SDImageFormat imageFormat = [NSData sd_imageFormatFromUTType:uttype];
213213

214-
UIImage *image = [SDImageIOAnimatedCoder createFrameAtIndex:0 source:source scale:scale preserveAspectRatio:preserveAspectRatio thumbnailSize:thumbnailSize lazyDecode:lazyDecode options:nil];
214+
UIImage *image = [SDImageIOAnimatedCoder createFrameAtIndex:0 source:source scale:scale preserveAspectRatio:preserveAspectRatio thumbnailSize:thumbnailSize lazyDecode:lazyDecode animatedImage:NO];
215215
CFRelease(source);
216216

217217
image.sd_imageFormat = imageFormat;
@@ -306,7 +306,7 @@ - (UIImage *)incrementalDecodedImageWithOptions:(SDImageCoderOptions *)options {
306306
if (scaleFactor != nil) {
307307
scale = MAX([scaleFactor doubleValue], 1);
308308
}
309-
image = [SDImageIOAnimatedCoder createFrameAtIndex:0 source:_imageSource scale:scale preserveAspectRatio:_preserveAspectRatio thumbnailSize:_thumbnailSize lazyDecode:_lazyDecode options:nil];
309+
image = [SDImageIOAnimatedCoder createFrameAtIndex:0 source:_imageSource scale:scale preserveAspectRatio:_preserveAspectRatio thumbnailSize:_thumbnailSize lazyDecode:_lazyDecode animatedImage:NO];
310310
if (image) {
311311
CFStringRef uttype = CGImageSourceGetType(_imageSource);
312312
image.sd_imageFormat = [NSData sd_imageFormatFromUTType:uttype];

SDWebImage/Private/SDImageIOAnimatedCoderInternal.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232

3333
+ (NSTimeInterval)frameDurationAtIndex:(NSUInteger)index source:(nonnull CGImageSourceRef)source;
3434
+ (NSUInteger)imageLoopCountWithSource:(nonnull CGImageSourceRef)source;
35-
+ (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;
35+
+ (nullable UIImage *)createFrameAtIndex:(NSUInteger)index source:(nonnull CGImageSourceRef)source scale:(CGFloat)scale preserveAspectRatio:(BOOL)preserveAspectRatio thumbnailSize:(CGSize)thumbnailSize lazyDecode:(BOOL)lazyDecode animatedImage:(BOOL)animatedImage;
3636
+ (BOOL)canEncodeToFormat:(SDImageFormat)format;
3737
+ (BOOL)canDecodeFromFormat:(SDImageFormat)format;
3838

Tests/Tests/SDImageCoderTests.m

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -363,14 +363,7 @@ - (void)test23ThatThumbnailEncodeCalculation {
363363
SDImageCoderEncodeMaxPixelSize: @(thumbnailSize)
364364
}];
365365
UIImage *encodedImage = [UIImage sd_imageWithData:encodedData];
366-
// 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)
367-
// See more in `SDCGImageCreateCopy`
368-
expect(image.sd_isDecoded).beFalsy();
369-
if (@available(iOS 15, tvOS 15, *)) {
370-
expect(encodedImage.size).equal(CGSizeMake(4000, 2628));
371-
} else {
372-
expect(encodedImage.size).equal(CGSizeMake(4000, 2629));
373-
}
366+
expect(encodedImage.size).equal(CGSizeMake(4000, 2629));
374367
}
375368

376369
- (void)test24ThatScaleSizeCalculation {

0 commit comments

Comments
 (0)