darling-cocotron/Onyx2D/O2Image+PDF.m
2020-05-12 17:04:16 -04:00

371 lines
13 KiB
Objective-C

#import <Onyx2D/O2ColorSpace+PDF.h>
#import <Onyx2D/O2DataProvider.h>
#import <Onyx2D/O2Image+PDF.h>
#import <Onyx2D/O2PDFArray.h>
#import <Onyx2D/O2PDFContext.h>
#import <Onyx2D/O2PDFDictionary.h>
#import <Onyx2D/O2PDFStream.h>
#if defined(__APPLE__) && !defined(DARLING)
#else
#import <Onyx2D/O2Defines_zlib.h>
#endif
#if ZLIB_PRESENT
#import <zlib.h>
#endif
@implementation O2Image (PDF)
O2ColorRenderingIntent O2ImageRenderingIntentWithName(const char *name) {
if (name == NULL)
return kO2RenderingIntentDefault;
if (strcmp(name, "AbsoluteColorimetric") == 0)
return kO2RenderingIntentAbsoluteColorimetric;
else if (strcmp(name, "RelativeColorimetric") == 0)
return kO2RenderingIntentRelativeColorimetric;
else if (strcmp(name, "Saturation") == 0)
return kO2RenderingIntentSaturation;
else if (strcmp(name, "Perceptual") == 0)
return kO2RenderingIntentPerceptual;
else
return kO2RenderingIntentDefault; // unknown
}
const char *O2ImageNameWithIntent(O2ColorRenderingIntent intent) {
switch (intent) {
case kO2RenderingIntentAbsoluteColorimetric:
return "AbsoluteColorimetric";
case kO2RenderingIntentRelativeColorimetric:
return "RelativeColorimetric";
case kO2RenderingIntentSaturation:
return "Saturation";
default:
case kO2RenderingIntentDefault:
case kO2RenderingIntentPerceptual:
return "Perceptual";
}
}
- (O2PDFObject *) encodeReferenceWithContext: (O2PDFContext *) context {
O2PDFStream *result = [O2PDFStream pdfStream];
O2PDFDictionary *dictionary = [result dictionary];
[dictionary setNameForKey: "Type" value: "XObject"];
[dictionary setNameForKey: "Subtype" value: "Image"];
[dictionary setIntegerForKey: "Width" value: _width];
[dictionary setIntegerForKey: "Height" value: _height];
if (_colorSpace != nil)
[dictionary
setObjectForKey: "ColorSpace"
value: [_colorSpace
encodeReferenceWithContext: context]];
[dictionary setIntegerForKey: "BitsPerComponent" value: _bitsPerComponent];
[dictionary setNameForKey: "Intent"
value: O2ImageNameWithIntent(_renderingIntent)];
[dictionary setBooleanForKey: "ImageMask" value: _isMask];
if (_mask != nil)
[dictionary
setObjectForKey: "Mask"
value: [_mask encodeReferenceWithContext: context]];
if (_decode != NULL)
[dictionary
setObjectForKey: "Decode"
value: [O2PDFArray
pdfArrayWithNumbers: _decode
count: O2ColorSpaceGetNumberOfComponents(
_colorSpace) *
2]];
[dictionary setBooleanForKey: "Interpolate" value: _interpolate];
if (O2ImageDecoderGetCompressionType(_decoder) == O2ImageCompressionJPEG) {
// If the image is JPEG compressed, we can put the JPEG data in the PDF,
// using a DCTDecode filter
O2DataProviderRef dataProvider =
O2ImageDecoderGetDataProvider(_decoder);
CFDataRef dctData = O2DataProviderCopyData(dataProvider);
[dictionary setNameForKey: "Filter" value: "DCTDecode"];
[[result mutableData] appendData: (NSData *) dctData];
CFRelease(dctData);
} else {
#define CHUNK 65536
// Input buffer for image data
uint8_t in[CHUNK + 3]; // CHUNK size + some additional room for rgb
int idx = 0;
#if ZLIB_PRESENT
// Put ZLIB compressed data in the PDF, using a DCTDecode filter
[dictionary setNameForKey: "Filter" value: "FlateDecode"];
// allocate deflate state
NSUInteger have;
z_stream strm;
strm.zalloc = Z_NULL;
strm.zfree = Z_NULL;
strm.opaque = Z_NULL;
deflateInit(&strm, 9);
// Compressed output buffer
uint8_t out[CHUNK + 3];
#else
// No compression : out buffer = in buffer
uint8_t *out = in;
#endif
const void *bytes = [self directBytes];
/* FIX, generate soft mask for alpha data
[dictionary setObjectForKey:"SMask" value:[softMask
encodeReferenceWithContext:context]];
*/
// It would be nice if jpg data would stay jpg data (instead of an
// uncompress stream), as it does with Quartz and CGImage
// Export RGB bytes, without the alpha data, in the expected order
// TODO : support non 32 bits pixels, respect the premultiplied state,
// non-RGB pixels...
const uint8_t *ptr = bytes;
int alphaInfo = _bitmapInfo & kO2BitmapAlphaInfoMask;
BOOL hasAlpha = alphaInfo != kO2ImageAlphaNone;
BOOL alphaFirst = alphaInfo == kO2ImageAlphaPremultipliedFirst ||
alphaInfo == kO2ImageAlphaFirst ||
alphaInfo == kO2ImageAlphaNoneSkipFirst;
BOOL alphaLast = hasAlpha && (alphaFirst == NO);
BOOL bigEndian = _bitmapInfo & kO2BitmapByteOrder32Big;
BOOL littleEndian = bigEndian == NO;
int bytesPerPixel = _bitsPerPixel / 8;
for (int i = 0; i < _height; i++, ptr += _bytesPerRow) {
const uint8_t *linePtr = ptr;
for (int j = 0; j < _width; j++, linePtr += bytesPerPixel) {
const uint8_t *pixelPtr = linePtr;
/*
AlphaFirst => The Alpha channel is next to the Red channel
(ARGB and BGRA are both Alpha First formats)
AlphaLast => The Alpha channel is next to the Blue channel
(RGBA and ABGR are both Alpha Last formats)
LittleEndian => Blue comes before Red
(BGRA and ABGR are Little endian formats)
BigEndian => Red comes before Blue
(ARGB and RGBA are Big endian formats).
*/
uint8_t r = 0, g = 0, b = 0;
if ((alphaFirst && bigEndian) || (alphaLast && littleEndian)) {
// Skip the alpha
++pixelPtr;
}
if (bigEndian) {
r = *pixelPtr++;
g = *pixelPtr++;
b = *pixelPtr++;
} else {
b = *pixelPtr++;
g = *pixelPtr++;
r = *pixelPtr++;
}
in[idx++] = r;
in[idx++] = g;
in[idx++] = b;
BOOL flush =
(i == _height - 1 && j == _width - 1) || (idx > CHUNK);
if (flush) {
#if ZLIB_PRESENT
strm.avail_in = idx;
flush = ((i == _height - 1 && j == _width - 1))
? Z_FINISH
: Z_NO_FLUSH;
strm.next_in = in;
// run deflate() on input until the output buffer is not
// full
do {
strm.avail_out = idx;
strm.next_out = out;
deflate(&strm, flush);
have = idx - strm.avail_out;
[[result mutableData] appendBytes: out length: have];
} while (strm.avail_out == 0);
#else
[[result mutableData] appendBytes: out length: idx];
#endif
idx = 0;
}
}
}
#if ZLIB_PRESENT
deflateEnd(&strm);
#endif
}
return [context encodeIndirectPDFObject: result];
}
+ (O2Image *) imageWithPDFObject: (O2PDFObject *) object {
O2PDFStream *stream = (O2PDFStream *) object;
O2PDFDictionary *dictionary = [stream dictionary];
O2PDFInteger width;
O2PDFInteger height;
O2PDFObject *colorSpaceObject = NULL;
O2PDFInteger bitsPerComponent;
const char *intent;
O2PDFBoolean isImageMask = NO;
O2PDFObject *imageMaskObject = NULL;
O2ColorSpaceRef colorSpace = NULL;
int componentsPerPixel;
O2PDFArray *decodeArray;
O2Float *decode = NULL;
BOOL interpolate;
O2PDFStream *softMaskStream = nil;
O2Image *softMask = NULL;
if (![dictionary getIntegerForKey: "Width" value: &width]) {
O2PDFError(__FILE__, __LINE__, @"Image has no Width");
return NULL;
}
if (![dictionary getIntegerForKey: "Height" value: &height]) {
O2PDFError(__FILE__, __LINE__, @"Image has no Height");
return NULL;
}
if (![dictionary getIntegerForKey: "BitsPerComponent"
value: &bitsPerComponent]) {
O2PDFError(__FILE__, __LINE__, @"Image has no BitsPerComponent");
return NULL;
}
if (![dictionary getNameForKey: "Intent" value: &intent])
intent = NULL;
[dictionary getBooleanForKey: "ImageMask" value: &isImageMask];
if (isImageMask)
O2PDFFix(__FILE__, __LINE__, @"ImageMask present");
if ([dictionary getObjectForKey: "Mask" value: &imageMaskObject]) {
O2PDFFix(__FILE__, __LINE__, @"Mask present");
}
if ([dictionary getObjectForKey: "ColorSpace" value: &colorSpaceObject]) {
if ((colorSpace = [O2ColorSpace
createColorSpaceFromPDFObject: colorSpaceObject]) ==
NULL) {
O2PDFError(__FILE__, __LINE__, @"Unable to create ColorSpace %@",
colorSpaceObject);
return NULL;
}
}
if (!isImageMask && colorSpace == NULL) {
O2PDFError(__FILE__, __LINE__, @"Image has no ColorSpace %@",
dictionary);
return NULL;
}
if (colorSpace == NULL)
componentsPerPixel = 1;
else
componentsPerPixel = O2ColorSpaceGetNumberOfComponents(colorSpace);
if (![dictionary getArrayForKey: "Decode" value: &decodeArray])
decode = NULL;
else {
NSUInteger count;
if (![decodeArray getNumbers: &decode count: &count]) {
O2PDFError(__FILE__, __LINE__, @"Unable to read decode array %@",
decodeArray);
return NULL;
}
if (count != componentsPerPixel * 2) {
O2PDFError(__FILE__, __LINE__,
@"Invalid decode array, count=%d, should be %d", count,
componentsPerPixel * 2);
return NULL;
}
}
if (![dictionary getBooleanForKey: "Interpolate" value: &interpolate])
interpolate = NO;
if ([dictionary getStreamForKey: "SMask" value: &softMaskStream]) {
softMask = [self imageWithPDFObject: softMaskStream];
}
NSData *data = [stream data];
int bitsPerPixel = componentsPerPixel * bitsPerComponent;
int bytesPerRow = ([stream bytesPerRow] != 0)
? [stream bytesPerRow]
: ((width * bitsPerPixel) + 7) / 8;
O2DataProvider *provider;
O2Image *image = NULL;
// NSLog(@"width=%d,height=%d,bpc=%d,bpp=%d,bpr=%d,cpp=%d",width,height,bitsPerComponent,bitsPerPixel,bytesPerRow,componentsPerPixel);
if (height * bytesPerRow != [data length]) {
O2PDFError(__FILE__, __LINE__,
@"Invalid data length=%d,should be %d=%d", [data length],
height * bytesPerRow, [data length] - height * bytesPerRow);
}
if (height * bytesPerRow > [data length]) {
// provide some gray data
NSMutableData *mutable =
[NSMutableData dataWithLength: height * bytesPerRow];
char *mbytes = [mutable mutableBytes];
int i;
for (i = 0; i < height * bytesPerRow; i++)
mbytes[i] = i;
data = mutable;
}
provider = O2DataProviderCreateWithCFData((CFDataRef) data);
if (isImageMask) {
image = [[O2Image alloc] initMaskWithWidth: width
height: height
bitsPerComponent: bitsPerComponent
bitsPerPixel: bitsPerPixel
bytesPerRow: bytesPerRow
provider: provider
decode: decode
interpolate: interpolate];
} else {
image = [[O2Image alloc]
initWithWidth: width
height: height
bitsPerComponent: bitsPerComponent
bitsPerPixel: bitsPerPixel
bytesPerRow: bytesPerRow
colorSpace: colorSpace
bitmapInfo: 0
decoder: NULL
provider: provider
decode: decode
interpolate: interpolate
renderingIntent: O2ImageRenderingIntentWithName(intent)];
if (softMask != NULL)
[image setMask: softMask];
}
if (decode != NULL)
NSZoneFree(NULL, decode);
O2DataProviderRelease(provider);
O2ColorSpaceRelease(colorSpace);
return image;
}
@end