forked from Hopding/pdf-lib
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathPngEmbedder.ts
138 lines (113 loc) · 4.08 KB
/
PngEmbedder.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
import pako from 'pako';
import PNG, { ColorSpace as ColorSpaceType } from 'png-ts';
import PDFRef from 'src/core/objects/PDFRef';
import PDFContext from 'src/core/PDFContext';
/**
* A note of thanks to the developers of https://github.com/foliojs/pdfkit, as
* this class borrows from:
* https://github.com/devongovett/pdfkit/blob/e71edab0dd4657b5a767804ba86c94c58d01fbca/lib/image/png.coffee
*/
class PngEmbedder {
static for = (imageData: Uint8Array) => new PngEmbedder(imageData);
readonly bitsPerComponent: number;
readonly height: number;
readonly width: number;
readonly colorSpace: ColorSpaceType;
private readonly image: PNG;
private imageData: Uint8Array;
private alphaChannel: Uint8Array | undefined;
private constructor(pngData: Uint8Array) {
this.image = PNG.load(pngData);
this.imageData = this.image.imgData;
this.alphaChannel = undefined;
this.bitsPerComponent = this.image.bits;
this.height = this.image.height;
this.width = this.image.width;
this.colorSpace = this.image.colorSpace;
// TODO: Handle the following two transparency types. They don't seem to be
// fully handled in:
// https://github.com/devongovett/pdfkit/blob/e71edab0dd4657b5a767804ba86c94c58d01fbca/lib/image/png.coffee
//
// if (this.image.transparency.grayscale)
// if (this.image.transparency.rgb)
if (this.image.transparency.indexed) {
this.loadIndexedAlphaChannel();
} else if (this.image.hasAlphaChannel) {
this.splitAlphaChannel();
}
}
embedIntoContext(context: PDFContext): PDFRef {
const SMask = this.embedAlphaChannel(context);
const { palette } = this.image;
let ColorSpace: string | any[] = this.image.colorSpace;
if (palette.length !== 0) {
const stream = context.stream(new Uint8Array(palette));
const ref = context.register(stream);
ColorSpace = ['Indexed', 'DeviceRGB', palette.length / 3 - 1, ref];
}
let DecodeParms;
if (!this.image.hasAlphaChannel) {
DecodeParms = {
Predictor: 15,
Colors: this.image.colors,
BitsPerComponent: this.image.bits,
Columns: this.image.width,
};
}
const xObject = context.stream(this.imageData, {
Type: 'XObject',
Subtype: 'Image',
BitsPerComponent: this.image.bits,
Width: this.image.width,
Height: this.image.height,
Filter: 'FlateDecode',
SMask,
DecodeParms,
ColorSpace,
});
return context.register(xObject);
}
private embedAlphaChannel(context: PDFContext): PDFRef | undefined {
if (!this.alphaChannel) return undefined;
const xObject = context.flateStream(this.alphaChannel, {
Type: 'XObject',
Subtype: 'Image',
Height: this.image.height,
Width: this.image.width,
BitsPerComponent: 8,
ColorSpace: 'DeviceGray',
Decode: [0, 1],
});
return context.register(xObject);
}
private splitAlphaChannel(): void {
const { colors, bits, width, height } = this.image;
const pixels = this.image.decodePixels();
const colorByteSize = (colors * bits) / 8;
const pixelCount = width * height;
const imageData = new Uint8Array(pixelCount * colorByteSize);
const alphaChannel = new Uint8Array(pixelCount);
let pixelOffset = 0;
let rgbOffset = 0;
let alphaOffset = 0;
const { length } = pixels;
while (pixelOffset < length) {
imageData[rgbOffset++] = pixels[pixelOffset++];
imageData[rgbOffset++] = pixels[pixelOffset++];
imageData[rgbOffset++] = pixels[pixelOffset++];
alphaChannel[alphaOffset++] = pixels[pixelOffset++];
}
this.imageData = pako.deflate(imageData);
this.alphaChannel = alphaChannel;
}
private loadIndexedAlphaChannel(): void {
const transparency = this.image.transparency.indexed!;
const pixels = this.image.decodePixels();
const alphaChannel = new Uint8Array(this.image.width * this.image.height);
for (let idx = 0, len = pixels.length; idx < len; idx++) {
alphaChannel[idx] = transparency[pixels[idx]];
}
this.alphaChannel = alphaChannel;
}
}
export default PngEmbedder;