From b1b16a17b36c3ecd5f81bf43cf115c8c9f30f375 Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Thu, 27 Oct 2022 11:09:29 +0800 Subject: [PATCH] Support use url.path or custom UTI hint passed to ImageIO, solve the TIFF/NEF/SRW raw image decoding with wrong size This is because file extension will cause ImageIO use different codec, which does not support all of them without context --- .../SDWebImage Demo/MasterViewController.m | 1 + SDWebImage/Core/SDImageCacheDefine.m | 6 ++++- SDWebImage/Core/SDImageCoder.h | 16 ++++++++++++ SDWebImage/Core/SDImageCoder.m | 2 ++ SDWebImage/Core/SDImageIOAnimatedCoder.h | 1 - SDWebImage/Core/SDImageIOAnimatedCoder.m | 26 ++++++++++++++++++- SDWebImage/Core/SDImageIOCoder.m | 26 +++++++++++++++++-- SDWebImage/Core/SDWebImageDefine.h | 9 +++++++ SDWebImage/Core/SDWebImageDefine.m | 1 + .../Private/SDImageIOAnimatedCoderInternal.h | 1 + 10 files changed, 84 insertions(+), 5 deletions(-) diff --git a/Examples/SDWebImage Demo/MasterViewController.m b/Examples/SDWebImage Demo/MasterViewController.m index eea3f634..0cfac7c9 100644 --- a/Examples/SDWebImage Demo/MasterViewController.m +++ b/Examples/SDWebImage Demo/MasterViewController.m @@ -73,6 +73,7 @@ @"https://s2.ax1x.com/2019/11/01/KHYIgJ.gif", @"https://raw.githubusercontent.com/icons8/flat-color-icons/master/pdf/stack_of_photos.pdf", @"https://nr-platform.s3.amazonaws.com/uploads/platform/published_extension/branding_icon/275/AmazonS3.png", + @"https://res.cloudinary.com/dwpjzbyux/raw/upload/v1666474070/RawDemo/raw_vebed5.NEF", @"http://via.placeholder.com/200x200.jpg", nil]; diff --git a/SDWebImage/Core/SDImageCacheDefine.m b/SDWebImage/Core/SDImageCacheDefine.m index 8ee8750c..0319ba10 100644 --- a/SDWebImage/Core/SDImageCacheDefine.m +++ b/SDWebImage/Core/SDImageCacheDefine.m @@ -28,12 +28,16 @@ SDImageCoderOptions * _Nonnull SDGetDecodeOptionsFromContext(SDWebImageContext * if (context[SDWebImageContextImageThumbnailPixelSize]) { thumbnailSizeValue = context[SDWebImageContextImageThumbnailPixelSize]; } + NSString *typeIdentifierHint = context[SDWebImageContextImageTypeIdentifierHint]; + NSString *fileExtensionHint = cacheKey.pathExtension; // without dot - SDImageCoderMutableOptions *mutableCoderOptions = [NSMutableDictionary dictionaryWithCapacity:2]; + SDImageCoderMutableOptions *mutableCoderOptions = [NSMutableDictionary dictionaryWithCapacity:6]; mutableCoderOptions[SDImageCoderDecodeFirstFrameOnly] = @(decodeFirstFrame); mutableCoderOptions[SDImageCoderDecodeScaleFactor] = @(scale); mutableCoderOptions[SDImageCoderDecodePreserveAspectRatio] = preserveAspectRatioValue; mutableCoderOptions[SDImageCoderDecodeThumbnailPixelSize] = thumbnailSizeValue; + mutableCoderOptions[SDImageCoderDecodeTypeIdentifierHint] = typeIdentifierHint; + mutableCoderOptions[SDImageCoderDecodeFileExtensionHint] = fileExtensionHint; mutableCoderOptions[SDImageCoderWebImageContext] = context; SDImageCoderOptions *coderOptions = [mutableCoderOptions copy]; diff --git a/SDWebImage/Core/SDImageCoder.h b/SDWebImage/Core/SDImageCoder.h index 53b52e5d..c5f519b0 100644 --- a/SDWebImage/Core/SDImageCoder.h +++ b/SDWebImage/Core/SDImageCoder.h @@ -44,6 +44,22 @@ FOUNDATION_EXPORT SDImageCoderOption _Nonnull const SDImageCoderDecodePreserveAs */ FOUNDATION_EXPORT SDImageCoderOption _Nonnull const SDImageCoderDecodeThumbnailPixelSize; +/** + A NSString value indicating the source image's file extension. Example: "jpg", "nef", "tif", don't prefix the dot + Some image file format share the same data structure but has different tag explanation, like TIFF and NEF/SRW, see https://en.wikipedia.org/wiki/TIFF + Changing the file extension cause the different image result. The coder (like ImageIO) may use file extension to choose the correct parser + @note However, different UTType may share the same file extension, like `public.jpeg` and `public.jpeg-2000` both use `.jpg`. If you want detail control, use `TypeIdentifierHint` below + */ +FOUNDATION_EXPORT SDImageCoderOption _Nonnull const SDImageCoderDecodeFileExtensionHint; + +/** + A NSString value (UTI) indicating the source image's file extension. Example: "public.jpeg-2000", "com.nikon.raw-image", "public.tiff" + Some image file format share the same data structure but has different tag explanation, like TIFF and NEF/SRW, see https://en.wikipedia.org/wiki/TIFF + Changing the file extension cause the different image result. The coder (like ImageIO) may use file extension to choose the correct parser + @note If you provide `TypeIdentifierHint`, the `FileExtensionHint` option above will be ignored (because UTType has high priority) + @note If you really don't want any hint which effect the image result, pass `NSNull.null` instead + */ +FOUNDATION_EXPORT SDImageCoderOption _Nonnull const SDImageCoderDecodeTypeIdentifierHint; // These options are for image encoding /** diff --git a/SDWebImage/Core/SDImageCoder.m b/SDWebImage/Core/SDImageCoder.m index 0fda1983..141377d5 100644 --- a/SDWebImage/Core/SDImageCoder.m +++ b/SDWebImage/Core/SDImageCoder.m @@ -12,6 +12,8 @@ SDImageCoderOption const SDImageCoderDecodeFirstFrameOnly = @"decodeFirstFrameOn SDImageCoderOption const SDImageCoderDecodeScaleFactor = @"decodeScaleFactor"; SDImageCoderOption const SDImageCoderDecodePreserveAspectRatio = @"decodePreserveAspectRatio"; SDImageCoderOption const SDImageCoderDecodeThumbnailPixelSize = @"decodeThumbnailPixelSize"; +SDImageCoderOption const SDImageCoderDecodeFileExtensionHint = @"decodeFileExtensionHint"; +SDImageCoderOption const SDImageCoderDecodeTypeIdentifierHint = @"decodeTypeIdentifierHint"; SDImageCoderOption const SDImageCoderEncodeFirstFrameOnly = @"encodeFirstFrameOnly"; SDImageCoderOption const SDImageCoderEncodeCompressionQuality = @"encodeCompressionQuality"; diff --git a/SDWebImage/Core/SDImageIOAnimatedCoder.h b/SDWebImage/Core/SDImageIOAnimatedCoder.h index a314c57a..67016c46 100644 --- a/SDWebImage/Core/SDImageIOAnimatedCoder.h +++ b/SDWebImage/Core/SDImageIOAnimatedCoder.h @@ -7,7 +7,6 @@ */ #import -#import #import "SDImageCoder.h" /** diff --git a/SDWebImage/Core/SDImageIOAnimatedCoder.m b/SDWebImage/Core/SDImageIOAnimatedCoder.m index 74a724ae..bc456561 100644 --- a/SDWebImage/Core/SDImageIOAnimatedCoder.m +++ b/SDWebImage/Core/SDImageIOAnimatedCoder.m @@ -15,6 +15,9 @@ #import "UIImage+ForceDecode.h" #import "SDInternalMacros.h" +#import +#import + // Specify DPI for vector format in CGImageSource, like PDF static NSString * kSDCGImageSourceRasterizationDPI = @"kCGImageSourceRasterizationDPI"; // Specify File Size for lossy format encoding, like JPEG @@ -320,10 +323,31 @@ static NSString * kSDCGImageDestinationRequestedFileSize = @"kCGImageDestination } #endif - CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL); + NSString *typeIdentifierHint = options[SDImageCoderDecodeTypeIdentifierHint]; + if (!typeIdentifierHint) { + // Check file extension and convert to UTI, from: https://stackoverflow.com/questions/1506251/getting-an-uniform-type-identifier-for-a-given-extension + NSString *fileExtensionHint = options[SDImageCoderDecodeFileExtensionHint]; + if (fileExtensionHint) { + typeIdentifierHint = (__bridge_transfer NSString *)UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)fileExtensionHint, NULL); + } + } else if ([typeIdentifierHint isEqual:NSNull.null]) { + // Hack if user don't want to imply file extension + typeIdentifierHint = nil; + } + + NSDictionary *creatingOptions = nil; + if (typeIdentifierHint) { + creatingOptions = @{(__bridge NSString *)kCGImageSourceTypeIdentifierHint : typeIdentifierHint}; + } + CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)data, (__bridge CFDictionaryRef)creatingOptions); + if (!source) { + // Try again without UTType hint, the call site from user may provide the wrong UTType + source = CGImageSourceCreateWithData((__bridge CFDataRef)data, (__bridge CFDictionaryRef)creatingOptions); + } if (!source) { return nil; } + size_t count = CGImageSourceGetCount(source); UIImage *animatedImage; diff --git a/SDWebImage/Core/SDImageIOCoder.m b/SDWebImage/Core/SDImageIOCoder.m index ba34a287..877a999c 100644 --- a/SDWebImage/Core/SDImageIOCoder.m +++ b/SDWebImage/Core/SDImageIOCoder.m @@ -9,10 +9,12 @@ #import "SDImageIOCoder.h" #import "SDImageCoderHelper.h" #import "NSImage+Compatibility.h" -#import #import "UIImage+Metadata.h" #import "SDImageIOAnimatedCoderInternal.h" +#import +#import + // Specify DPI for vector format in CGImageSource, like PDF static NSString * kSDCGImageSourceRasterizationDPI = @"kCGImageSourceRasterizationDPI"; // Specify File Size for lossy format encoding, like JPEG @@ -110,7 +112,27 @@ static NSString * kSDCGImageDestinationRequestedFileSize = @"kCGImageDestination preserveAspectRatio = preserveAspectRatioValue.boolValue; } - CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL); + NSString *typeIdentifierHint = options[SDImageCoderDecodeTypeIdentifierHint]; + if (!typeIdentifierHint) { + // Check file extension and convert to UTI, from: https://stackoverflow.com/questions/1506251/getting-an-uniform-type-identifier-for-a-given-extension + NSString *fileExtensionHint = options[SDImageCoderDecodeFileExtensionHint]; + if (fileExtensionHint) { + typeIdentifierHint = (__bridge_transfer NSString *)UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)fileExtensionHint, NULL); + } + } else if ([typeIdentifierHint isEqual:NSNull.null]) { + // Hack if user don't want to imply file extension + typeIdentifierHint = nil; + } + + NSDictionary *creatingOptions = nil; + if (typeIdentifierHint) { + creatingOptions = @{(__bridge NSString *)kCGImageSourceTypeIdentifierHint : typeIdentifierHint}; + } + CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)data, (__bridge CFDictionaryRef)creatingOptions); + if (!source) { + // Try again without UTType hint, the call site from user may provide the wrong UTType + source = CGImageSourceCreateWithData((__bridge CFDataRef)data, (__bridge CFDictionaryRef)creatingOptions); + } if (!source) { return nil; } diff --git a/SDWebImage/Core/SDWebImageDefine.h b/SDWebImage/Core/SDWebImageDefine.h index 3fb0976a..6bea3f56 100644 --- a/SDWebImage/Core/SDWebImageDefine.h +++ b/SDWebImage/Core/SDWebImageDefine.h @@ -262,6 +262,15 @@ FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextImageP */ FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextImageThumbnailPixelSize; +/** + A NSString value (UTI) indicating the source image's file extension. Example: "public.jpeg-2000", "com.nikon.raw-image", "public.tiff" + Some image file format share the same data structure but has different tag explanation, like TIFF and NEF/SRW, see https://en.wikipedia.org/wiki/TIFF + Changing the file extension cause the different image result. The coder (like ImageIO) may use file extension to choose the correct parser + @note If you don't provide this option, we will use the `URL.path` as file extension to calculate the UTI hint + @note If you really don't want any hint which effect the image result, pass `NSNull.null` instead + */ +FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextImageTypeIdentifierHint; + /** A SDImageCacheType raw value which specify the source of cache to query. Specify `SDImageCacheTypeDisk` to query from disk cache only; `SDImageCacheTypeMemory` to query from memory only. And `SDImageCacheTypeAll` to query from both memory cache and disk cache. Specify `SDImageCacheTypeNone` is invalid and totally ignore the cache query. If not provide or the value is invalid, we will use `SDImageCacheTypeAll`. (NSNumber) diff --git a/SDWebImage/Core/SDWebImageDefine.m b/SDWebImage/Core/SDWebImageDefine.m index 8fb2de44..ee1ec4dd 100644 --- a/SDWebImage/Core/SDWebImageDefine.m +++ b/SDWebImage/Core/SDWebImageDefine.m @@ -134,6 +134,7 @@ SDWebImageContextOption const SDWebImageContextImageTransformer = @"imageTransfo SDWebImageContextOption const SDWebImageContextImageScaleFactor = @"imageScaleFactor"; SDWebImageContextOption const SDWebImageContextImagePreserveAspectRatio = @"imagePreserveAspectRatio"; SDWebImageContextOption const SDWebImageContextImageThumbnailPixelSize = @"imageThumbnailPixelSize"; +SDWebImageContextOption const SDWebImageContextImageTypeIdentifierHint = @"imageTypeIdentifierHint"; SDWebImageContextOption const SDWebImageContextQueryCacheType = @"queryCacheType"; SDWebImageContextOption const SDWebImageContextStoreCacheType = @"storeCacheType"; SDWebImageContextOption const SDWebImageContextOriginalQueryCacheType = @"originalQueryCacheType"; diff --git a/SDWebImage/Private/SDImageIOAnimatedCoderInternal.h b/SDWebImage/Private/SDImageIOAnimatedCoderInternal.h index f65ccdd6..0ae40fe4 100644 --- a/SDWebImage/Private/SDImageIOAnimatedCoderInternal.h +++ b/SDWebImage/Private/SDImageIOAnimatedCoderInternal.h @@ -7,6 +7,7 @@ */ #import +#import #import "SDImageIOAnimatedCoder.h" // AVFileTypeHEIC/AVFileTypeHEIF is defined in AVFoundation via iOS 11, we use this without import AVFoundation