@@ -81,6 +81,7 @@ jsPDF.API.processPNG = function(imageData, index, alias, compression) {
8181 const {
8282 colorSpace,
8383 colorsPerPixel,
84+ sMaskBitsPerComponent,
8485 colorBytes,
8586 alphaBytes,
8687 needSMask,
@@ -94,25 +95,36 @@ jsPDF.API.processPNG = function(imageData, index, alias, compression) {
9495 if ( canCompress ( compression ) ) {
9596 predictor = getPredictorFromCompression ( compression ) ;
9697 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+
98104 imageData = compressBytes (
99105 colorBytes ,
100- width * colorsPerPixel ,
106+ rowByteLength ,
101107 colorsPerPixel ,
108+ bitsPerComponent ,
102109 compression
103110 ) ;
104111 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+ ) ;
106120 }
107121 } else {
108122 filter = undefined ;
109- decodeParameters = "" ;
123+ decodeParameters = undefined ;
110124 imageData = colorBytes ;
111125 if ( needSMask ) sMask = alphaBytes ;
112126 }
113127
114- decodeParameters += `/Colors ${ colorsPerPixel } /BitsPerComponent ${ bitsPerComponent } /Columns ${ width } ` ;
115-
116128 if (
117129 this . __addimage__ . isArrayBuffer ( imageData ) ||
118130 this . __addimage__ . isArrayBufferView ( imageData )
@@ -140,6 +152,7 @@ jsPDF.API.processPNG = function(imageData, index, alias, compression) {
140152 width,
141153 height,
142154 bitsPerComponent,
155+ sMaskBitsPerComponent,
143156 colorSpace
144157 } ;
145158} ;
@@ -169,7 +182,13 @@ function canCompress(value) {
169182function hasCompressionJS ( ) {
170183 return typeof zlibSync === "function" ;
171184}
172- function compressBytes ( bytes , lineLength , colorsPerPixel , compression ) {
185+ function compressBytes (
186+ bytes ,
187+ lineByteLength ,
188+ channels ,
189+ bitsPerComponent ,
190+ compression
191+ ) {
173192 let level = 4 ;
174193 let filter_method = filterUp ;
175194
@@ -190,10 +209,11 @@ function compressBytes(bytes, lineLength, colorsPerPixel, compression) {
190209 break ;
191210 }
192211
212+ const bytesPerPixel = Math . ceil ( ( channels * bitsPerComponent ) / 8 ) ;
193213 bytes = applyPngFilterMethod (
194214 bytes ,
195- lineLength ,
196- colorsPerPixel ,
215+ lineByteLength ,
216+ bytesPerPixel ,
197217 filter_method
198218 ) ;
199219 const dat = zlibSync ( bytes , { level : level } ) ;
@@ -202,27 +222,27 @@ function compressBytes(bytes, lineLength, colorsPerPixel, compression) {
202222
203223function applyPngFilterMethod (
204224 bytes ,
205- lineLength ,
206- colorsPerPixel ,
225+ lineByteLength ,
226+ bytesPerPixel ,
207227 filter_method
208228) {
209- const lines = bytes . length / lineLength ;
229+ const lines = bytes . length / lineByteLength ;
210230 const result = new Uint8Array ( bytes . length + lines ) ;
211231 const filter_methods = getFilterMethods ( ) ;
212232 let prevLine ;
213233
214234 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 ) ;
217237
218238 if ( filter_method ) {
219- result . set ( filter_method ( line , colorsPerPixel , prevLine ) , offset + i ) ;
239+ result . set ( filter_method ( line , bytesPerPixel , prevLine ) , offset + i ) ;
220240 } else {
221241 const len = filter_methods . length ;
222242 const results = [ ] ;
223243
224244 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 ) ;
226246 }
227247
228248 const ind = getIndexOfSmallestSum ( results . concat ( ) ) ;
@@ -384,18 +404,22 @@ function processIndexedPNG(decodedPng) {
384404 mask = undefined ;
385405
386406 const totalPixels = width * height ;
407+ // per PNG spec, palettes always use 8 bits per component
387408 alphaBytes = new Uint8Array ( totalPixels ) ;
388409 const dataView = new DataView ( data . buffer ) ;
389410 for ( let p = 0 ; p < totalPixels ; p ++ ) {
390411 const paletteIndex = readSample ( dataView , p , depth ) ;
391412 const [ , , , alpha ] = decodedPalette [ paletteIndex ] ;
392413 alphaBytes [ p ] = alpha ;
393414 }
415+ } else if ( maskLength === 0 ) {
416+ mask = undefined ;
394417 }
395418
396419 return {
397420 colorSpace : "Indexed" ,
398421 colorsPerPixel : 1 ,
422+ sMaskBitsPerComponent : needSMask ? 8 : undefined ,
399423 colorBytes : data ,
400424 alphaBytes,
401425 needSMask,
@@ -447,6 +471,7 @@ function processAlphaPNG(decodedPng) {
447471 return {
448472 colorSpace,
449473 colorsPerPixel,
474+ sMaskBitsPerComponent : needSMask ? depth : undefined ,
450475 colorBytes,
451476 alphaBytes,
452477 needSMask
@@ -457,11 +482,31 @@ function processOpaquePNG(decodedPng) {
457482 const { data, channels } = decodedPng ;
458483 const colorSpace = channels === 1 ? "DeviceGray" : "DeviceRGB" ;
459484 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+
462493 return { colorSpace, colorsPerPixel, colorBytes, needSMask : false } ;
463494}
464495
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+
465510function readSample ( view , sampleIndex , depth ) {
466511 const bitIndex = sampleIndex * depth ;
467512 const byteIndex = Math . floor ( bitIndex / 8 ) ;
0 commit comments