From 6a2eaf707e82bb3adb16884b799906600096f85d Mon Sep 17 00:00:00 2001 From: Inso Date: Mon, 28 Mar 2022 12:41:22 +0800 Subject: [PATCH 1/6] feat: store image data --- SDWebImage/Core/SDImageCache.h | 25 +++++++++++++++++++++++++ SDWebImage/Core/SDImageCache.m | 17 ++++++++++++++++- 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/SDWebImage/Core/SDImageCache.h b/SDWebImage/Core/SDImageCache.h index ad3afd53..926fe78d 100644 --- a/SDWebImage/Core/SDImageCache.h +++ b/SDWebImage/Core/SDImageCache.h @@ -179,6 +179,31 @@ typedef NS_OPTIONS(NSUInteger, SDImageCacheOptions) { toDisk:(BOOL)toDisk completion:(nullable SDWebImageNoParamsBlock)completionBlock; +/** + * Asynchronously store an image data into memory and disk cache at the given key. + * + * @param imageData The image data to store + * @param key The unique image cache key, usually it's image absolute URL + * @param completionBlock A block executed after the operation is finished + */ +- (void)storeImageData:(nullable NSData *)imageData + forKey:(nullable NSString *)key + completion:(nullable SDWebImageNoParamsBlock)completionBlock; + +/** + * Asynchronously store an image data into memory and disk cache at the given key. + * + * @param imageData The image data to store + * @param key The unique image cache key, usually it's image absolute URL + * @param toDisk Store the image to disk cache if YES. If NO, the completion block is called synchronously + * @param completionBlock A block executed after the operation is finished + * @note If no image data is provided and encode to disk, we will try to detect the image format (using either `sd_imageFormat` or `SDAnimatedImage` protocol method) and animation status, to choose the best matched format, including GIF, JPEG or PNG. + */ +- (void)storeImageData:(nullable NSData *)imageData + forKey:(nullable NSString *)key + toDisk:(BOOL)toDisk + completion:(nullable SDWebImageNoParamsBlock)completionBlock; + /** * Asynchronously store an image into memory and disk cache at the given key. * diff --git a/SDWebImage/Core/SDImageCache.m b/SDWebImage/Core/SDImageCache.m index 635908b3..367ff5c0 100644 --- a/SDWebImage/Core/SDImageCache.m +++ b/SDWebImage/Core/SDImageCache.m @@ -168,6 +168,17 @@ static NSString * _defaultDiskCacheDirectory; [self storeImage:image imageData:nil forKey:key toDisk:toDisk completion:completionBlock]; } +- (void)storeImageData:(NSData *)imageData + forKey:(NSString *)key + completion:(SDWebImageNoParamsBlock)completionBlock { + [self storeImageData:imageData forKey:key toDisk:YES completion:completionBlock]; +} + +- (void)storeImageData:(NSData *)imageData forKey:(NSString *)key toDisk:(BOOL)toDisk completion:(SDWebImageNoParamsBlock)completionBlock { + [self storeImage:nil imageData:imageData forKey:key toDisk:toDisk completion:completionBlock]; +} + + - (void)storeImage:(nullable UIImage *)image imageData:(nullable NSData *)imageData forKey:(nullable NSString *)key @@ -182,7 +193,7 @@ static NSString * _defaultDiskCacheDirectory; toMemory:(BOOL)toMemory toDisk:(BOOL)toDisk completion:(nullable SDWebImageNoParamsBlock)completionBlock { - if (!image || !key) { + if ((!image && !imageData) || !key) { if (completionBlock) { completionBlock(); } @@ -191,6 +202,10 @@ static NSString * _defaultDiskCacheDirectory; // if memory cache is enabled if (toMemory && self.config.shouldCacheImagesInMemory) { NSUInteger cost = image.sd_memoryCost; + if (!image && imageData) { + cost = imageData.length; + image = [UIImage imageWithData:imageData]; + } [self.memoryCache setObject:image forKey:key cost:cost]; } From 7c66265ee82fdffe72c851395f8d61211f02bdc6 Mon Sep 17 00:00:00 2001 From: Inso Date: Wed, 30 Mar 2022 15:24:32 +0800 Subject: [PATCH 2/6] refactor: refactor store data to memory cache logic --- SDWebImage/Core/SDImageCache.m | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/SDWebImage/Core/SDImageCache.m b/SDWebImage/Core/SDImageCache.m index 367ff5c0..9934d1c5 100644 --- a/SDWebImage/Core/SDImageCache.m +++ b/SDWebImage/Core/SDImageCache.m @@ -201,12 +201,10 @@ static NSString * _defaultDiskCacheDirectory; } // if memory cache is enabled if (toMemory && self.config.shouldCacheImagesInMemory) { - NSUInteger cost = image.sd_memoryCost; - if (!image && imageData) { - cost = imageData.length; - image = [UIImage imageWithData:imageData]; + if (image) { + NSUInteger cost = image.sd_memoryCost; + [self.memoryCache setObject:image forKey:key cost:cost]; } - [self.memoryCache setObject:image forKey:key cost:cost]; } if (!toDisk) { From 2fc131c44bf8af283561dcb0b77d42d96b8ea28b Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Sun, 12 Jun 2022 17:03:04 +0800 Subject: [PATCH 3/6] Update SDImageCache.h Remove extra toDisk param --- SDWebImage/Core/SDImageCache.h | 24 +++++------------------- 1 file changed, 5 insertions(+), 19 deletions(-) diff --git a/SDWebImage/Core/SDImageCache.h b/SDWebImage/Core/SDImageCache.h index 926fe78d..e8b2ee73 100644 --- a/SDWebImage/Core/SDImageCache.h +++ b/SDWebImage/Core/SDImageCache.h @@ -180,29 +180,15 @@ typedef NS_OPTIONS(NSUInteger, SDImageCacheOptions) { completion:(nullable SDWebImageNoParamsBlock)completionBlock; /** - * Asynchronously store an image data into memory and disk cache at the given key. + * Asynchronously store an image data into disk cache at the given key. * * @param imageData The image data to store * @param key The unique image cache key, usually it's image absolute URL * @param completionBlock A block executed after the operation is finished */ - (void)storeImageData:(nullable NSData *)imageData - forKey:(nullable NSString *)key - completion:(nullable SDWebImageNoParamsBlock)completionBlock; - -/** - * Asynchronously store an image data into memory and disk cache at the given key. - * - * @param imageData The image data to store - * @param key The unique image cache key, usually it's image absolute URL - * @param toDisk Store the image to disk cache if YES. If NO, the completion block is called synchronously - * @param completionBlock A block executed after the operation is finished - * @note If no image data is provided and encode to disk, we will try to detect the image format (using either `sd_imageFormat` or `SDAnimatedImage` protocol method) and animation status, to choose the best matched format, including GIF, JPEG or PNG. - */ -- (void)storeImageData:(nullable NSData *)imageData - forKey:(nullable NSString *)key - toDisk:(BOOL)toDisk - completion:(nullable SDWebImageNoParamsBlock)completionBlock; + forKey:(nullable NSString *)key + completion:(nullable SDWebImageNoParamsBlock)completionBlock; /** * Asynchronously store an image into memory and disk cache at the given key. @@ -223,7 +209,7 @@ typedef NS_OPTIONS(NSUInteger, SDImageCacheOptions) { completion:(nullable SDWebImageNoParamsBlock)completionBlock; /** - * Synchronously store image into memory cache at the given key. + * Synchronously store an image into memory cache at the given key. * * @param image The image to store * @param key The unique image cache key, usually it's image absolute URL @@ -232,7 +218,7 @@ typedef NS_OPTIONS(NSUInteger, SDImageCacheOptions) { forKey:(nullable NSString *)key; /** - * Synchronously store image data into disk cache at the given key. + * Synchronously store an image data into disk cache at the given key. * * @param imageData The image data to store * @param key The unique image cache key, usually it's image absolute URL From 754de4d7c8b07d39dcd8e8886f9547e53e370254 Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Sun, 12 Jun 2022 17:04:52 +0800 Subject: [PATCH 4/6] Update SDImageCache.m Remove extra toDisk param --- SDWebImage/Core/SDImageCache.m | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/SDWebImage/Core/SDImageCache.m b/SDWebImage/Core/SDImageCache.m index 9934d1c5..8b044c7c 100644 --- a/SDWebImage/Core/SDImageCache.m +++ b/SDWebImage/Core/SDImageCache.m @@ -168,17 +168,12 @@ static NSString * _defaultDiskCacheDirectory; [self storeImage:image imageData:nil forKey:key toDisk:toDisk completion:completionBlock]; } -- (void)storeImageData:(NSData *)imageData - forKey:(NSString *)key - completion:(SDWebImageNoParamsBlock)completionBlock { - [self storeImageData:imageData forKey:key toDisk:YES completion:completionBlock]; +- (void)storeImageData:(nullable NSData *)imageData + forKey:(nullable NSString *)key + completion:(nullable SDWebImageNoParamsBlock)completionBlock { + [self storeImage:nil imageData:imageData forKey:key toDisk:YES completion:completionBlock]; } -- (void)storeImageData:(NSData *)imageData forKey:(NSString *)key toDisk:(BOOL)toDisk completion:(SDWebImageNoParamsBlock)completionBlock { - [self storeImage:nil imageData:imageData forKey:key toDisk:toDisk completion:completionBlock]; -} - - - (void)storeImage:(nullable UIImage *)image imageData:(nullable NSData *)imageData forKey:(nullable NSString *)key From a66972bdc830103cd556c465599dc704d8ec5053 Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Sun, 12 Jun 2022 17:10:23 +0800 Subject: [PATCH 5/6] Update SDImageCache.m Little logic check for nil value --- SDWebImage/Core/SDImageCache.m | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/SDWebImage/Core/SDImageCache.m b/SDWebImage/Core/SDImageCache.m index 8b044c7c..a083b6ac 100644 --- a/SDWebImage/Core/SDImageCache.m +++ b/SDWebImage/Core/SDImageCache.m @@ -195,11 +195,9 @@ static NSString * _defaultDiskCacheDirectory; return; } // if memory cache is enabled - if (toMemory && self.config.shouldCacheImagesInMemory) { - if (image) { - NSUInteger cost = image.sd_memoryCost; - [self.memoryCache setObject:image forKey:key cost:cost]; - } + if (image && toMemory && self.config.shouldCacheImagesInMemory) { + NSUInteger cost = image.sd_memoryCost; + [self.memoryCache setObject:image forKey:key cost:cost]; } if (!toDisk) { @@ -242,7 +240,7 @@ static NSString * _defaultDiskCacheDirectory; } - (void)_archivedDataWithImage:(UIImage *)image forKey:(NSString *)key { - if (!image) { + if (!image || !key) { return; } // Check extended data From 9b9fc6fc9bbd5bee2a431062ead34176eb34135c Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Sun, 12 Jun 2022 18:13:30 +0800 Subject: [PATCH 6/6] Support using iOS 15 UIImage new API imageByPreparingForDisplay for faster force decoding This API seems support both async/sync version, SD use sync version here because we already call them on global queue The API mark available on watchOS, but header does not pass that __has_include, a little strange --- SDWebImage/Core/SDImageCoderHelper.m | 79 +++++++++++++++++++--------- 1 file changed, 53 insertions(+), 26 deletions(-) diff --git a/SDWebImage/Core/SDImageCoderHelper.m b/SDWebImage/Core/SDImageCoderHelper.m index 1f641212..1980decc 100644 --- a/SDWebImage/Core/SDImageCoderHelper.m +++ b/SDWebImage/Core/SDImageCoderHelper.m @@ -330,6 +330,19 @@ static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to over return image; } +#if SD_UIKIT + // See: https://developer.apple.com/documentation/uikit/uiimage/3750834-imagebypreparingfordisplay + // Need CGImage-based + if (@available(iOS 15, tvOS 15, *)) { + UIImage *decodedImage = [image imageByPreparingForDisplay]; + if (decodedImage) { + SDImageCopyAssociatedObject(image, decodedImage); + decodedImage.sd_isDecoded = YES; + return decodedImage; + } + } +#endif + CGImageRef imageRef = image.CGImage; if (!imageRef) { return image; @@ -355,36 +368,54 @@ static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to over return image; } - if (![self shouldScaleDownImage:image limitBytes:bytes]) { - return [self decodedImageWithImage:image]; - } - CGFloat destTotalPixels; CGFloat tileTotalPixels; if (bytes == 0) { - bytes = kDestImageLimitBytes; + bytes = [self defaultScaleDownLimitBytes]; } + bytes = MAX(bytes, kBytesPerPixel); destTotalPixels = bytes / kBytesPerPixel; tileTotalPixels = destTotalPixels / 3; + + CGImageRef sourceImageRef = image.CGImage; + CGSize sourceResolution = CGSizeZero; + sourceResolution.width = CGImageGetWidth(sourceImageRef); + sourceResolution.height = CGImageGetHeight(sourceImageRef); + + if (![self shouldScaleDownImagePixelSize:sourceResolution limitBytes:bytes]) { + return [self decodedImageWithImage:image]; + } + + CGFloat sourceTotalPixels = sourceResolution.width * sourceResolution.height; + // Determine the scale ratio to apply to the input image + // that results in an output image of the defined size. + // see kDestImageSizeMB, and how it relates to destTotalPixels. + CGFloat imageScale = sqrt(destTotalPixels / sourceTotalPixels); + CGSize destResolution = CGSizeZero; + destResolution.width = MAX(1, (int)(sourceResolution.width * imageScale)); + destResolution.height = MAX(1, (int)(sourceResolution.height * imageScale)); + +#if SD_UIKIT + // See: https://developer.apple.com/documentation/uikit/uiimage/3750835-imagebypreparingthumbnailofsize + // Need CGImage-based + if (@available(iOS 15, tvOS 15, *)) { + // Calculate thumbnail point size + CGFloat scale = image.scale ?: 1; + CGSize thumbnailSize = CGSizeMake(destResolution.width / scale, destResolution.height / scale); + UIImage *decodedImage = [image imageByPreparingThumbnailOfSize:thumbnailSize]; + if (decodedImage) { + SDImageCopyAssociatedObject(image, decodedImage); + decodedImage.sd_isDecoded = YES; + return decodedImage; + } + } +#endif + CGContextRef destContext = NULL; // autorelease the bitmap context and all vars to help system to free memory when there are memory warning. // on iOS7, do not forget to call [[SDImageCache sharedImageCache] clearMemory]; @autoreleasepool { - CGImageRef sourceImageRef = image.CGImage; - - CGSize sourceResolution = CGSizeZero; - sourceResolution.width = CGImageGetWidth(sourceImageRef); - sourceResolution.height = CGImageGetHeight(sourceImageRef); - CGFloat sourceTotalPixels = sourceResolution.width * sourceResolution.height; - // Determine the scale ratio to apply to the input image - // that results in an output image of the defined size. - // see kDestImageSizeMB, and how it relates to destTotalPixels. - CGFloat imageScale = sqrt(destTotalPixels / sourceTotalPixels); - CGSize destResolution = CGSizeZero; - destResolution.width = MAX(1, (int)(sourceResolution.width * imageScale)); - destResolution.height = MAX(1, (int)(sourceResolution.height * imageScale)); - // device color space CGColorSpaceRef colorspaceRef = [self colorSpaceGetDeviceRGB]; BOOL hasAlpha = [self CGImageContainsAlpha:sourceImageRef]; @@ -592,14 +623,10 @@ static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to over return YES; } -+ (BOOL)shouldScaleDownImage:(nonnull UIImage *)image limitBytes:(NSUInteger)bytes { ++ (BOOL)shouldScaleDownImagePixelSize:(CGSize)sourceResolution limitBytes:(NSUInteger)bytes { BOOL shouldScaleDown = YES; - CGImageRef sourceImageRef = image.CGImage; - CGSize sourceResolution = CGSizeZero; - sourceResolution.width = CGImageGetWidth(sourceImageRef); - sourceResolution.height = CGImageGetHeight(sourceImageRef); - float sourceTotalPixels = sourceResolution.width * sourceResolution.height; + CGFloat sourceTotalPixels = sourceResolution.width * sourceResolution.height; if (sourceTotalPixels <= 0) { return NO; } @@ -609,7 +636,7 @@ static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to over } bytes = MAX(bytes, kBytesPerPixel); destTotalPixels = bytes / kBytesPerPixel; - float imageScale = destTotalPixels / sourceTotalPixels; + CGFloat imageScale = destTotalPixels / sourceTotalPixels; if (imageScale < 1) { shouldScaleDown = YES; } else {