From cb84dbb273de89ba6e984079097339a474a01c08 Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Thu, 30 Jan 2020 13:31:57 +0800 Subject: [PATCH 01/11] Added the PDF/SVG image type define --- SDWebImage/Core/NSData+ImageContentType.h | 2 ++ SDWebImage/Core/NSData+ImageContentType.m | 10 ++++++++++ 2 files changed, 12 insertions(+) diff --git a/SDWebImage/Core/NSData+ImageContentType.h b/SDWebImage/Core/NSData+ImageContentType.h index 5bbb4ae6..8c2f97e8 100644 --- a/SDWebImage/Core/NSData+ImageContentType.h +++ b/SDWebImage/Core/NSData+ImageContentType.h @@ -23,6 +23,8 @@ static const SDImageFormat SDImageFormatTIFF = 3; static const SDImageFormat SDImageFormatWebP = 4; static const SDImageFormat SDImageFormatHEIC = 5; static const SDImageFormat SDImageFormatHEIF = 6; +static const SDImageFormat SDImageFormatPDF = 7; +static const SDImageFormat SDImageFormatSVG = 8; /** NSData category about the image content type and UTI. diff --git a/SDWebImage/Core/NSData+ImageContentType.m b/SDWebImage/Core/NSData+ImageContentType.m index 34dd4aa0..b3b8e22b 100644 --- a/SDWebImage/Core/NSData+ImageContentType.m +++ b/SDWebImage/Core/NSData+ImageContentType.m @@ -93,6 +93,12 @@ case SDImageFormatHEIF: UTType = kSDUTTypeHEIF; break; + case SDImageFormatPDF: + UTType = kUTTypePDF; + break; + case SDImageFormatSVG: + UTType = kUTTypeScalableVectorGraphics; + break; default: // default is kUTTypePNG UTType = kUTTypePNG; @@ -120,6 +126,10 @@ imageFormat = SDImageFormatHEIC; } else if (CFStringCompare(uttype, kSDUTTypeHEIF, 0) == kCFCompareEqualTo) { imageFormat = SDImageFormatHEIF; + } else if (CFStringCompare(uttype, kUTTypePDF, 0) == kCFCompareEqualTo) { + imageFormat = SDImageFormatPDF; + } else if (CFStringCompare(uttype, kUTTypeScalableVectorGraphics, 0) == kCFCompareEqualTo) { + imageFormat = SDImageFormatSVG; } else { imageFormat = SDImageFormatUndefined; } From 8ca455606655a9f57d9e315f91595eb9235107cf Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Thu, 30 Jan 2020 13:50:53 +0800 Subject: [PATCH 02/11] Added the `sd_isVector` API on UIImage+Metadata, useful for case when we want to filter the vector/bitmap images. Vector currently only sipports PDF/SVG --- SDWebImage/Core/UIImage+Metadata.h | 10 +++++++- SDWebImage/Core/UIImage+Metadata.m | 41 +++++++++++++++++++++++++++--- 2 files changed, 47 insertions(+), 4 deletions(-) diff --git a/SDWebImage/Core/UIImage+Metadata.h b/SDWebImage/Core/UIImage+Metadata.h index e42ff697..8328c261 100644 --- a/SDWebImage/Core/UIImage+Metadata.h +++ b/SDWebImage/Core/UIImage+Metadata.h @@ -28,12 +28,20 @@ /** * UIKit: - * Check the `images` array property + * Check the `images` array property. * AppKit: * NSImage currently only support animated via GIF imageRep unlike UIImage. It will check the imageRep's frame count. */ @property (nonatomic, assign, readonly) BOOL sd_isAnimated; +/** + * UIKit: + * Check the `isSymbolImage` property. Also check the system PDF(iOS 11+) && SVG(iOS 13+) support. + * AppKit: + * NSImage supports PDF && SVG && EPS imageRep, check the imageRep class. + */ +@property (nonatomic, assign, readonly) BOOL sd_isVector; + /** * The image format represent the original compressed image data format. * If you don't manually specify a format, this information is retrieve from CGImage using `CGImageGetUTType`, which may return nil for non-CG based image. At this time it will return `SDImageFormatUndefined` as default value. diff --git a/SDWebImage/Core/UIImage+Metadata.m b/SDWebImage/Core/UIImage+Metadata.m index 3c9bf929..ef63c41f 100644 --- a/SDWebImage/Core/UIImage+Metadata.m +++ b/SDWebImage/Core/UIImage+Metadata.m @@ -32,6 +32,26 @@ return (self.images != nil); } +- (BOOL)sd_isVector { + if (@available(iOS 13.0, tvOS 13.0, watchOS 6.0, *)) { + if (self.isSymbolImage) { + return YES; + } + } +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Warc-performSelector-leaks" + SEL SVGSelector = NSSelectorFromString(@"_CGSVGDocument"); + if ([self respondsToSelector:SVGSelector] && [self performSelector:SVGSelector] != nil) { + return YES; + } + SEL PDFSelector = NSSelectorFromString(@"_CGPDFPage"); + if ([self respondsToSelector:PDFSelector] && [self performSelector:PDFSelector] != nil) { + return YES; + } +#pragma clang diagnostic pop + return NO; +} + #else - (NSUInteger)sd_imageLoopCount { @@ -61,7 +81,7 @@ } - (BOOL)sd_isAnimated { - BOOL isGIF = NO; + BOOL isAnimated = NO; NSRect imageRect = NSMakeRect(0, 0, self.size.width, self.size.height); NSImageRep *imageRep = [self bestRepresentationForRect:imageRect context:nil hints:nil]; NSBitmapImageRep *bitmapImageRep; @@ -70,9 +90,24 @@ } if (bitmapImageRep) { NSUInteger frameCount = [[bitmapImageRep valueForProperty:NSImageFrameCount] unsignedIntegerValue]; - isGIF = frameCount > 1 ? YES : NO; + isAnimated = frameCount > 1 ? YES : NO; } - return isGIF; + return isAnimated; +} + +- (BOOL)sd_isVector { + NSRect imageRect = NSMakeRect(0, 0, self.size.width, self.size.height); + NSImageRep *imageRep = [self bestRepresentationForRect:imageRect context:nil hints:nil]; + if ([imageRep isKindOfClass:[NSPDFImageRep class]]) { + return YES; + } + if ([imageRep isKindOfClass:[NSEPSImageRep class]]) { + return YES; + } + if ([NSStringFromClass(imageRep.class) hasSuffix:@"NSSVGImageRep"]) { + return YES; + } + return NO; } #endif From 08aab785db3ac135e3e3e243bfd81518d75d738e Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Thu, 30 Jan 2020 14:04:12 +0800 Subject: [PATCH 03/11] Added the case to detect PDF format from file signature --- SDWebImage/Core/NSData+ImageContentType.m | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/SDWebImage/Core/NSData+ImageContentType.m b/SDWebImage/Core/NSData+ImageContentType.m index b3b8e22b..6bce90b5 100644 --- a/SDWebImage/Core/NSData+ImageContentType.m +++ b/SDWebImage/Core/NSData+ImageContentType.m @@ -65,6 +65,15 @@ } break; } + case 0x25: { + if (data.length >= 4) { + //%PDF + NSString *testString = [[NSString alloc] initWithData:[data subdataWithRange:NSMakeRange(1, 4)] encoding:NSASCIIStringEncoding]; + if ([testString isEqualToString:@"PDF"]) { + return SDImageFormatPDF; + } + } + } } return SDImageFormatUndefined; } From 96b0a2e0316111e0967ebe557fbb2e513f44cf45 Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Thu, 30 Jan 2020 15:11:42 +0800 Subject: [PATCH 04/11] Added the default ImageIO coder with PDF support, use the screen size if user does not provide any explict pixel size --- SDWebImage/Core/SDImageIOAnimatedCoder.m | 50 ++++++++++++++++++------ 1 file changed, 39 insertions(+), 11 deletions(-) diff --git a/SDWebImage/Core/SDImageIOAnimatedCoder.m b/SDWebImage/Core/SDImageIOAnimatedCoder.m index b72dc4e0..1db7a495 100644 --- a/SDWebImage/Core/SDImageIOAnimatedCoder.m +++ b/SDWebImage/Core/SDImageIOAnimatedCoder.m @@ -14,6 +14,9 @@ #import "SDAnimatedImageRep.h" #import "UIImage+ForceDecode.h" +// Specify DPI for vector format in CGImageSource, like PDF +static NSString * kSDCGImageSourceRasterizationDPI = @"kCGImageSourceRasterizationDPI"; + @interface SDImageIOCoderFrame : NSObject @property (nonatomic, assign) NSUInteger index; // Frame index (zero based) @@ -158,9 +161,33 @@ exifOrientation = kCGImagePropertyOrientationUp; } + CFStringRef uttype = CGImageSourceGetType(source); + // Check vector format + BOOL isVector = NO; + if ([NSData sd_imageFormatFromUTType:uttype] == SDImageFormatPDF) { + isVector = YES; + } + CGImageRef imageRef; - if (thumbnailSize.width == 0 || thumbnailSize.height == 0 || (pixelWidth <= thumbnailSize.width && pixelHeight <= thumbnailSize.height)) { - imageRef = CGImageSourceCreateImageAtIndex(source, index, NULL); + if (thumbnailSize.width == 0 || thumbnailSize.height == 0 || pixelWidth == 0 || pixelHeight == 0 || (pixelWidth <= thumbnailSize.width && pixelHeight <= thumbnailSize.height)) { + NSDictionary *options; + if (isVector) { + if (thumbnailSize.width == 0 || thumbnailSize.height == 0) { + // Provide the default pixel count for vector images, simply just use the screen size +#if SD_WATCH + thumbnailSize = WKInterfaceDevice.currentDevice.screenBounds.size; +#elif SD_UIKIT + thumbnailSize = UIScreen.mainScreen.bounds.size; +#elif SD_MAC + thumbnailSize = NSScreen.mainScreen.frame.size; +#endif + } + CGFloat maxPixelSize = MAX(thumbnailSize.width, thumbnailSize.height); + NSUInteger DPIPerPixel = 2; + NSUInteger rasterizationDPI = maxPixelSize * DPIPerPixel; + options = @{kSDCGImageSourceRasterizationDPI : @(rasterizationDPI)}; + } + imageRef = CGImageSourceCreateImageAtIndex(source, index, (__bridge CFDictionaryRef)options); } else { NSMutableDictionary *thumbnailOptions = [NSMutableDictionary dictionary]; thumbnailOptions[(__bridge NSString *)kCGImageSourceCreateThumbnailWithTransform] = @(preserveAspectRatio); @@ -179,21 +206,22 @@ thumbnailOptions[(__bridge NSString *)kCGImageSourceThumbnailMaxPixelSize] = @(maxPixelSize); thumbnailOptions[(__bridge NSString *)kCGImageSourceCreateThumbnailFromImageIfAbsent] = @(YES); imageRef = CGImageSourceCreateThumbnailAtIndex(source, index, (__bridge CFDictionaryRef)thumbnailOptions); + } + if (!imageRef) { + return nil; + } + + if (thumbnailSize.width > 0 && thumbnailSize.height > 0) { if (preserveAspectRatio) { // kCGImageSourceCreateThumbnailWithTransform will apply EXIF transform as well, we should not apply twice exifOrientation = kCGImagePropertyOrientationUp; } else { // `CGImageSourceCreateThumbnailAtIndex` take only pixel dimension, if not `preserveAspectRatio`, we should manual scale to the target size - if (imageRef) { - CGImageRef scaledImageRef = [SDImageCoderHelper CGImageCreateScaled:imageRef size:thumbnailSize]; - CGImageRelease(imageRef); - imageRef = scaledImageRef; - } + CGImageRef scaledImageRef = [SDImageCoderHelper CGImageCreateScaled:imageRef size:thumbnailSize]; + CGImageRelease(imageRef); + imageRef = scaledImageRef; } } - if (!imageRef) { - return nil; - } #if SD_UIKIT || SD_WATCH UIImageOrientation imageOrientation = [SDImageCoderHelper imageOrientationFromEXIFOrientation:exifOrientation]; @@ -363,7 +391,7 @@ if (scaleFactor != nil) { scale = MAX([scaleFactor doubleValue], 1); } - image = [SDImageIOAnimatedCoder createFrameAtIndex:0 source:_imageSource scale:scale preserveAspectRatio:_preserveAspectRatio thumbnailSize:_thumbnailSize]; + image = [self.class createFrameAtIndex:0 source:_imageSource scale:scale preserveAspectRatio:_preserveAspectRatio thumbnailSize:_thumbnailSize]; if (image) { image.sd_imageFormat = self.class.imageFormat; } From 8c6556e835386199af0cbf80b55a891bda92dbab Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Thu, 30 Jan 2020 15:29:15 +0800 Subject: [PATCH 05/11] Fix the PDF data detection --- SDWebImage/Core/NSData+ImageContentType.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SDWebImage/Core/NSData+ImageContentType.m b/SDWebImage/Core/NSData+ImageContentType.m index 6bce90b5..4db1ff5b 100644 --- a/SDWebImage/Core/NSData+ImageContentType.m +++ b/SDWebImage/Core/NSData+ImageContentType.m @@ -68,7 +68,7 @@ case 0x25: { if (data.length >= 4) { //%PDF - NSString *testString = [[NSString alloc] initWithData:[data subdataWithRange:NSMakeRange(1, 4)] encoding:NSASCIIStringEncoding]; + NSString *testString = [[NSString alloc] initWithData:[data subdataWithRange:NSMakeRange(1, 3)] encoding:NSASCIIStringEncoding]; if ([testString isEqualToString:@"PDF"]) { return SDImageFormatPDF; } From eeec6de69840767bdff8fac6299d10b6fc469836 Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Thu, 30 Jan 2020 15:29:57 +0800 Subject: [PATCH 06/11] Update the PDF demo and test cases --- .../SDWebImage Demo/MasterViewController.m | 1 + .../project.pbxproj | 8 +++++ Tests/Tests/Images/TestImage.pdf | Bin 0 -> 1018 bytes Tests/Tests/SDImageCoderTests.m | 33 +++++++++++++++--- 4 files changed, 37 insertions(+), 5 deletions(-) create mode 100644 Tests/Tests/Images/TestImage.pdf diff --git a/Examples/SDWebImage Demo/MasterViewController.m b/Examples/SDWebImage Demo/MasterViewController.m index f131f55f..b82b17c0 100644 --- a/Examples/SDWebImage Demo/MasterViewController.m +++ b/Examples/SDWebImage Demo/MasterViewController.m @@ -75,6 +75,7 @@ @"https://nokiatech.github.io/heif/content/images/ski_jump_1440x960.heic", @"https://nokiatech.github.io/heif/content/image_sequences/starfield_animation.heic", @"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", @"http://via.placeholder.com/200x200.jpg", nil]; diff --git a/Tests/SDWebImage Tests.xcodeproj/project.pbxproj b/Tests/SDWebImage Tests.xcodeproj/project.pbxproj index c98bbde1..ac80009d 100644 --- a/Tests/SDWebImage Tests.xcodeproj/project.pbxproj +++ b/Tests/SDWebImage Tests.xcodeproj/project.pbxproj @@ -14,6 +14,9 @@ 322241802272F808002429DB /* SDUtilsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3222417E2272F808002429DB /* SDUtilsTests.m */; }; 3226ECBB20754F7700FAFACF /* SDWebImageTestDownloadOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 3226ECBA20754F7700FAFACF /* SDWebImageTestDownloadOperation.m */; }; 3226ECBC20754F7700FAFACF /* SDWebImageTestDownloadOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 3226ECBA20754F7700FAFACF /* SDWebImageTestDownloadOperation.m */; }; + 3234306223E2BAC800C290C8 /* TestImage.pdf in Resources */ = {isa = PBXBuildFile; fileRef = 3234306123E2BAC800C290C8 /* TestImage.pdf */; }; + 3234306323E2BAC800C290C8 /* TestImage.pdf in Resources */ = {isa = PBXBuildFile; fileRef = 3234306123E2BAC800C290C8 /* TestImage.pdf */; }; + 3234306423E2BAC800C290C8 /* TestImage.pdf in Resources */ = {isa = PBXBuildFile; fileRef = 3234306123E2BAC800C290C8 /* TestImage.pdf */; }; 323B8E1F20862322008952BE /* SDWebImageTestLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 323B8E1E20862322008952BE /* SDWebImageTestLoader.m */; }; 323B8E2020862322008952BE /* SDWebImageTestLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 323B8E1E20862322008952BE /* SDWebImageTestLoader.m */; }; 324047442271956F007C53E1 /* TestEXIF.png in Resources */ = {isa = PBXBuildFile; fileRef = 324047432271956F007C53E1 /* TestEXIF.png */; }; @@ -107,6 +110,7 @@ 3222417E2272F808002429DB /* SDUtilsTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDUtilsTests.m; sourceTree = ""; }; 3226ECB920754F7700FAFACF /* SDWebImageTestDownloadOperation.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDWebImageTestDownloadOperation.h; sourceTree = ""; }; 3226ECBA20754F7700FAFACF /* SDWebImageTestDownloadOperation.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDWebImageTestDownloadOperation.m; sourceTree = ""; }; + 3234306123E2BAC800C290C8 /* TestImage.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; path = TestImage.pdf; sourceTree = ""; }; 323B8E1D20862322008952BE /* SDWebImageTestLoader.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDWebImageTestLoader.h; sourceTree = ""; }; 323B8E1E20862322008952BE /* SDWebImageTestLoader.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDWebImageTestLoader.m; sourceTree = ""; }; 324047432271956F007C53E1 /* TestEXIF.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = TestEXIF.png; sourceTree = ""; }; @@ -238,6 +242,7 @@ 433BBBB81D7EF8260086B6E9 /* TestImage.png */, 327A418B211D660600495442 /* TestImage.heic */, 32905E63211D786E00460FCF /* TestImage.heif */, + 3234306123E2BAC800C290C8 /* TestImage.pdf */, 327054E1206CEFF3006EA328 /* TestImageAnimated.apng */, 3297A09E23374D1600814590 /* TestImageAnimated.heic */, ); @@ -443,6 +448,7 @@ 3299228B2365DC6C00EAFD97 /* TestImage.heic in Resources */, 329922872365DC6C00EAFD97 /* TestLoopCount.gif in Resources */, 3299228C2365DC6C00EAFD97 /* TestImage.heif in Resources */, + 3234306423E2BAC800C290C8 /* TestImage.pdf in Resources */, 329922892365DC6C00EAFD97 /* TestImageLarge.jpg in Resources */, 3299228A2365DC6C00EAFD97 /* TestImage.png in Resources */, 329922842365DC6C00EAFD97 /* MonochromeTestImage.jpg in Resources */, @@ -461,6 +467,7 @@ 32B99EA3203B31360017FD66 /* TestImage.gif in Resources */, 324047452271956F007C53E1 /* TestEXIF.png in Resources */, 32B99EA4203B31360017FD66 /* TestImage.jpg in Resources */, + 3234306323E2BAC800C290C8 /* TestImage.pdf in Resources */, 32B99EA6203B31360017FD66 /* TestImage.png in Resources */, 3297A0A023374D1700814590 /* TestImageAnimated.heic in Resources */, 32B99EA2203B31360017FD66 /* MonochromeTestImage.jpg in Resources */, @@ -479,6 +486,7 @@ 5F7F38AD1AE2A77A00B0E330 /* TestImage.jpg in Resources */, 32905E64211D786E00460FCF /* TestImage.heif in Resources */, 43828A451DA67F9900000E62 /* TestImageLarge.jpg in Resources */, + 3234306223E2BAC800C290C8 /* TestImage.pdf in Resources */, 433BBBB71D7EF8200086B6E9 /* TestImage.gif in Resources */, 433BBBB91D7EF8260086B6E9 /* TestImage.png in Resources */, 3297A09F23374D1700814590 /* TestImageAnimated.heic in Resources */, diff --git a/Tests/Tests/Images/TestImage.pdf b/Tests/Tests/Images/TestImage.pdf new file mode 100644 index 0000000000000000000000000000000000000000..132681b067d4be6b5415f062924037811b926d9a GIT binary patch literal 1018 zcma)5O=uHA6c!I6>kmb-t$KMvqb+E5lkFyH357OIg1-$LMZ^{zH`8P#-HEf4k}6(2 zhzhnK>Y>!1s#HAGqTofL2t9~+5PA_24;H)#MGt}pf8T6=8rqA)FthW`oAitZz#TJi;sUQI%vR7omQezP!CWiD{|0|kf{KGny~YNa@_Mo&|Ye>3{|?i6jyLc zhLqC8k+JTLt9PuA|Cm^~b9a8eF>*1fwTy0W`to}-Y2NvK;l`iYshNgH<4t{+TKva` zR=LA-=jJ{)J~@A(dFz8~&9@sSGyHo0>!&@zk>Nu37@y9a+_mMzt(TYk-;7^*I5YX; z>QU=g-`wb##2P)lY4M)_{ry)T4(;o-*1h|_d&-EL(`((2ZwEGv{nfvI+BSRhTR5pu zCsW~vUs9QrBX<-OXz!5s@)WG;_LRm;BAFJb5~5KF;82N%2T)_(5CGL8xT1Evz!hi3 zk5Tgj>fwK=eZHt=89toCtt0r|>kz3RSsotzpAvspNU`d7R zQQL24Tx2YTnl-jXDXh*xsLnwQyF)MztroF4gMdGv`Ge4!6QaaDa#G0Img{Fc z+t%iVNpY4bTG(y>|7@uW;kxi;{xKp*o@L9fu_A7z4oh!JoDM1)L=>$yAY>T20fSJ* z3?1ts2RJn>7+Q&G0Zh+wyLQa#GE7JB%Q2%PvH}YS>+A$4cAj$Y7fst|DN>g0X)-22 e3NN?IUT2VjEQ429R}d~dh2d~m@%iF?3FRM0)gsma literal 0 HcmV?d00001 diff --git a/Tests/Tests/SDImageCoderTests.m b/Tests/Tests/SDImageCoderTests.m index 23dc6d56..95b8b5a2 100644 --- a/Tests/Tests/SDImageCoderTests.m +++ b/Tests/Tests/SDImageCoderTests.m @@ -156,22 +156,34 @@ withLocalImageURL:heicURL supportsEncoding:supportsEncoding encodingFormat:SDImageFormatHEIC - isAnimatedImage:isAnimatedImage]; + isAnimatedImage:isAnimatedImage + isVectorImage:NO]; } } +- (void)test17ThatPDFWorks { + NSURL *pdfURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"TestImage" withExtension:@"pdf"]; + [self verifyCoder:[SDImageIOCoder sharedCoder] + withLocalImageURL:pdfURL + supportsEncoding:NO + encodingFormat:SDImageFormatUndefined + isAnimatedImage:NO + isVectorImage:YES]; +} + - (void)verifyCoder:(id)coder withLocalImageURL:(NSURL *)imageUrl supportsEncoding:(BOOL)supportsEncoding isAnimatedImage:(BOOL)isAnimated { - [self verifyCoder:coder withLocalImageURL:imageUrl supportsEncoding:supportsEncoding encodingFormat:SDImageFormatUndefined isAnimatedImage:isAnimated]; + [self verifyCoder:coder withLocalImageURL:imageUrl supportsEncoding:supportsEncoding encodingFormat:SDImageFormatUndefined isAnimatedImage:isAnimated isVectorImage:NO]; } - (void)verifyCoder:(id)coder withLocalImageURL:(NSURL *)imageUrl supportsEncoding:(BOOL)supportsEncoding encodingFormat:(SDImageFormat)encodingFormat - isAnimatedImage:(BOOL)isAnimated { + isAnimatedImage:(BOOL)isAnimated + isVectorImage:(BOOL)isVector { NSData *inputImageData = [NSData dataWithContentsOfURL:imageUrl]; expect(inputImageData).toNot.beNil(); SDImageFormat inputImageFormat = [NSData sd_imageFormatForImageData:inputImageData]; @@ -204,7 +216,18 @@ withLocalImageURL:(NSURL *)imageUrl CGFloat pixelHeight = inputImage.size.height; expect(pixelWidth).beGreaterThan(0); expect(pixelHeight).beGreaterThan(0); - // check thumnail with scratch + // check vector format supports thumbnail with screen size + if (isVector) { +#if SD_UIKIT + CGFloat maxScreenSize = MAX(UIScreen.mainScreen.bounds.size.width, UIScreen.mainScreen.bounds.size.height); +#else + CGFloat maxScreenSize = MAX(NSScreen.mainScreen.frame.size.width, NSScreen.mainScreen.frame.size.height); +#endif + expect(pixelWidth).equal(maxScreenSize); + expect(pixelHeight).equal(maxScreenSize); + } + + // check thumbnail with scratch CGFloat thumbnailWidth = 50; CGFloat thumbnailHeight = 50; UIImage *thumbImage = [coder decodedImageWithData:inputImageData options:@{ @@ -213,7 +236,7 @@ withLocalImageURL:(NSURL *)imageUrl }]; expect(thumbImage).toNot.beNil(); expect(thumbImage.size).equal(CGSizeMake(thumbnailWidth, thumbnailHeight)); - // check thumnail with aspect ratio limit + // check thumbnail with aspect ratio limit thumbImage = [coder decodedImageWithData:inputImageData options:@{ SDImageCoderDecodeThumbnailPixelSize : @(CGSizeMake(thumbnailWidth, thumbnailHeight)), SDImageCoderDecodePreserveAspectRatio : @(YES) From ef2373668e1db302502aa23c9d43291d38b41fe6 Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Thu, 30 Jan 2020 15:56:25 +0800 Subject: [PATCH 07/11] Fix the Xcode 10 support using runtime selector, the force decode feature does not process on vector image format --- SDWebImage/Core/SDImageCoderHelper.m | 4 ++++ SDWebImage/Core/UIImage+Metadata.m | 28 +++++++++++++++++----------- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/SDWebImage/Core/SDImageCoderHelper.m b/SDWebImage/Core/SDImageCoderHelper.m index c29685a9..7bba1025 100644 --- a/SDWebImage/Core/SDImageCoderHelper.m +++ b/SDWebImage/Core/SDImageCoderHelper.m @@ -575,6 +575,10 @@ static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to over if (image.sd_isAnimated) { return NO; } + // do not decode vector images + if (image.sd_isVector) { + return NO; + } return YES; } diff --git a/SDWebImage/Core/UIImage+Metadata.m b/SDWebImage/Core/UIImage+Metadata.m index ef63c41f..2d24c4e2 100644 --- a/SDWebImage/Core/UIImage+Metadata.m +++ b/SDWebImage/Core/UIImage+Metadata.m @@ -32,25 +32,31 @@ return (self.images != nil); } +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Warc-performSelector-leaks" - (BOOL)sd_isVector { if (@available(iOS 13.0, tvOS 13.0, watchOS 6.0, *)) { - if (self.isSymbolImage) { + // Xcode 11 supports symbol image, keep Xcode 10 compatible currently + SEL SymbolSelector = NSSelectorFromString(@"isSymbolImage"); + if ([self respondsToSelector:SymbolSelector] && [self performSelector:SymbolSelector]) { + return YES; + } + // SVG + SEL SVGSelector = NSSelectorFromString(@"_CGSVGDocument"); + if ([self respondsToSelector:SVGSelector] && [self performSelector:SVGSelector]) { return YES; } } -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Warc-performSelector-leaks" - SEL SVGSelector = NSSelectorFromString(@"_CGSVGDocument"); - if ([self respondsToSelector:SVGSelector] && [self performSelector:SVGSelector] != nil) { - return YES; + if (@available(iOS 11.0, tvOS 11.0, watchOS 4.0, *)) { + // PDF + SEL PDFSelector = NSSelectorFromString(@"_CGPDFPage"); + if ([self respondsToSelector:PDFSelector] && [self performSelector:PDFSelector]) { + return YES; + } } - SEL PDFSelector = NSSelectorFromString(@"_CGPDFPage"); - if ([self respondsToSelector:PDFSelector] && [self performSelector:PDFSelector] != nil) { - return YES; - } -#pragma clang diagnostic pop return NO; } +#pragma clang diagnostic pop #else From 5629af83303e6d4c219a4a97a1ec4cfae111df91 Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Thu, 30 Jan 2020 16:09:46 +0800 Subject: [PATCH 08/11] Added `SDWebImageTransformVectorImage`, which can allows the transformer to transform the vector image format, although most coders works for vector format (if you don't grab CGImage), some are not --- SDWebImage/Core/SDWebImageDefine.h | 6 ++++++ SDWebImage/Core/SDWebImageManager.m | 8 ++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/SDWebImage/Core/SDWebImageDefine.h b/SDWebImage/Core/SDWebImageDefine.h index bd4d4e68..568de147 100644 --- a/SDWebImage/Core/SDWebImageDefine.h +++ b/SDWebImage/Core/SDWebImageDefine.h @@ -195,6 +195,12 @@ typedef NS_OPTIONS(NSUInteger, SDWebImageOptions) { * Note if you use this when using the custom cache serializer, or using the transformer, we will also wait until the output image data written is finished. */ SDWebImageWaitStoreCache = 1 << 22, + + /** + * We usually don't apply transform on vector images, because vector images supports dynamically changing to any size, rasterize to a fixed size will loss details. To modify vector images, you can process the vector data at runtime (such as modifying PDF tag / SVG element). + * Use this flag to transform them anyway. + */ + SDWebImageTransformVectorImage = 1 << 23, }; diff --git a/SDWebImage/Core/SDWebImageManager.m b/SDWebImage/Core/SDWebImageManager.m index 97ffb488..4fcb0ab1 100644 --- a/SDWebImage/Core/SDWebImageManager.m +++ b/SDWebImage/Core/SDWebImageManager.m @@ -332,7 +332,9 @@ static id _defaultImageLoader; id transformer = context[SDWebImageContextImageTransformer]; id cacheSerializer = context[SDWebImageContextCacheSerializer]; - BOOL shouldTransformImage = downloadedImage && (!downloadedImage.sd_isAnimated || (options & SDWebImageTransformAnimatedImage)) && transformer; + BOOL shouldTransformImage = downloadedImage && transformer; + shouldTransformImage = shouldTransformImage && (!downloadedImage.sd_isAnimated || (options & SDWebImageTransformAnimatedImage)); + shouldTransformImage = shouldTransformImage && (!downloadedImage.sd_isVector || (options & SDWebImageTransformVectorImage)); BOOL shouldCacheOriginal = downloadedImage && finished; BOOL waitStoreCache = SD_OPTIONS_CONTAINS(options, SDWebImageWaitStoreCache); @@ -380,7 +382,9 @@ static id _defaultImageLoader; NSString *key = [self cacheKeyForURL:url context:context]; id transformer = context[SDWebImageContextImageTransformer]; id cacheSerializer = context[SDWebImageContextCacheSerializer]; - BOOL shouldTransformImage = originalImage && (!originalImage.sd_isAnimated || (options & SDWebImageTransformAnimatedImage)) && transformer; + BOOL shouldTransformImage = originalImage && transformer; + shouldTransformImage = shouldTransformImage && (!originalImage.sd_isAnimated || (options & SDWebImageTransformAnimatedImage)); + shouldTransformImage = shouldTransformImage && (!originalImage.sd_isVector || (options & SDWebImageTransformVectorImage)); BOOL waitStoreCache = SD_OPTIONS_CONTAINS(options, SDWebImageWaitStoreCache); // if available, store transformed image to cache if (shouldTransformImage) { From 4d354c4acdc0ba8e3db1af8fc687a8bf450a46ee Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Wed, 12 Feb 2020 12:13:04 +0800 Subject: [PATCH 09/11] Make the SDAniamtedImage response to the UIImage+Metadata category method, which should return the status matching the behavior --- SDWebImage/Core/SDAnimatedImage.m | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/SDWebImage/Core/SDAnimatedImage.m b/SDWebImage/Core/SDAnimatedImage.m index ce5d5d29..d27e2c4b 100644 --- a/SDWebImage/Core/SDAnimatedImage.m +++ b/SDWebImage/Core/SDAnimatedImage.m @@ -12,6 +12,7 @@ #import "SDImageCodersManager.h" #import "SDImageFrame.h" #import "UIImage+MemoryCacheCost.h" +#import "UIImage+Metadata.h" #import "SDImageAssetManager.h" #import "objc/runtime.h" @@ -298,3 +299,31 @@ static CGFloat SDImageScaleFromPath(NSString *string) { } @end + +@implementation SDAnimatedImage (Metadata) + +- (BOOL)sd_isAnimated { + return YES; +} + +- (NSUInteger)sd_imageLoopCount { + return self.animatedImageLoopCount; +} + +- (void)setSd_imageLoopCount:(NSUInteger)sd_imageLoopCount { + return; +} + +- (SDImageFormat)sd_imageFormat { + return self.animatedImageFormat; +} + +- (void)setSd_imageFormat:(SDImageFormat)sd_imageFormat { + return; +} + +- (BOOL)sd_isVector { + return NO; +} + +@end From 4acd81177bc5d906ab9fad6e48f194ca6ec02b57 Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Wed, 12 Feb 2020 12:35:48 +0800 Subject: [PATCH 10/11] Added macros to expand SPI symbol to Selector, which can make it easy to distinguish and maintain in the future --- SDWebImage/Core/SDAnimatedImageView.m | 4 ++-- SDWebImage/Core/UIImage+Metadata.m | 5 +++-- SDWebImage/Private/SDInternalMacros.h | 12 ++++++++++++ 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/SDWebImage/Core/SDAnimatedImageView.m b/SDWebImage/Core/SDAnimatedImageView.m index e9734383..71ee7e34 100644 --- a/SDWebImage/Core/SDAnimatedImageView.m +++ b/SDWebImage/Core/SDAnimatedImageView.m @@ -470,10 +470,10 @@ // NSImageView use a subview. We need this subview's layer for actual rendering. // Why using this design may because of properties like `imageAlignment` and `imageScaling`, which it's not available for UIImageView.contentMode (it's impossible to align left and keep aspect ratio at the same time) - (NSView *)imageView { - NSImageView *imageView = imageView = objc_getAssociatedObject(self, NSSelectorFromString(@"_imageView")); + NSImageView *imageView = imageView = objc_getAssociatedObject(self, SD_SEL_SPI(imageView)); if (!imageView) { // macOS 10.14 - imageView = objc_getAssociatedObject(self, NSSelectorFromString(@"_imageSubview")); + imageView = objc_getAssociatedObject(self, SD_SEL_SPI(imageSubview)); } return imageView; } diff --git a/SDWebImage/Core/UIImage+Metadata.m b/SDWebImage/Core/UIImage+Metadata.m index 2d24c4e2..09724236 100644 --- a/SDWebImage/Core/UIImage+Metadata.m +++ b/SDWebImage/Core/UIImage+Metadata.m @@ -8,6 +8,7 @@ #import "UIImage+Metadata.h" #import "NSImage+Compatibility.h" +#import "SDInternalMacros.h" #import "objc/runtime.h" @implementation UIImage (Metadata) @@ -42,14 +43,14 @@ return YES; } // SVG - SEL SVGSelector = NSSelectorFromString(@"_CGSVGDocument"); + SEL SVGSelector = SD_SEL_SPI(CGSVGDocument); if ([self respondsToSelector:SVGSelector] && [self performSelector:SVGSelector]) { return YES; } } if (@available(iOS 11.0, tvOS 11.0, watchOS 4.0, *)) { // PDF - SEL PDFSelector = NSSelectorFromString(@"_CGPDFPage"); + SEL PDFSelector = SD_SEL_SPI(CGPDFPage); if ([self respondsToSelector:PDFSelector] && [self performSelector:PDFSelector]) { return YES; } diff --git a/SDWebImage/Private/SDInternalMacros.h b/SDWebImage/Private/SDInternalMacros.h index 837d77b0..aad700f8 100644 --- a/SDWebImage/Private/SDInternalMacros.h +++ b/SDWebImage/Private/SDInternalMacros.h @@ -21,6 +21,18 @@ #define SD_OPTIONS_CONTAINS(options, value) (((options) & (value)) == (value)) #endif +#ifndef SD_CSTRING +#define SD_CSTRING(str) #str +#endif + +#ifndef SD_NSSTRING +#define SD_NSSTRING(str) @(SD_CSTRING(str)) +#endif + +#ifndef SD_SEL_SPI +#define SD_SEL_SPI(name) NSSelectorFromString([NSString stringWithFormat:@"_%@", SD_NSSTRING(name)]) +#endif + #ifndef weakify #define weakify(...) \ sd_keywordify \ From ac4dcbe316a91d1cc9f06d2687f88790eaa4bbb4 Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Wed, 26 Feb 2020 12:14:50 +0800 Subject: [PATCH 11/11] Copy the SVG detection from SVGCoder to the utils --- SDWebImage/Core/NSData+ImageContentType.m | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/SDWebImage/Core/NSData+ImageContentType.m b/SDWebImage/Core/NSData+ImageContentType.m index 4db1ff5b..f9014480 100644 --- a/SDWebImage/Core/NSData+ImageContentType.m +++ b/SDWebImage/Core/NSData+ImageContentType.m @@ -17,6 +17,7 @@ // Currently Image/IO does not support WebP #define kSDUTTypeWebP ((__bridge CFStringRef)@"public.webp") +#define kSVGTagEnd @"" @implementation NSData (ImageContentType) @@ -74,6 +75,15 @@ } } } + case 0x3C: { + if (data.length > 100) { + // Check end with SVG tag + NSString *testString = [[NSString alloc] initWithData:[data subdataWithRange:NSMakeRange(data.length - 100, 100)] encoding:NSASCIIStringEncoding]; + if ([testString containsString:kSVGTagEnd]) { + return SDImageFormatSVG; + } + } + } } return SDImageFormatUndefined; }