|
|
@@ -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;
|