diff --git a/SDWebImage/Core/SDImageCacheDefine.m b/SDWebImage/Core/SDImageCacheDefine.m index 19db161a..fa9f4830 100644 --- a/SDWebImage/Core/SDImageCacheDefine.m +++ b/SDWebImage/Core/SDImageCacheDefine.m @@ -14,6 +14,8 @@ #import "SDInternalMacros.h" UIImage * _Nullable SDImageCacheDecodeImageData(NSData * _Nonnull imageData, NSString * _Nonnull cacheKey, SDWebImageOptions options, SDWebImageContext * _Nullable context) { + NSCParameterAssert(imageData); + NSCParameterAssert(cacheKey); UIImage *image; BOOL decodeFirstFrame = SD_OPTIONS_CONTAINS(options, SDWebImageDecodeFirstFrameOnly); NSNumber *scaleValue = context[SDWebImageContextImageScaleFactor]; @@ -79,6 +81,8 @@ UIImage * _Nullable SDImageCacheDecodeImageData(NSData * _Nonnull imageData, NSS if (shouldDecode) { image = [SDImageCoderHelper decodedImageWithImage:image]; } + // mark the image as thumbnail, to let manager check whether to re-decode if needed + image.sd_isThumbnail = thumbnailSizeValue != nil; } return image; diff --git a/SDWebImage/Core/SDImageIOAnimatedCoder.m b/SDWebImage/Core/SDImageIOAnimatedCoder.m index b1bd1b24..f79761a0 100644 --- a/SDWebImage/Core/SDImageIOAnimatedCoder.m +++ b/SDWebImage/Core/SDImageIOAnimatedCoder.m @@ -311,11 +311,14 @@ static NSString * kSDCGImageDestinationRequestedFileSize = @"kCGImageDestination // Which decode frames in time and reduce memory usage if (thumbnailSize.width == 0 || thumbnailSize.height == 0) { SDAnimatedImageRep *imageRep = [[SDAnimatedImageRep alloc] initWithData:data]; - NSSize size = NSMakeSize(imageRep.pixelsWide / scale, imageRep.pixelsHigh / scale); - imageRep.size = size; - NSImage *animatedImage = [[NSImage alloc] initWithSize:size]; - [animatedImage addRepresentation:imageRep]; - return animatedImage; + if (imageRep) { + NSSize size = NSMakeSize(imageRep.pixelsWide / scale, imageRep.pixelsHigh / scale); + imageRep.size = size; + NSImage *animatedImage = [[NSImage alloc] initWithSize:size]; + [animatedImage addRepresentation:imageRep]; + animatedImage.sd_imageFormat = self.class.imageFormat; + return animatedImage; + } } #endif diff --git a/SDWebImage/Core/SDImageLoader.m b/SDWebImage/Core/SDImageLoader.m index d6b95948..31b8c3d0 100644 --- a/SDWebImage/Core/SDImageLoader.m +++ b/SDWebImage/Core/SDImageLoader.m @@ -106,6 +106,8 @@ UIImage * _Nullable SDImageLoaderDecodeImageData(NSData * _Nonnull imageData, NS if (shouldDecode) { image = [SDImageCoderHelper decodedImageWithImage:image]; } + // mark the image as thumbnail, to let manager check whether to re-decode if needed + image.sd_isThumbnail = thumbnailSizeValue != nil; } return image; @@ -204,6 +206,8 @@ UIImage * _Nullable SDImageLoaderDecodeProgressiveImageData(NSData * _Nonnull im } // mark the image as progressive (completed one are not mark as progressive) image.sd_isIncremental = !finished; + // mark the image as thumbnail, to let manager check whether to re-decode if needed + image.sd_isThumbnail = thumbnailSizeValue != nil; } return image; diff --git a/SDWebImage/Core/SDWebImageManager.m b/SDWebImage/Core/SDWebImageManager.m index 9a4e97bd..3c0fa69e 100644 --- a/SDWebImage/Core/SDWebImageManager.m +++ b/SDWebImage/Core/SDWebImageManager.m @@ -510,7 +510,7 @@ static id _defaultImageLoader; // normally use the store cache type, but if target image is transformed, use original store cache type instead SDImageCacheType targetStoreCacheType = shouldTransformImage ? originalStoreCacheType : storeCacheType; UIImage *originalImage = downloadedImage; - BOOL thumbnailed = context[SDWebImageContextImageThumbnailPixelSize]; + BOOL thumbnailed = context[SDWebImageContextImageThumbnailPixelSize] != nil; if (thumbnailed) { // Thumbnail decoding does not keep original image // Here we only store the original data to disk for original cache key @@ -560,10 +560,16 @@ static id _defaultImageLoader; } id cacheSerializer = context[SDWebImageContextCacheSerializer]; + // transformer check BOOL shouldTransformImage = originalImage && transformer; shouldTransformImage = shouldTransformImage && (!originalImage.sd_isAnimated || (options & SDWebImageTransformAnimatedImage)); shouldTransformImage = shouldTransformImage && (!originalImage.sd_isVector || (options & SDWebImageTransformVectorImage)); - // if available, store transformed image to cache + + // thumbnail check + // This exist when previous thumbnail pipeline callback into next full size pipeline, because we share the same URL download but need different image + // Actually this is a hack, we attach the metadata into image object, which should design a better concept like `ImageInfo` and keep that around + BOOL shouldDecodeFullImage = originalImage.sd_isThumbnail && context[SDWebImageContextImageThumbnailPixelSize] == nil; + if (shouldTransformImage) { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ @autoreleasepool { @@ -587,6 +593,19 @@ static id _defaultImageLoader; } } }); + } else if (shouldDecodeFullImage) { + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ + @autoreleasepool { + // transformed/thumbnailed cache key + NSString *key = [self cacheKeyForURL:url context:context]; + // disable thumbnail decoding + SDWebImageMutableContext *tempContext = [context mutableCopy]; + tempContext[SDWebImageContextImageThumbnailPixelSize] = nil; + UIImage *originalImage = SDImageCacheDecodeImageData(originalData, key, options, tempContext); + // Continue store transform cache process + [self callStoreTransformCacheProcessForOperation:operation url:url options:options context:context image:originalImage data:originalData cacheType:cacheType transformed:NO finished:finished completed:completedBlock]; + } + }); } else { // Continue store transform cache process [self callStoreTransformCacheProcessForOperation:operation url:url options:options context:context image:originalImage data:originalData cacheType:cacheType transformed:NO finished:finished completed:completedBlock]; @@ -620,7 +639,7 @@ static id _defaultImageLoader; // but the storeImage does not handle the thumbnail context option // to keep exist SDImageCache's impl compatible, we introduce this helper NSData *cacheData = data; - BOOL thumbnailed = context[SDWebImageContextImageThumbnailPixelSize]; + BOOL thumbnailed = context[SDWebImageContextImageThumbnailPixelSize] != nil; if (thumbnailed) { // Thumbnail decoding already stored original data before in `storeCacheProcess` // Here we only store the thumbnail image to memory for thumbnail cache key diff --git a/SDWebImage/Core/UIImage+Metadata.h b/SDWebImage/Core/UIImage+Metadata.h index 6a278e2e..fafd39bd 100644 --- a/SDWebImage/Core/UIImage+Metadata.h +++ b/SDWebImage/Core/UIImage+Metadata.h @@ -65,4 +65,9 @@ */ @property (nonatomic, assign) BOOL sd_isIncremental; +/** + A bool value indicating whether the image is from thumbnail decoding and may be smaller than the full image data pixel size. + */ +@property (nonatomic, assign) BOOL sd_isThumbnail; + @end diff --git a/SDWebImage/Core/UIImage+Metadata.m b/SDWebImage/Core/UIImage+Metadata.m index b8f4fd82..21eb3b3b 100644 --- a/SDWebImage/Core/UIImage+Metadata.m +++ b/SDWebImage/Core/UIImage+Metadata.m @@ -186,4 +186,13 @@ return value.boolValue; } +- (void)setSd_isThumbnail:(BOOL)sd_isThumbnail { + objc_setAssociatedObject(self, @selector(sd_isThumbnail), @(sd_isThumbnail), OBJC_ASSOCIATION_RETAIN_NONATOMIC); +} + +- (BOOL)sd_isThumbnail { + NSNumber *value = objc_getAssociatedObject(self, @selector(sd_isThumbnail)); + return value.boolValue; +} + @end diff --git a/SDWebImage/Private/SDAssociatedObject.m b/SDWebImage/Private/SDAssociatedObject.m index a7c70763..84305831 100644 --- a/SDWebImage/Private/SDAssociatedObject.m +++ b/SDWebImage/Private/SDAssociatedObject.m @@ -18,6 +18,7 @@ void SDImageCopyAssociatedObject(UIImage * _Nullable source, UIImage * _Nullable } // Image Metadata target.sd_isIncremental = source.sd_isIncremental; + target.sd_isThumbnail = source.sd_isThumbnail; target.sd_imageLoopCount = source.sd_imageLoopCount; target.sd_imageFormat = source.sd_imageFormat; // Force Decode