diff --git a/SDWebImage/Core/SDImageIOAnimatedCoder.m b/SDWebImage/Core/SDImageIOAnimatedCoder.m index 22c050d5..922d0186 100644 --- a/SDWebImage/Core/SDImageIOAnimatedCoder.m +++ b/SDWebImage/Core/SDImageIOAnimatedCoder.m @@ -197,13 +197,6 @@ static NSString * kSDCGImageDestinationRequestedFileSize = @"kCGImageDestination if (!exifOrientation) { exifOrientation = kCGImagePropertyOrientationUp; } - - CFStringRef uttype = CGImageSourceGetType(source); - // Check vector format - BOOL isVector = NO; - if ([NSData sd_imageFormatFromUTType:uttype] == SDImageFormatPDF) { - isVector = YES; - } NSMutableDictionary *decodingOptions; if (options) { @@ -214,22 +207,6 @@ static NSString * kSDCGImageDestinationRequestedFileSize = @"kCGImageDestination CGImageRef imageRef; BOOL createFullImage = thumbnailSize.width == 0 || thumbnailSize.height == 0 || pixelWidth == 0 || pixelHeight == 0 || (pixelWidth <= thumbnailSize.width && pixelHeight <= thumbnailSize.height); if (createFullImage) { - 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; - decodingOptions[kSDCGImageSourceRasterizationDPI] = @(rasterizationDPI); - } imageRef = CGImageSourceCreateImageAtIndex(source, index, (__bridge CFDictionaryRef)[decodingOptions copy]); } else { decodingOptions[(__bridge NSString *)kCGImageSourceCreateThumbnailWithTransform] = @(preserveAspectRatio); diff --git a/SDWebImage/Core/SDImageIOCoder.m b/SDWebImage/Core/SDImageIOCoder.m index 7b5b4a8a..42f70846 100644 --- a/SDWebImage/Core/SDImageIOCoder.m +++ b/SDWebImage/Core/SDImageIOCoder.m @@ -13,6 +13,8 @@ #import "UIImage+Metadata.h" #import "SDImageIOAnimatedCoderInternal.h" +// Specify DPI for vector format in CGImageSource, like PDF +static NSString * kSDCGImageSourceRasterizationDPI = @"kCGImageSourceRasterizationDPI"; // Specify File Size for lossy format encoding, like JPEG static NSString * kSDCGImageDestinationRequestedFileSize = @"kCGImageDestinationRequestedFileSize"; @@ -52,6 +54,31 @@ static NSString * kSDCGImageDestinationRequestedFileSize = @"kCGImageDestination return coder; } +#pragma mark - Utils ++ (CGRect)boxRectFromPDFFData:(nonnull NSData *)data { + CGDataProviderRef provider = CGDataProviderCreateWithCFData((__bridge CFDataRef)data); + if (!provider) { + return CGRectZero; + } + CGPDFDocumentRef document = CGPDFDocumentCreateWithProvider(provider); + CGDataProviderRelease(provider); + if (!document) { + return CGRectZero; + } + + // `CGPDFDocumentGetPage` page number is 1-indexed. + CGPDFPageRef page = CGPDFDocumentGetPage(document, 1); + if (!page) { + CGPDFDocumentRelease(document); + return CGRectZero; + } + + CGRect boxRect = CGPDFPageGetBoxRect(page, kCGPDFMediaBox); + CGPDFDocumentRelease(document); + + return boxRect; +} + #pragma mark - Decode - (BOOL)canDecodeFromData:(nullable NSData *)data { return YES; @@ -88,13 +115,39 @@ static NSString * kSDCGImageDestinationRequestedFileSize = @"kCGImageDestination return nil; } - UIImage *image = [SDImageIOAnimatedCoder createFrameAtIndex:0 source:source scale:scale preserveAspectRatio:preserveAspectRatio thumbnailSize:thumbnailSize options:nil]; + CFStringRef uttype = CGImageSourceGetType(source); + SDImageFormat imageFormat = [NSData sd_imageFormatFromUTType:uttype]; + // Check vector format + NSDictionary *decodingOptions = nil; + if (imageFormat == SDImageFormatPDF) { + // Use 72 DPI (1:1 inch to pixel) by default, matching Apple's PDFKit behavior + NSUInteger rasterizationDPI = 72; + CGFloat maxPixelSize = MAX(thumbnailSize.width, thumbnailSize.height); + if (maxPixelSize > 0) { + // Calculate DPI based on PDF box and pixel size + CGRect boxRect = [self.class boxRectFromPDFFData:data]; + CGFloat maxBoxSize = MAX(boxRect.size.width, boxRect.size.height); + if (maxBoxSize > 0) { + rasterizationDPI = rasterizationDPI * (maxPixelSize / maxBoxSize); + } + } + decodingOptions = @{ + // This option will cause ImageIO return the pixel size from `CGImageSourceCopyProperties` + // If not provided, it always return 0 size + kSDCGImageSourceRasterizationDPI : @(rasterizationDPI), + }; + // Already calculated DPI, avoid re-calculation based on thumbnail information + preserveAspectRatio = YES; + thumbnailSize = CGSizeZero; + } + + UIImage *image = [SDImageIOAnimatedCoder createFrameAtIndex:0 source:source scale:scale preserveAspectRatio:preserveAspectRatio thumbnailSize:thumbnailSize options:decodingOptions]; CFRelease(source); if (!image) { return nil; } - image.sd_imageFormat = [NSData sd_imageFormatForImageData:data]; + image.sd_imageFormat = imageFormat; return image; } diff --git a/Tests/Tests/SDImageCoderTests.m b/Tests/Tests/SDImageCoderTests.m index fa8795ac..f796e567 100644 --- a/Tests/Tests/SDImageCoderTests.m +++ b/Tests/Tests/SDImageCoderTests.m @@ -11,6 +11,12 @@ #import "UIColor+SDHexString.h" #import +@interface SDImageIOCoder () + ++ (CGRect)boxRectFromPDFFData:(nonnull NSData *)data; + +@end + @interface SDWebImageDecoderTests : SDTestCase @end @@ -437,15 +443,14 @@ withLocalImageURL:(NSURL *)imageUrl CGFloat pixelHeight = inputImage.size.height; expect(pixelWidth).beGreaterThan(0); expect(pixelHeight).beGreaterThan(0); - // check vector format supports thumbnail with screen size + // check vector format should use 72 DPI 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); + CGRect boxRect = [SDImageIOCoder boxRectFromPDFFData:inputImageData]; + expect(boxRect.size.width).beGreaterThan(0); + expect(boxRect.size.height).beGreaterThan(0); + // Since 72 DPI is 1:1 from inch size to pixel size + expect(boxRect.size.width).equal(pixelWidth); + expect(boxRect.size.height).equal(pixelHeight); } // check thumbnail with scratch