Use CoreGraphics to decode PDF instead of ImageIO to solve iOS 16's issue

We no longer use ImageIO to decode PDF, seems they don't maintain that kSDCGImageSourceRasterizationDPI
Copy the code from SDWebImagePDFCoder, need to update
This commit is contained in:
DreamPiggy 2022-11-10 23:47:34 +08:00
parent 8a26bb3f3d
commit f4706453d6
3 changed files with 103 additions and 46 deletions

View File

@ -18,8 +18,6 @@
#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
static NSString * kSDCGImageDestinationRequestedFileSize = @"kCGImageDestinationRequestedFileSize";
@ -568,6 +566,12 @@ static CGImageRef __nullable SDCGImageCreateCopy(CGImageRef cg_nullable image) {
return nil;
}
NSMutableDictionary *properties = [NSMutableDictionary dictionary];
#if SD_UIKIT || SD_WATCH
CGImagePropertyOrientation exifOrientation = [SDImageCoderHelper exifOrientationFromImageOrientation:image.imageOrientation];
#else
CGImagePropertyOrientation exifOrientation = kCGImagePropertyOrientationUp;
#endif
properties[(__bridge NSString *)kCGImagePropertyOrientation] = @(exifOrientation);
// Encoding Options
double compressionQuality = 1;
if (options[SDImageCoderEncodeCompressionQuality]) {

View File

@ -10,13 +10,12 @@
#import "SDImageCoderHelper.h"
#import "NSImage+Compatibility.h"
#import "UIImage+Metadata.h"
#import "SDImageGraphics.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
static NSString * kSDCGImageDestinationRequestedFileSize = @"kCGImageDestinationRequestedFileSize";
@ -57,29 +56,65 @@ static NSString * kSDCGImageDestinationRequestedFileSize = @"kCGImageDestination
return coder;
}
#pragma mark - Utils
+ (CGRect)boxRectFromPDFFData:(nonnull NSData *)data {
#pragma mark - Bitmap PDF representation
- (UIImage *)createBitmapPDFWithData:(nonnull NSData *)data pageNumber:(NSUInteger)pageNumber targetSize:(CGSize)targetSize preserveAspectRatio:(BOOL)preserveAspectRatio {
NSParameterAssert(data);
UIImage *image;
CGDataProviderRef provider = CGDataProviderCreateWithCFData((__bridge CFDataRef)data);
if (!provider) {
return CGRectZero;
return nil;
}
CGPDFDocumentRef document = CGPDFDocumentCreateWithProvider(provider);
CGDataProviderRelease(provider);
if (!document) {
return CGRectZero;
return nil;
}
// `CGPDFDocumentGetPage` page number is 1-indexed.
CGPDFPageRef page = CGPDFDocumentGetPage(document, 1);
CGPDFPageRef page = CGPDFDocumentGetPage(document, pageNumber + 1);
if (!page) {
CGPDFDocumentRelease(document);
return CGRectZero;
return nil;
}
CGRect boxRect = CGPDFPageGetBoxRect(page, kCGPDFMediaBox);
CGPDFBox box = kCGPDFMediaBox;
CGRect rect = CGPDFPageGetBoxRect(page, box);
CGRect targetRect = rect;
if (!CGSizeEqualToSize(targetSize, CGSizeZero)) {
targetRect = CGRectMake(0, 0, targetSize.width, targetSize.height);
}
CGFloat xRatio = targetRect.size.width / rect.size.width;
CGFloat yRatio = targetRect.size.height / rect.size.height;
CGFloat xScale = preserveAspectRatio ? MIN(xRatio, yRatio) : xRatio;
CGFloat yScale = preserveAspectRatio ? MIN(xRatio, yRatio) : yRatio;
// `CGPDFPageGetDrawingTransform` will only scale down, but not scale up, so we need calculate the actual scale again
CGRect drawRect = CGRectMake( 0, 0, targetRect.size.width / xScale, targetRect.size.height / yScale);
CGAffineTransform scaleTransform = CGAffineTransformMakeScale(xScale, yScale);
CGAffineTransform transform = CGPDFPageGetDrawingTransform(page, box, drawRect, 0, preserveAspectRatio);
SDGraphicsBeginImageContextWithOptions(targetRect.size, NO, 0);
CGContextRef context = SDGraphicsGetCurrentContext();
#if SD_UIKIT || SD_WATCH
// Core Graphics coordinate system use the bottom-left, UIKit use the flipped one
CGContextTranslateCTM(context, 0, targetRect.size.height);
CGContextScaleCTM(context, 1, -1);
#endif
CGContextConcatCTM(context, scaleTransform);
CGContextConcatCTM(context, transform);
CGContextDrawPDFPage(context, page);
image = SDGraphicsGetImageFromCurrentImageContext();
SDGraphicsEndImageContext();
CGPDFDocumentRelease(document);
return boxRect;
return image;
}
#pragma mark - Decode
@ -113,6 +148,31 @@ static NSString * kSDCGImageDestinationRequestedFileSize = @"kCGImageDestination
preserveAspectRatio = preserveAspectRatioValue.boolValue;
}
// Check vector format
if ([NSData sd_imageFormatForImageData:data] == SDImageFormatPDF) {
// History before iOS 16, ImageIO can decode PDF with rasterization size, but can't ever :(
// So, use CoreGraphics to decode PDF (copy code from SDWebImagePDFCoder, may do refactor in the future)
UIImage *image;
NSUInteger pageNumber = 0; // Still use first page, may added options is user want
#if SD_MAC
// If don't use thumbnail, prefers the built-in generation of vector image
// macOS's `NSImage` supports PDF built-in rendering
if (thumbnailSize.width == 0 || thumbnailSize.height == 0) {
NSPDFImageRep *imageRep = [[NSPDFImageRep alloc] initWithData:data];
if (imageRep) {
imageRep.currentPage = pageNumber;
image = [[NSImage alloc] initWithSize:imageRep.size];
[image addRepresentation:imageRep];
image.sd_imageFormat = SDImageFormatPDF;
return image;
}
}
#endif
image = [self createBitmapPDFWithData:data pageNumber:pageNumber targetSize:thumbnailSize preserveAspectRatio:preserveAspectRatio];
image.sd_imageFormat = SDImageFormatPDF;
return image;
}
BOOL lazyDecode = YES; // Defaults YES for static image coder
NSNumber *lazyDecodeValue = options[SDImageCoderDecodeUseLazyDecoding];
if (lazyDecodeValue != nil) {
@ -150,35 +210,9 @@ static NSString * kSDCGImageDestinationRequestedFileSize = @"kCGImageDestination
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 lazyDecode:lazyDecode options:decodingOptions];
UIImage *image = [SDImageIOAnimatedCoder createFrameAtIndex:0 source:source scale:scale preserveAspectRatio:preserveAspectRatio thumbnailSize:thumbnailSize lazyDecode:lazyDecode options:nil];
CFRelease(source);
if (!image) {
return nil;
}
image.sd_imageFormat = imageFormat;
return image;

View File

@ -11,12 +11,6 @@
#import "UIColor+SDHexString.h"
#import <SDWebImageWebPCoder/SDWebImageWebPCoder.h>
@interface SDImageIOCoder ()
+ (CGRect)boxRectFromPDFFData:(nonnull NSData *)data;
@end
@interface SDWebImageDecoderTests : SDTestCase
@end
@ -484,7 +478,7 @@ withLocalImageURL:(NSURL *)imageUrl
expect(pixelHeight).beGreaterThan(0);
// check vector format should use 72 DPI
if (isVector) {
CGRect boxRect = [SDImageIOCoder boxRectFromPDFFData:inputImageData];
CGRect boxRect = [self boxRectFromPDFData: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
@ -564,4 +558,29 @@ withLocalImageURL:(NSURL *)imageUrl
return thumbnailImages;
}
#pragma mark - Utils
- (CGRect)boxRectFromPDFData:(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;
}
@end