Skip to content

Commit 36eb9b0

Browse files
committed
Add a option to allow advanced user to prefer bitmap version, instead of vector image format for PDF. Do refactory for implementation
1 parent 4c1e864 commit 36eb9b0

File tree

3 files changed

+149
-21
lines changed

3 files changed

+149
-21
lines changed

SDWebImagePDFCoder/Classes/SDImagePDFCoder.m

Lines changed: 141 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
#import "SDImagePDFCoder.h"
99
#import "SDWebImagePDFCoderDefine.h"
10+
#import "objc/runtime.h"
1011

1112
#define SD_FOUR_CC(c1,c2,c3,c4) ((uint32_t)(((c4) << 24) | ((c3) << 16) | ((c2) << 8) | (c1)))
1213

@@ -22,6 +23,92 @@ + (instancetype)_imageWithCGPDFPage:(CGPDFPageRef)page scale:(double)scale orien
2223
@end
2324
#endif
2425

26+
#if SD_MAC
27+
static void *kNSGraphicsContextScaleFactorKey;
28+
29+
static CGContextRef SDCGContextCreateBitmapContext(CGSize size, BOOL opaque, CGFloat scale) {
30+
if (scale == 0) {
31+
// Match `UIGraphicsBeginImageContextWithOptions`, reset to the scale factor of the device’s main screen if scale is 0.
32+
scale = [NSScreen mainScreen].backingScaleFactor;
33+
}
34+
size_t width = ceil(size.width * scale);
35+
size_t height = ceil(size.height * scale);
36+
if (width < 1 || height < 1) return NULL;
37+
38+
//pre-multiplied BGRA for non-opaque, BGRX for opaque, 8-bits per component, as Apple's doc
39+
CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB();
40+
CGImageAlphaInfo alphaInfo = kCGBitmapByteOrder32Host | (opaque ? kCGImageAlphaNoneSkipFirst : kCGImageAlphaPremultipliedFirst);
41+
CGContextRef context = CGBitmapContextCreate(NULL, width, height, 8, 0, space, kCGBitmapByteOrderDefault | alphaInfo);
42+
CGColorSpaceRelease(space);
43+
if (!context) {
44+
return NULL;
45+
}
46+
CGContextScaleCTM(context, scale, scale);
47+
48+
return context;
49+
}
50+
#endif
51+
52+
static void SDGraphicsBeginImageContextWithOptions(CGSize size, BOOL opaque, CGFloat scale) {
53+
#if SD_UIKIT || SD_WATCH
54+
UIGraphicsBeginImageContextWithOptions(size, opaque, scale);
55+
#else
56+
CGContextRef context = SDCGContextCreateBitmapContext(size, opaque, scale);
57+
if (!context) {
58+
return;
59+
}
60+
NSGraphicsContext *graphicsContext = [NSGraphicsContext graphicsContextWithCGContext:context flipped:NO];
61+
objc_setAssociatedObject(graphicsContext, &kNSGraphicsContextScaleFactorKey, @(scale), OBJC_ASSOCIATION_RETAIN);
62+
CGContextRelease(context);
63+
[NSGraphicsContext saveGraphicsState];
64+
NSGraphicsContext.currentContext = graphicsContext;
65+
#endif
66+
}
67+
68+
static CGContextRef SDGraphicsGetCurrentContext(void) {
69+
#if SD_UIKIT || SD_WATCH
70+
return UIGraphicsGetCurrentContext();
71+
#else
72+
return NSGraphicsContext.currentContext.CGContext;
73+
#endif
74+
}
75+
76+
static void SDGraphicsEndImageContext(void) {
77+
#if SD_UIKIT || SD_WATCH
78+
UIGraphicsEndImageContext();
79+
#else
80+
[NSGraphicsContext restoreGraphicsState];
81+
#endif
82+
}
83+
84+
static UIImage * SDGraphicsGetImageFromCurrentImageContext(void) {
85+
#if SD_UIKIT || SD_WATCH
86+
return UIGraphicsGetImageFromCurrentImageContext();
87+
#else
88+
NSGraphicsContext *context = NSGraphicsContext.currentContext;
89+
CGContextRef contextRef = context.CGContext;
90+
if (!contextRef) {
91+
return nil;
92+
}
93+
CGImageRef imageRef = CGBitmapContextCreateImage(contextRef);
94+
if (!imageRef) {
95+
return nil;
96+
}
97+
CGFloat scale = 0;
98+
NSNumber *scaleFactor = objc_getAssociatedObject(context, &kNSGraphicsContextScaleFactorKey);
99+
if ([scaleFactor isKindOfClass:[NSNumber class]]) {
100+
scale = scaleFactor.doubleValue;
101+
}
102+
if (!scale) {
103+
// reset to the scale factor of the device’s main screen if scale is 0.
104+
scale = [NSScreen mainScreen].backingScaleFactor;
105+
}
106+
NSImage *image = [[NSImage alloc] initWithCGImage:imageRef scale:scale orientation:kCGImagePropertyOrientationUp];
107+
CGImageRelease(imageRef);
108+
return image;
109+
#endif
110+
}
111+
25112
@implementation SDImagePDFCoder
26113

27114
+ (SDImagePDFCoder *)sharedCoder {
@@ -43,13 +130,17 @@ - (UIImage *)decodedImageWithData:(NSData *)data options:(SDImageCoderOptions *)
43130
}
44131

45132
NSUInteger pageNumber = 0;
133+
BOOL preferredBitmap = NO;
46134
CGSize imageSize = CGSizeZero;
47135
BOOL preserveAspectRatio = YES;
48136
// Parse args
49137
SDWebImageContext *context = options[SDImageCoderWebImageContext];
50138
if (context[SDWebImageContextPDFPageNumber]) {
51139
pageNumber = [context[SDWebImageContextPDFPageNumber] unsignedIntegerValue];
52140
}
141+
if (context[SDWebImageContextPDFPerferredBitmap]) {
142+
preferredBitmap = [context[SDWebImageContextPDFPerferredBitmap] boolValue];
143+
}
53144
if (context[SDWebImageContextPDFImageSize]) {
54145
NSValue *sizeValue = context[SDWebImageContextPDFImageSize];
55146
#if SD_UIKIT
@@ -62,7 +153,14 @@ - (UIImage *)decodedImageWithData:(NSData *)data options:(SDImageCoderOptions *)
62153
preserveAspectRatio = [context[SDWebImageContextPDFImagePreserveAspectRatio] boolValue];
63154
}
64155

65-
UIImage *image = [self sd_createPDFImageWithData:data pageNumber:pageNumber targetSize:imageSize preserveAspectRatio:preserveAspectRatio];
156+
UIImage *image;
157+
if (!preferredBitmap && [self.class supportsVectorPDFImage]) {
158+
image = [self createVectorPDFWithData:data pageNumber:pageNumber];
159+
} else {
160+
image = [self createBitmapPDFWithData:data pageNumber:pageNumber targetSize:imageSize preserveAspectRatio:preserveAspectRatio];
161+
}
162+
163+
image.sd_imageFormat = SDImageFormatPDF;
66164

67165
return image;
68166
}
@@ -76,8 +174,8 @@ - (NSData *)encodedDataWithImage:(UIImage *)image format:(SDImageFormat)format o
76174
return nil;
77175
}
78176

79-
// Using Core Graphics to draw PDF but not PDFKit(iOS 11+/macOS 10.4+) to keep old firmware compatible
80-
- (UIImage *)sd_createPDFImageWithData:(nonnull NSData *)data pageNumber:(NSUInteger)pageNumber targetSize:(CGSize)targetSize preserveAspectRatio:(BOOL)preserveAspectRatio {
177+
#pragma mark - Vector PDF representation
178+
- (UIImage *)createVectorPDFWithData:(nonnull NSData *)data pageNumber:(NSUInteger)pageNumber {
81179
NSParameterAssert(data);
82180
UIImage *image;
83181

@@ -87,12 +185,10 @@ - (UIImage *)sd_createPDFImageWithData:(nonnull NSData *)data pageNumber:(NSUInt
87185
if (!imageRep) {
88186
return nil;
89187
}
90-
imageRep.currentPage = pageNumber
188+
imageRep.currentPage = pageNumber;
91189
image = [[NSImage alloc] initWithSize:imageRep.size];
92190
[image addRepresentation:imageRep];
93-
94191
#else
95-
96192
CGDataProviderRef provider = CGDataProviderCreateWithCFData((__bridge CFDataRef)data);
97193
if (!provider) {
98194
return nil;
@@ -110,11 +206,33 @@ - (UIImage *)sd_createPDFImageWithData:(nonnull NSData *)data pageNumber:(NSUInt
110206
return nil;
111207
}
112208

113-
// Check if we can use built-in PDF image support, instead of draw bitmap
114-
if ([[self class] supportsBuiltInPDFImage]) {
115-
UIImage *image = [UIImage _imageWithCGPDFPage:page];
209+
image = [UIImage _imageWithCGPDFPage:page];
210+
CGPDFDocumentRelease(document);
211+
#endif
212+
213+
return image;
214+
}
215+
216+
#pragma mark - Bitmap PDF representation
217+
- (UIImage *)createBitmapPDFWithData:(nonnull NSData *)data pageNumber:(NSUInteger)pageNumber targetSize:(CGSize)targetSize preserveAspectRatio:(BOOL)preserveAspectRatio {
218+
NSParameterAssert(data);
219+
UIImage *image;
220+
221+
CGDataProviderRef provider = CGDataProviderCreateWithCFData((__bridge CFDataRef)data);
222+
if (!provider) {
223+
return nil;
224+
}
225+
CGPDFDocumentRef document = CGPDFDocumentCreateWithProvider(provider);
226+
CGDataProviderRelease(provider);
227+
if (!document) {
228+
return nil;
229+
}
230+
231+
// `CGPDFDocumentGetPage` page number is 1-indexed.
232+
CGPDFPageRef page = CGPDFDocumentGetPage(document, pageNumber + 1);
233+
if (!page) {
116234
CGPDFDocumentRelease(document);
117-
return image;
235+
return nil;
118236
}
119237

120238
CGPDFBox box = kCGPDFCropBox;
@@ -134,31 +252,33 @@ - (UIImage *)sd_createPDFImageWithData:(nonnull NSData *)data pageNumber:(NSUInt
134252
CGAffineTransform scaleTransform = CGAffineTransformMakeScale(xScale, yScale);
135253
CGAffineTransform transform = CGPDFPageGetDrawingTransform(page, box, drawRect, 0, preserveAspectRatio);
136254

137-
UIGraphicsBeginImageContextWithOptions(targetRect.size, NO, 0);
138-
CGContextRef context = UIGraphicsGetCurrentContext();
255+
SDGraphicsBeginImageContextWithOptions(targetRect.size, NO, 0);
256+
CGContextRef context = SDGraphicsGetCurrentContext();
139257

140-
// Core Graphics coordinate system use the bottom-left, iOS use the flipped one
258+
#if SD_UIKIT
259+
// Core Graphics coordinate system use the bottom-left, UIkit use the flipped one
141260
CGContextTranslateCTM(context, 0, targetRect.size.height);
142261
CGContextScaleCTM(context, 1, -1);
262+
#endif
143263

144264
CGContextConcatCTM(context, scaleTransform);
145265
CGContextConcatCTM(context, transform);
146266

147267
CGContextDrawPDFPage(context, page);
148268

149-
image = UIGraphicsGetImageFromCurrentImageContext();
150-
UIGraphicsEndImageContext();
269+
image = SDGraphicsGetImageFromCurrentImageContext();
270+
SDGraphicsEndImageContext();
151271

152272
CGPDFDocumentRelease(document);
153-
#endif
154-
155-
image.sd_imageFormat = SDImageFormatPDF;
156273

157274
return image;
158275
}
159276

160-
#if SD_UIKIT
161-
+ (BOOL)supportsBuiltInPDFImage {
277+
+ (BOOL)supportsVectorPDFImage {
278+
#if SD_MAC
279+
// macOS's `NSImage` supports PDF built-in rendering
280+
return YES;
281+
#else
162282
static dispatch_once_t onceToken;
163283
static BOOL supports;
164284
dispatch_once(&onceToken, ^{
@@ -170,8 +290,8 @@ + (BOOL)supportsBuiltInPDFImage {
170290
}
171291
});
172292
return supports;
173-
}
174293
#endif
294+
}
175295

176296
+ (BOOL)isPDFFormatForData:(NSData *)data {
177297
if (!data) {

SDWebImagePDFCoder/Classes/SDWebImagePDFCoderDefine.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,13 @@ NS_ASSUME_NONNULL_BEGIN
1515
*/
1616
FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextPDFPageNumber;
1717

18+
/**
19+
A BOOL value which specify whether we prefer the actual bitmap representation instead of vector representation for PDF image. This is because the UIImage on iOS 11+ (NSImgae on macOS) can use the vector image format, which support dynamic scale without losing any detail. However, for some image processing logic, user may need the actual bitmap representation to manage pixels. Also, for lower firmware on iOS, the `UIImage` does not support vector rendering, user may want to handle them using the same code. (NSNumber)
20+
If you don't provide this value, use NO for default value and prefer the vector format when possible.
21+
*/
22+
FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextPDFPerferredBitmap;
23+
24+
#pragma mark - Bitmap Representation Options
1825
/**
1926
A CGSize raw value which specify the desired PDF image size during image loading. Because vector image like PDF format, may not contains a fixed size, or you want to get a larger size bitmap representation UIImage. (NSValue)
2027
If you don't provide this value, use the PDF cropBox size instead.

SDWebImagePDFCoder/Classes/SDWebImagePDFCoderDefine.m

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,6 @@
88
#import "SDWebImagePDFCoderDefine.h"
99

1010
SDWebImageContextOption _Nonnull const SDWebImageContextPDFPageNumber = @"pdfPageNumber";
11+
SDWebImageContextOption _Nonnull const SDWebImageContextPDFPerferredBitmap = @"pdfPerferredBitmap";
1112
SDWebImageContextOption _Nonnull const SDWebImageContextPDFImageSize = @"pdfImageSize";
1213
SDWebImageContextOption _Nonnull const SDWebImageContextPDFImagePreserveAspectRatio = @"pdfImagePreserveAspectRatio";

0 commit comments

Comments
 (0)