Merge pull request #3419 from dreampiggy/feature/tiff_uti_hint

Support use url.path or custom UTI hint passed to ImageIO, solve the TIFF/NEF/SRW raw image decoding with wrong size
This commit is contained in:
DreamPiggy 2022-10-29 18:34:56 +08:00 committed by GitHub
commit 3a52ef5aeb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 140 additions and 5 deletions

View File

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

View File

@ -25,6 +25,8 @@ static const SDImageFormat SDImageFormatHEIC = 5;
static const SDImageFormat SDImageFormatHEIF = 6;
static const SDImageFormat SDImageFormatPDF = 7;
static const SDImageFormat SDImageFormatSVG = 8;
static const SDImageFormat SDImageFormatBMP = 9;
static const SDImageFormat SDImageFormatRAW = 10;
/**
NSData category about the image content type and UTI.

View File

@ -37,6 +37,8 @@
case 0x49:
case 0x4D:
return SDImageFormatTIFF;
case 0x42:
return SDImageFormatBMP;
case 0x52: {
if (data.length >= 12) {
//RIFF....WEBP
@ -113,6 +115,12 @@
case SDImageFormatSVG:
UTType = kSDUTTypeSVG;
break;
case SDImageFormatBMP:
UTType = kSDUTTypeBMP;
break;
case SDImageFormatRAW:
UTType = kSDUTTypeRAW;
break;
default:
// default is kUTTypeImage abstract type
UTType = kSDUTTypeImage;
@ -144,6 +152,10 @@
imageFormat = SDImageFormatPDF;
} else if (CFStringCompare(uttype, kSDUTTypeSVG, 0) == kCFCompareEqualTo) {
imageFormat = SDImageFormatSVG;
} else if (CFStringCompare(uttype, kSDUTTypeBMP, 0) == kCFCompareEqualTo) {
imageFormat = SDImageFormatBMP;
} else if (UTTypeConformsTo(uttype, kSDUTTypeRAW)) {
imageFormat = SDImageFormatRAW;
} else {
imageFormat = SDImageFormatUndefined;
}

View File

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

View File

@ -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
/**

View File

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

View File

@ -7,7 +7,6 @@
*/
#import <Foundation/Foundation.h>
#import <ImageIO/ImageIO.h>
#import "SDImageCoder.h"
/**

View File

@ -15,6 +15,9 @@
#import "UIImage+ForceDecode.h"
#import "SDInternalMacros.h"
#import <ImageIO/ImageIO.h>
#import <CoreServices/CoreServices.h>
// 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;

View File

@ -9,10 +9,12 @@
#import "SDImageIOCoder.h"
#import "SDImageCoderHelper.h"
#import "NSImage+Compatibility.h"
#import <ImageIO/ImageIO.h>
#import "UIImage+Metadata.h"
#import "SDImageIOAnimatedCoderInternal.h"
#import <ImageIO/ImageIO.h>
#import <CoreServices/CoreServices.h>
// 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;
}

View File

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

View File

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

View File

@ -7,6 +7,7 @@
*/
#import <Foundation/Foundation.h>
#import <ImageIO/ImageIO.h>
#import "SDImageIOAnimatedCoder.h"
// AVFileTypeHEIC/AVFileTypeHEIF is defined in AVFoundation via iOS 11, we use this without import AVFoundation
@ -24,6 +25,8 @@
#define kSDUTTypeSVG ((__bridge CFStringRef)@"public.svg-image")
#define kSDUTTypeGIF ((__bridge CFStringRef)@"com.compuserve.gif")
#define kSDUTTypePDF ((__bridge CFStringRef)@"com.adobe.pdf")
#define kSDUTTypeBMP ((__bridge CFStringRef)@"com.microsoft.bmp")
#define kSDUTTypeRAW ((__bridge CFStringRef)@"public.camera-raw-image")
@interface SDImageIOAnimatedCoder ()

View File

@ -98,6 +98,9 @@
32B99EAD203B36690017FD66 /* SDWebImagePrefetcherTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 4369C1D01D97F80F007E863A /* SDWebImagePrefetcherTests.m */; };
32B99EAE203B366C0017FD66 /* SDWebCacheCategoriesTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 4369C2731D9804B1007E863A /* SDWebCacheCategoriesTests.m */; };
32E6F0321F3A1B4700A945E6 /* SDWebImageTestCoder.m in Sources */ = {isa = PBXBuildFile; fileRef = 32E6F0311F3A1B4700A945E6 /* SDWebImageTestCoder.m */; };
32F788A3290D252200B57A1C /* TestImage.nef in Resources */ = {isa = PBXBuildFile; fileRef = 32F788A2290D252200B57A1C /* TestImage.nef */; };
32F788A4290D252200B57A1C /* TestImage.nef in Resources */ = {isa = PBXBuildFile; fileRef = 32F788A2290D252200B57A1C /* TestImage.nef */; };
32F788A5290D252200B57A1C /* TestImage.nef in Resources */ = {isa = PBXBuildFile; fileRef = 32F788A2290D252200B57A1C /* TestImage.nef */; };
37D122881EC48B5E00D98CEB /* SDMockFileManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 37D122871EC48B5E00D98CEB /* SDMockFileManager.m */; };
433BBBB51D7EF5C00086B6E9 /* SDImageCoderTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 433BBBB41D7EF5C00086B6E9 /* SDImageCoderTests.m */; };
433BBBB71D7EF8200086B6E9 /* TestImage.gif in Resources */ = {isa = PBXBuildFile; fileRef = 433BBBB61D7EF8200086B6E9 /* TestImage.gif */; };
@ -158,6 +161,7 @@
32B99E96203B2DF90017FD66 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
32E6F0301F3A1B4700A945E6 /* SDWebImageTestCoder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDWebImageTestCoder.h; sourceTree = "<group>"; };
32E6F0311F3A1B4700A945E6 /* SDWebImageTestCoder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDWebImageTestCoder.m; sourceTree = "<group>"; };
32F788A2290D252200B57A1C /* TestImage.nef */ = {isa = PBXFileReference; lastKnownFileType = file; path = TestImage.nef; sourceTree = "<group>"; };
37D122861EC48B5E00D98CEB /* SDMockFileManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDMockFileManager.h; sourceTree = "<group>"; };
37D122871EC48B5E00D98CEB /* SDMockFileManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDMockFileManager.m; sourceTree = "<group>"; };
433BBBB41D7EF5C00086B6E9 /* SDImageCoderTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDImageCoderTests.m; sourceTree = "<group>"; };
@ -270,6 +274,7 @@
327A418B211D660600495442 /* TestImage.heic */,
32905E63211D786E00460FCF /* TestImage.heif */,
3234306123E2BAC800C290C8 /* TestImage.pdf */,
32F788A2290D252200B57A1C /* TestImage.nef */,
327054E1206CEFF3006EA328 /* TestImageAnimated.apng */,
3297A09E23374D1600814590 /* TestImageAnimated.heic */,
32515F9824AF1919005E8F79 /* TestImageAnimated.webp */,
@ -474,6 +479,7 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
32F788A5290D252200B57A1C /* TestImage.nef in Resources */,
6BC1C210270F073A003FFAB1 /* TestAnimatedImageMemory.webp in Resources */,
3299228D2365DC6C00EAFD97 /* TestImageAnimated.apng in Resources */,
3299228B2365DC6C00EAFD97 /* TestImage.heic in Resources */,
@ -499,6 +505,7 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
32F788A4290D252200B57A1C /* TestImage.nef in Resources */,
6BC1C20F270F0193003FFAB1 /* TestAnimatedImageMemory.webp in Resources */,
327054E3206CEFF3006EA328 /* TestImageAnimated.apng in Resources */,
32B99EA3203B31360017FD66 /* TestImage.gif in Resources */,
@ -524,6 +531,7 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
32F788A3290D252200B57A1C /* TestImage.nef in Resources */,
327A418C211D660600495442 /* TestImage.heic in Resources */,
6B181A1B265757ED00BD06B3 /* TestAnimatedImageMemory.webp in Resources */,
5F7F38AD1AE2A77A00B0E330 /* TestImage.jpg in Resources */,

Binary file not shown.

View File

@ -396,6 +396,38 @@
expect(size8).equal(CGSizeMake(999, 999));
}
- (void)test25ThatBMPWorks {
NSURL *bmpURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"TestImage" withExtension:@"bmp"];
[self verifyCoder:[SDImageIOCoder sharedCoder]
withLocalImageURL:bmpURL
supportsEncoding:YES
encodingFormat:SDImageFormatBMP
isAnimatedImage:NO
isVectorImage:NO];
}
- (void)test26ThatRawImageTypeHintWorks {
NSURL *url = [[NSBundle bundleForClass:[self class]] URLForResource:@"TestImage" withExtension:@"nef"];
NSData *data = [NSData dataWithContentsOfURL:url];
// 1. Test without hint will use TIFF's IFD#0, which size should always be 160x120, see: http://lclevy.free.fr/nef/
UIImage *image1 = [SDImageIOCoder.sharedCoder decodedImageWithData:data options:nil];
expect(image1.size).equal(CGSizeMake(160, 120));
expect(image1.sd_imageFormat).equal(SDImageFormatTIFF);
#if SD_MAC || SD_IOS
// 2. Test with NEF file extension should be NEF
UIImage *image2 = [SDImageIOCoder.sharedCoder decodedImageWithData:data options:@{SDImageCoderDecodeFileExtensionHint : @"nef"}];
expect(image2.size).equal(CGSizeMake(3008, 2000));
expect(image2.sd_imageFormat).equal(SDImageFormatRAW);
// 3. Test with UTType hint should be NEF
UIImage *image3 = [SDImageIOCoder.sharedCoder decodedImageWithData:data options:@{SDImageCoderDecodeTypeIdentifierHint : @"com.nikon.raw-image"}];
expect(image3.size).equal(CGSizeMake(3008, 2000));
expect(image3.sd_imageFormat).equal(SDImageFormatRAW);
#endif
}
#pragma mark - Utils
- (void)verifyCoder:(id<SDImageCoder>)coder