Merge pull request #3436 from dreampiggy/bugfix/ios16_pdf_use_coregraphics

Use CoreGraphics to decode PDF instead of ImageIO to solve iOS 16's issue
This commit is contained in:
DreamPiggy 2022-11-11 14:17:17 +08:00 committed by GitHub
commit bc3f09ccbd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
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.class 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