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 <ImageIO/ImageIO.h>
|
||||||
#import <CoreServices/CoreServices.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
|
// Specify File Size for lossy format encoding, like JPEG
|
||||||
static NSString * kSDCGImageDestinationRequestedFileSize = @"kCGImageDestinationRequestedFileSize";
|
static NSString * kSDCGImageDestinationRequestedFileSize = @"kCGImageDestinationRequestedFileSize";
|
||||||
|
|
||||||
|
@ -568,6 +566,12 @@ static CGImageRef __nullable SDCGImageCreateCopy(CGImageRef cg_nullable image) {
|
||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
NSMutableDictionary *properties = [NSMutableDictionary dictionary];
|
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
|
// Encoding Options
|
||||||
double compressionQuality = 1;
|
double compressionQuality = 1;
|
||||||
if (options[SDImageCoderEncodeCompressionQuality]) {
|
if (options[SDImageCoderEncodeCompressionQuality]) {
|
||||||
|
|
|
@ -10,13 +10,12 @@
|
||||||
#import "SDImageCoderHelper.h"
|
#import "SDImageCoderHelper.h"
|
||||||
#import "NSImage+Compatibility.h"
|
#import "NSImage+Compatibility.h"
|
||||||
#import "UIImage+Metadata.h"
|
#import "UIImage+Metadata.h"
|
||||||
|
#import "SDImageGraphics.h"
|
||||||
#import "SDImageIOAnimatedCoderInternal.h"
|
#import "SDImageIOAnimatedCoderInternal.h"
|
||||||
|
|
||||||
#import <ImageIO/ImageIO.h>
|
#import <ImageIO/ImageIO.h>
|
||||||
#import <CoreServices/CoreServices.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
|
// Specify File Size for lossy format encoding, like JPEG
|
||||||
static NSString * kSDCGImageDestinationRequestedFileSize = @"kCGImageDestinationRequestedFileSize";
|
static NSString * kSDCGImageDestinationRequestedFileSize = @"kCGImageDestinationRequestedFileSize";
|
||||||
|
|
||||||
|
@ -57,29 +56,65 @@ static NSString * kSDCGImageDestinationRequestedFileSize = @"kCGImageDestination
|
||||||
return coder;
|
return coder;
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - Utils
|
#pragma mark - Bitmap PDF representation
|
||||||
+ (CGRect)boxRectFromPDFFData:(nonnull NSData *)data {
|
+ (UIImage *)createBitmapPDFWithData:(nonnull NSData *)data pageNumber:(NSUInteger)pageNumber targetSize:(CGSize)targetSize preserveAspectRatio:(BOOL)preserveAspectRatio {
|
||||||
|
NSParameterAssert(data);
|
||||||
|
UIImage *image;
|
||||||
|
|
||||||
CGDataProviderRef provider = CGDataProviderCreateWithCFData((__bridge CFDataRef)data);
|
CGDataProviderRef provider = CGDataProviderCreateWithCFData((__bridge CFDataRef)data);
|
||||||
if (!provider) {
|
if (!provider) {
|
||||||
return CGRectZero;
|
return nil;
|
||||||
}
|
}
|
||||||
CGPDFDocumentRef document = CGPDFDocumentCreateWithProvider(provider);
|
CGPDFDocumentRef document = CGPDFDocumentCreateWithProvider(provider);
|
||||||
CGDataProviderRelease(provider);
|
CGDataProviderRelease(provider);
|
||||||
if (!document) {
|
if (!document) {
|
||||||
return CGRectZero;
|
return nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
// `CGPDFDocumentGetPage` page number is 1-indexed.
|
// `CGPDFDocumentGetPage` page number is 1-indexed.
|
||||||
CGPDFPageRef page = CGPDFDocumentGetPage(document, 1);
|
CGPDFPageRef page = CGPDFDocumentGetPage(document, pageNumber + 1);
|
||||||
if (!page) {
|
if (!page) {
|
||||||
CGPDFDocumentRelease(document);
|
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);
|
CGPDFDocumentRelease(document);
|
||||||
|
|
||||||
return boxRect;
|
return image;
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - Decode
|
#pragma mark - Decode
|
||||||
|
@ -113,6 +148,31 @@ static NSString * kSDCGImageDestinationRequestedFileSize = @"kCGImageDestination
|
||||||
preserveAspectRatio = preserveAspectRatioValue.boolValue;
|
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
|
BOOL lazyDecode = YES; // Defaults YES for static image coder
|
||||||
NSNumber *lazyDecodeValue = options[SDImageCoderDecodeUseLazyDecoding];
|
NSNumber *lazyDecodeValue = options[SDImageCoderDecodeUseLazyDecoding];
|
||||||
if (lazyDecodeValue != nil) {
|
if (lazyDecodeValue != nil) {
|
||||||
|
@ -150,35 +210,9 @@ static NSString * kSDCGImageDestinationRequestedFileSize = @"kCGImageDestination
|
||||||
|
|
||||||
CFStringRef uttype = CGImageSourceGetType(source);
|
CFStringRef uttype = CGImageSourceGetType(source);
|
||||||
SDImageFormat imageFormat = [NSData sd_imageFormatFromUTType:uttype];
|
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);
|
CFRelease(source);
|
||||||
if (!image) {
|
|
||||||
return nil;
|
|
||||||
}
|
|
||||||
|
|
||||||
image.sd_imageFormat = imageFormat;
|
image.sd_imageFormat = imageFormat;
|
||||||
return image;
|
return image;
|
||||||
|
|
|
@ -11,12 +11,6 @@
|
||||||
#import "UIColor+SDHexString.h"
|
#import "UIColor+SDHexString.h"
|
||||||
#import <SDWebImageWebPCoder/SDWebImageWebPCoder.h>
|
#import <SDWebImageWebPCoder/SDWebImageWebPCoder.h>
|
||||||
|
|
||||||
@interface SDImageIOCoder ()
|
|
||||||
|
|
||||||
+ (CGRect)boxRectFromPDFFData:(nonnull NSData *)data;
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
@interface SDWebImageDecoderTests : SDTestCase
|
@interface SDWebImageDecoderTests : SDTestCase
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
@ -484,7 +478,7 @@ withLocalImageURL:(NSURL *)imageUrl
|
||||||
expect(pixelHeight).beGreaterThan(0);
|
expect(pixelHeight).beGreaterThan(0);
|
||||||
// check vector format should use 72 DPI
|
// check vector format should use 72 DPI
|
||||||
if (isVector) {
|
if (isVector) {
|
||||||
CGRect boxRect = [SDImageIOCoder boxRectFromPDFFData:inputImageData];
|
CGRect boxRect = [self boxRectFromPDFData:inputImageData];
|
||||||
expect(boxRect.size.width).beGreaterThan(0);
|
expect(boxRect.size.width).beGreaterThan(0);
|
||||||
expect(boxRect.size.height).beGreaterThan(0);
|
expect(boxRect.size.height).beGreaterThan(0);
|
||||||
// Since 72 DPI is 1:1 from inch size to pixel size
|
// Since 72 DPI is 1:1 from inch size to pixel size
|
||||||
|
@ -564,4 +558,29 @@ withLocalImageURL:(NSURL *)imageUrl
|
||||||
return thumbnailImages;
|
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
|
@end
|
||||||
|
|
Loading…
Reference in New Issue