@@ -81,6 +81,7 @@ jsPDF.API.processPNG = function(imageData, index, alias, compression) {
81
81
const {
82
82
colorSpace,
83
83
colorsPerPixel,
84
+ sMaskBitsPerComponent,
84
85
colorBytes,
85
86
alphaBytes,
86
87
needSMask,
@@ -94,25 +95,36 @@ jsPDF.API.processPNG = function(imageData, index, alias, compression) {
94
95
if ( canCompress ( compression ) ) {
95
96
predictor = getPredictorFromCompression ( compression ) ;
96
97
filter = this . decode . FLATE_DECODE ;
97
- decodeParameters = `/Predictor ${ predictor } ` ;
98
+ decodeParameters = `/Predictor ${ predictor } /Colors ${ colorsPerPixel } /BitsPerComponent ${ bitsPerComponent } /Columns ${ width } ` ;
99
+
100
+ const rowByteLength = Math . ceil (
101
+ ( width * colorsPerPixel * bitsPerComponent ) / 8
102
+ ) ;
103
+
98
104
imageData = compressBytes (
99
105
colorBytes ,
100
- width * colorsPerPixel ,
106
+ rowByteLength ,
101
107
colorsPerPixel ,
108
+ bitsPerComponent ,
102
109
compression
103
110
) ;
104
111
if ( needSMask ) {
105
- sMask = compressBytes ( alphaBytes , width , 1 , compression ) ;
112
+ const sMaskRowByteLength = Math . ceil ( ( width * sMaskBitsPerComponent ) / 8 ) ;
113
+ sMask = compressBytes (
114
+ alphaBytes ,
115
+ sMaskRowByteLength ,
116
+ 1 ,
117
+ sMaskBitsPerComponent ,
118
+ compression
119
+ ) ;
106
120
}
107
121
} else {
108
122
filter = undefined ;
109
- decodeParameters = "" ;
123
+ decodeParameters = undefined ;
110
124
imageData = colorBytes ;
111
125
if ( needSMask ) sMask = alphaBytes ;
112
126
}
113
127
114
- decodeParameters += `/Colors ${ colorsPerPixel } /BitsPerComponent ${ bitsPerComponent } /Columns ${ width } ` ;
115
-
116
128
if (
117
129
this . __addimage__ . isArrayBuffer ( imageData ) ||
118
130
this . __addimage__ . isArrayBufferView ( imageData )
@@ -140,6 +152,7 @@ jsPDF.API.processPNG = function(imageData, index, alias, compression) {
140
152
width,
141
153
height,
142
154
bitsPerComponent,
155
+ sMaskBitsPerComponent,
143
156
colorSpace
144
157
} ;
145
158
} ;
@@ -169,7 +182,13 @@ function canCompress(value) {
169
182
function hasCompressionJS ( ) {
170
183
return typeof zlibSync === "function" ;
171
184
}
172
- function compressBytes ( bytes , lineLength , colorsPerPixel , compression ) {
185
+ function compressBytes (
186
+ bytes ,
187
+ lineByteLength ,
188
+ channels ,
189
+ bitsPerComponent ,
190
+ compression
191
+ ) {
173
192
let level = 4 ;
174
193
let filter_method = filterUp ;
175
194
@@ -190,10 +209,11 @@ function compressBytes(bytes, lineLength, colorsPerPixel, compression) {
190
209
break ;
191
210
}
192
211
212
+ const bytesPerPixel = Math . ceil ( ( channels * bitsPerComponent ) / 8 ) ;
193
213
bytes = applyPngFilterMethod (
194
214
bytes ,
195
- lineLength ,
196
- colorsPerPixel ,
215
+ lineByteLength ,
216
+ bytesPerPixel ,
197
217
filter_method
198
218
) ;
199
219
const dat = zlibSync ( bytes , { level : level } ) ;
@@ -202,27 +222,27 @@ function compressBytes(bytes, lineLength, colorsPerPixel, compression) {
202
222
203
223
function applyPngFilterMethod (
204
224
bytes ,
205
- lineLength ,
206
- colorsPerPixel ,
225
+ lineByteLength ,
226
+ bytesPerPixel ,
207
227
filter_method
208
228
) {
209
- const lines = bytes . length / lineLength ;
229
+ const lines = bytes . length / lineByteLength ;
210
230
const result = new Uint8Array ( bytes . length + lines ) ;
211
231
const filter_methods = getFilterMethods ( ) ;
212
232
let prevLine ;
213
233
214
234
for ( let i = 0 ; i < lines ; i += 1 ) {
215
- const offset = i * lineLength ;
216
- const line = bytes . subarray ( offset , offset + lineLength ) ;
235
+ const offset = i * lineByteLength ;
236
+ const line = bytes . subarray ( offset , offset + lineByteLength ) ;
217
237
218
238
if ( filter_method ) {
219
- result . set ( filter_method ( line , colorsPerPixel , prevLine ) , offset + i ) ;
239
+ result . set ( filter_method ( line , bytesPerPixel , prevLine ) , offset + i ) ;
220
240
} else {
221
241
const len = filter_methods . length ;
222
242
const results = [ ] ;
223
243
224
244
for ( let j = 0 ; j < len ; j += 1 ) {
225
- results [ j ] = filter_methods [ j ] ( line , colorsPerPixel , prevLine ) ;
245
+ results [ j ] = filter_methods [ j ] ( line , bytesPerPixel , prevLine ) ;
226
246
}
227
247
228
248
const ind = getIndexOfSmallestSum ( results . concat ( ) ) ;
@@ -384,18 +404,22 @@ function processIndexedPNG(decodedPng) {
384
404
mask = undefined ;
385
405
386
406
const totalPixels = width * height ;
407
+ // per PNG spec, palettes always use 8 bits per component
387
408
alphaBytes = new Uint8Array ( totalPixels ) ;
388
409
const dataView = new DataView ( data . buffer ) ;
389
410
for ( let p = 0 ; p < totalPixels ; p ++ ) {
390
411
const paletteIndex = readSample ( dataView , p , depth ) ;
391
412
const [ , , , alpha ] = decodedPalette [ paletteIndex ] ;
392
413
alphaBytes [ p ] = alpha ;
393
414
}
415
+ } else if ( maskLength === 0 ) {
416
+ mask = undefined ;
394
417
}
395
418
396
419
return {
397
420
colorSpace : "Indexed" ,
398
421
colorsPerPixel : 1 ,
422
+ sMaskBitsPerComponent : needSMask ? 8 : undefined ,
399
423
colorBytes : data ,
400
424
alphaBytes,
401
425
needSMask,
@@ -447,6 +471,7 @@ function processAlphaPNG(decodedPng) {
447
471
return {
448
472
colorSpace,
449
473
colorsPerPixel,
474
+ sMaskBitsPerComponent : needSMask ? depth : undefined ,
450
475
colorBytes,
451
476
alphaBytes,
452
477
needSMask
@@ -457,11 +482,31 @@ function processOpaquePNG(decodedPng) {
457
482
const { data, channels } = decodedPng ;
458
483
const colorSpace = channels === 1 ? "DeviceGray" : "DeviceRGB" ;
459
484
const colorsPerPixel = colorSpace === "DeviceGray" ? 1 : 3 ;
460
- const colorBytes =
461
- data instanceof Uint8Array ? data : new Uint8Array ( data . buffer ) ;
485
+
486
+ let colorBytes ;
487
+ if ( data instanceof Uint16Array ) {
488
+ colorBytes = convertUint16ArrayToUint8Array ( data ) ;
489
+ } else {
490
+ colorBytes = data ;
491
+ }
492
+
462
493
return { colorSpace, colorsPerPixel, colorBytes, needSMask : false } ;
463
494
}
464
495
496
+ function convertUint16ArrayToUint8Array ( data ) {
497
+ // PNG/PDF expect MSB-first byte order. Since EcmaScript does not specify
498
+ // the byte order of Uint16Array, we need to use a DataView to ensure the
499
+ // correct byte order.
500
+ const sampleCount = data . length ;
501
+ const out = new Uint8Array ( sampleCount * 2 ) ;
502
+ const outView = new DataView ( out . buffer , out . byteOffset , out . byteLength ) ;
503
+
504
+ for ( let i = 0 ; i < sampleCount ; i ++ ) {
505
+ outView . setUint16 ( i * 2 , data [ i ] , false ) ;
506
+ }
507
+ return out ;
508
+ }
509
+
465
510
function readSample ( view , sampleIndex , depth ) {
466
511
const bitIndex = sampleIndex * depth ;
467
512
const byteIndex = Math . floor ( bitIndex / 8 ) ;
0 commit comments