Просмотр исходного кода

Update to force decode CGImage before any UIImage initialization on iOS 15+

DreamPiggy 3 лет назад
Родитель
Сommit
89178a7d89

+ 33 - 17
SDWebImage/Core/SDImageIOAnimatedCoder.m

@@ -14,6 +14,12 @@
 #import "SDAnimatedImageRep.h"
 #import "UIImage+ForceDecode.h"
 
+#define SD_CHECK_CGIMAGE_RETAIN_SOURCE TARGET_OS_MACCATALYST ||\
+    (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_15_0) ||\
+    (__MAC_OS_X_VERSION_MAX_ALLOWED >= __MAC_12_0) ||\
+    (__TV_OS_VERSION_MAX_ALLOWED >= __TVOS_15_0) ||\
+    (__WATCH_OS_VERSION_MAX_ALLOWED >= __WATCHOS_8_0)
+
 // Specify DPI for vector format in CGImageSource, like PDF
 static NSString * kSDCGImageSourceRasterizationDPI = @"kCGImageSourceRasterizationDPI";
 // Specify File Size for lossy format encoding, like JPEG
@@ -187,7 +193,7 @@ static NSString * kSDCGImageDestinationRequestedFileSize = @"kCGImageDestination
     return frameDuration;
 }
 
-+ (UIImage *)createFrameAtIndex:(NSUInteger)index source:(CGImageSourceRef)source scale:(CGFloat)scale preserveAspectRatio:(BOOL)preserveAspectRatio thumbnailSize:(CGSize)thumbnailSize options:(NSDictionary *)options {
++ (UIImage *)createFrameAtIndex:(NSUInteger)index source:(CGImageSourceRef)source scale:(CGFloat)scale preserveAspectRatio:(BOOL)preserveAspectRatio thumbnailSize:(CGSize)thumbnailSize forceDecode:(BOOL)forceDecode options:(NSDictionary *)options {
     // Some options need to pass to `CGImageSourceCopyPropertiesAtIndex` before `CGImageSourceCreateImageAtIndex`, or ImageIO will ignore them because they parse once :)
     // Parse the image properties
     NSDictionary *properties = (__bridge_transfer NSDictionary *)CGImageSourceCopyPropertiesAtIndex(source, index, (__bridge CFDictionaryRef)options);
@@ -229,6 +235,7 @@ static NSString * kSDCGImageDestinationRequestedFileSize = @"kCGImageDestination
     if (!imageRef) {
         return nil;
     }
+    BOOL isDecoded = NO;
     // Thumbnail image post-process
     if (!createFullImage) {
         if (preserveAspectRatio) {
@@ -239,8 +246,26 @@ static NSString * kSDCGImageDestinationRequestedFileSize = @"kCGImageDestination
             CGImageRef scaledImageRef = [SDImageCoderHelper CGImageCreateScaled:imageRef size:thumbnailSize];
             CGImageRelease(imageRef);
             imageRef = scaledImageRef;
+            isDecoded = YES;
         }
     }
+    // Check whether output CGImage is decoded
+    if (forceDecode) {
+        if (!isDecoded) {
+            // Use CoreGraphics to trigger immediately decode
+            CGImageRef decodedImageRef = [SDImageCoderHelper CGImageCreateDecoded:imageRef];
+//            CGImageRef decodedImageRef = CGImageCreate(CGImageGetWidth(imageRef), CGImageGetHeight(imageRef), CGImageGetBitsPerComponent(imageRef), CGImageGetBitsPerPixel(imageRef), CGImageGetBytesPerRow(imageRef), CGImageGetColorSpace(imageRef), CGImageGetBitmapInfo(imageRef), CGImageGetDataProvider(imageRef), NULL, CGImageGetShouldInterpolate(imageRef), CGImageGetRenderingIntent(imageRef));
+            CGImageRelease(imageRef);
+            imageRef = decodedImageRef;
+            isDecoded = YES;
+        }
+#if DEBUG && SD_CHECK_CGIMAGE_RETAIN_SOURCE
+        // 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
+        extern CGImageSourceRef CGImageGetImageSource(CGImageRef);
+        NSCAssert(!CGImageGetImageSource(imageRef), @"Animated Coder created CGImageRef should not retain CGImageSourceRef, which may cause thread-safe issue without lock");
+#endif
+    }
     
 #if SD_UIKIT || SD_WATCH
     UIImageOrientation imageOrientation = [SDImageCoderHelper imageOrientationFromEXIFOrientation:exifOrientation];
@@ -308,12 +333,12 @@ static NSString * kSDCGImageDestinationRequestedFileSize = @"kCGImageDestination
     
     BOOL decodeFirstFrame = [options[SDImageCoderDecodeFirstFrameOnly] boolValue];
     if (decodeFirstFrame || count <= 1) {
-        animatedImage = [self.class createFrameAtIndex:0 source:source scale:scale preserveAspectRatio:preserveAspectRatio thumbnailSize:thumbnailSize options:nil];
+        animatedImage = [self.class createFrameAtIndex:0 source:source scale:scale preserveAspectRatio:preserveAspectRatio thumbnailSize:thumbnailSize forceDecode:NO options:nil];
     } else {
         NSMutableArray<SDImageFrame *> *frames = [NSMutableArray array];
         
         for (size_t i = 0; i < count; i++) {
-            UIImage *image = [self.class createFrameAtIndex:i source:source scale:scale preserveAspectRatio:preserveAspectRatio thumbnailSize:thumbnailSize options:nil];
+            UIImage *image = [self.class createFrameAtIndex:i source:source scale:scale preserveAspectRatio:preserveAspectRatio thumbnailSize:thumbnailSize forceDecode:NO options:nil];
             if (!image) {
                 continue;
             }
@@ -417,7 +442,7 @@ static NSString * kSDCGImageDestinationRequestedFileSize = @"kCGImageDestination
         if (scaleFactor != nil) {
             scale = MAX([scaleFactor doubleValue], 1);
         }
-        image = [self.class createFrameAtIndex:0 source:_imageSource scale:scale preserveAspectRatio:_preserveAspectRatio thumbnailSize:_thumbnailSize options:nil];
+        image = [self.class createFrameAtIndex:0 source:_imageSource scale:scale preserveAspectRatio:_preserveAspectRatio thumbnailSize:_thumbnailSize forceDecode:NO options:nil];
         if (image) {
             image.sd_imageFormat = self.class.imageFormat;
         }
@@ -628,35 +653,26 @@ static NSString * kSDCGImageDestinationRequestedFileSize = @"kCGImageDestination
         return nil;
     }
     NSDictionary *options;
-    BOOL isDecoded = YES;
+    BOOL forceDecode = NO;
     if (@available(iOS 15, tvOS 15, *)) {
         // 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
-        isDecoded = NO;
+        forceDecode = YES;
         options = @{
             (__bridge NSString *)kCGImageSourceShouldCacheImmediately : @(NO),
             (__bridge NSString *)kCGImageSourceShouldCache : @(NO)
         };
     } else {
         // Animated Image should not use the CGContext solution to force decode on lower firmware. Prefers to use Image/IO built in method, which is safer and memory friendly, see https://github.com/SDWebImage/SDWebImage/issues/2961
-        isDecoded = YES;
+        forceDecode = NO;
         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 options:options];
+    UIImage *image = [self.class createFrameAtIndex:index source:_imageSource scale:_scale preserveAspectRatio:_preserveAspectRatio thumbnailSize:_thumbnailSize forceDecode:forceDecode options:options];
     if (!image) {
         return nil;
     }
-    if (!isDecoded) {
-        image = [SDImageCoderHelper decodedImageWithImage:image];
-#if DEBUG
-        // 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
-        extern CGImageSourceRef CGImageGetImageSource(CGImageRef);
-        NSCAssert(!CGImageGetImageSource(image.CGImage), @"Animated Coder created CGImageRef should not retain CGImageSourceRef, which may cause thread-safe issue without lock");
-#endif
-    }
     image.sd_imageFormat = self.class.imageFormat;
     image.sd_isDecoded = YES;
     return image;

+ 2 - 2
SDWebImage/Core/SDImageIOCoder.m

@@ -141,7 +141,7 @@ static NSString * kSDCGImageDestinationRequestedFileSize = @"kCGImageDestination
         thumbnailSize = CGSizeZero;
     }
     
-    UIImage *image = [SDImageIOAnimatedCoder createFrameAtIndex:0 source:source scale:scale preserveAspectRatio:preserveAspectRatio thumbnailSize:thumbnailSize options:decodingOptions];
+    UIImage *image = [SDImageIOAnimatedCoder createFrameAtIndex:0 source:source scale:scale preserveAspectRatio:preserveAspectRatio thumbnailSize:thumbnailSize forceDecode:NO options:decodingOptions];
     CFRelease(source);
     if (!image) {
         return nil;
@@ -233,7 +233,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 options:nil];
+        image = [SDImageIOAnimatedCoder createFrameAtIndex:0 source:_imageSource scale:scale preserveAspectRatio:_preserveAspectRatio thumbnailSize:_thumbnailSize forceDecode:NO options:nil];
         if (image) {
             CFStringRef uttype = CGImageSourceGetType(_imageSource);
             image.sd_imageFormat = [NSData sd_imageFormatFromUTType:uttype];

+ 1 - 1
SDWebImage/Private/SDImageIOAnimatedCoderInternal.h

@@ -29,7 +29,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 options:(nullable NSDictionary *)options;
++ (nullable UIImage *)createFrameAtIndex:(NSUInteger)index source:(nonnull CGImageSourceRef)source scale:(CGFloat)scale preserveAspectRatio:(BOOL)preserveAspectRatio thumbnailSize:(CGSize)thumbnailSize forceDecode:(BOOL)forceDecode options:(nullable NSDictionary *)options;
 + (BOOL)canEncodeToFormat:(SDImageFormat)format;
 + (BOOL)canDecodeFromFormat:(SDImageFormat)format;