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:
commit
bc3f09ccbd
|
@ -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]) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue