diff --git a/src/modules/addimage.js b/src/modules/addimage.js index 607daf039..85122df80 100644 --- a/src/modules/addimage.js +++ b/src/modules/addimage.js @@ -251,7 +251,11 @@ import { atob } from "../libs/AtobBtoa.js"; value: "<<" + image.decodeParameters + ">>" }); } - if ("transparency" in image && Array.isArray(image.transparency)) { + if ( + "transparency" in image && + Array.isArray(image.transparency) && + image.transparency.length > 0 + ) { var transparency = "", i = 0, len = image.transparency.length; @@ -285,20 +289,17 @@ import { atob } from "../libs/AtobBtoa.js"; // Soft mask if ("sMask" in image && typeof image.sMask !== "undefined") { - var decodeParameters = - (image.predictor != null ? "/Predictor " + image.predictor : "") + - " /Colors 1 /BitsPerComponent 8" + - " /Columns " + - image.width; - var sMask = { + const sMaskBitsPerComponent = + image.sMaskBitsPerComponent ?? image.bitsPerComponent; + const sMask = { width: image.width, height: image.height, colorSpace: "DeviceGray", - bitsPerComponent: image.bitsPerComponent, - decodeParameters: decodeParameters, + bitsPerComponent: sMaskBitsPerComponent, data: image.sMask }; if ("filter" in image) { + sMask.decodeParameters = `/Predictor ${image.predictor} /Colors 1 /BitsPerComponent ${sMaskBitsPerComponent} /Columns ${image.width}`; sMask.filter = image.filter; } putImage.call(this, sMask); diff --git a/src/modules/png_support.js b/src/modules/png_support.js index a687e8d33..43b9362a3 100644 --- a/src/modules/png_support.js +++ b/src/modules/png_support.js @@ -81,6 +81,7 @@ jsPDF.API.processPNG = function(imageData, index, alias, compression) { const { colorSpace, colorsPerPixel, + sMaskBitsPerComponent, colorBytes, alphaBytes, needSMask, @@ -94,25 +95,36 @@ jsPDF.API.processPNG = function(imageData, index, alias, compression) { if (canCompress(compression)) { predictor = getPredictorFromCompression(compression); filter = this.decode.FLATE_DECODE; - decodeParameters = `/Predictor ${predictor} `; + decodeParameters = `/Predictor ${predictor} /Colors ${colorsPerPixel} /BitsPerComponent ${bitsPerComponent} /Columns ${width}`; + + const rowByteLength = Math.ceil( + (width * colorsPerPixel * bitsPerComponent) / 8 + ); + imageData = compressBytes( colorBytes, - width * colorsPerPixel, + rowByteLength, colorsPerPixel, + bitsPerComponent, compression ); if (needSMask) { - sMask = compressBytes(alphaBytes, width, 1, compression); + const sMaskRowByteLength = Math.ceil((width * sMaskBitsPerComponent) / 8); + sMask = compressBytes( + alphaBytes, + sMaskRowByteLength, + 1, + sMaskBitsPerComponent, + compression + ); } } else { filter = undefined; - decodeParameters = ""; + decodeParameters = undefined; imageData = colorBytes; if (needSMask) sMask = alphaBytes; } - decodeParameters += `/Colors ${colorsPerPixel} /BitsPerComponent ${bitsPerComponent} /Columns ${width}`; - if ( this.__addimage__.isArrayBuffer(imageData) || this.__addimage__.isArrayBufferView(imageData) @@ -140,6 +152,7 @@ jsPDF.API.processPNG = function(imageData, index, alias, compression) { width, height, bitsPerComponent, + sMaskBitsPerComponent, colorSpace }; }; @@ -169,7 +182,13 @@ function canCompress(value) { function hasCompressionJS() { return typeof zlibSync === "function"; } -function compressBytes(bytes, lineLength, colorsPerPixel, compression) { +function compressBytes( + bytes, + lineByteLength, + channels, + bitsPerComponent, + compression +) { let level = 4; let filter_method = filterUp; @@ -190,10 +209,11 @@ function compressBytes(bytes, lineLength, colorsPerPixel, compression) { break; } + const bytesPerPixel = Math.ceil((channels * bitsPerComponent) / 8); bytes = applyPngFilterMethod( bytes, - lineLength, - colorsPerPixel, + lineByteLength, + bytesPerPixel, filter_method ); const dat = zlibSync(bytes, { level: level }); @@ -202,27 +222,27 @@ function compressBytes(bytes, lineLength, colorsPerPixel, compression) { function applyPngFilterMethod( bytes, - lineLength, - colorsPerPixel, + lineByteLength, + bytesPerPixel, filter_method ) { - const lines = bytes.length / lineLength; + const lines = bytes.length / lineByteLength; const result = new Uint8Array(bytes.length + lines); const filter_methods = getFilterMethods(); let prevLine; for (let i = 0; i < lines; i += 1) { - const offset = i * lineLength; - const line = bytes.subarray(offset, offset + lineLength); + const offset = i * lineByteLength; + const line = bytes.subarray(offset, offset + lineByteLength); if (filter_method) { - result.set(filter_method(line, colorsPerPixel, prevLine), offset + i); + result.set(filter_method(line, bytesPerPixel, prevLine), offset + i); } else { const len = filter_methods.length; const results = []; for (let j = 0; j < len; j += 1) { - results[j] = filter_methods[j](line, colorsPerPixel, prevLine); + results[j] = filter_methods[j](line, bytesPerPixel, prevLine); } const ind = getIndexOfSmallestSum(results.concat()); @@ -384,6 +404,7 @@ function processIndexedPNG(decodedPng) { mask = undefined; const totalPixels = width * height; + // per PNG spec, palettes always use 8 bits per component alphaBytes = new Uint8Array(totalPixels); const dataView = new DataView(data.buffer); for (let p = 0; p < totalPixels; p++) { @@ -391,11 +412,14 @@ function processIndexedPNG(decodedPng) { const [, , , alpha] = decodedPalette[paletteIndex]; alphaBytes[p] = alpha; } + } else if (maskLength === 0) { + mask = undefined; } return { colorSpace: "Indexed", colorsPerPixel: 1, + sMaskBitsPerComponent: needSMask ? 8 : undefined, colorBytes: data, alphaBytes, needSMask, @@ -447,6 +471,7 @@ function processAlphaPNG(decodedPng) { return { colorSpace, colorsPerPixel, + sMaskBitsPerComponent: needSMask ? depth : undefined, colorBytes, alphaBytes, needSMask @@ -457,11 +482,31 @@ function processOpaquePNG(decodedPng) { const { data, channels } = decodedPng; const colorSpace = channels === 1 ? "DeviceGray" : "DeviceRGB"; const colorsPerPixel = colorSpace === "DeviceGray" ? 1 : 3; - const colorBytes = - data instanceof Uint8Array ? data : new Uint8Array(data.buffer); + + let colorBytes; + if (data instanceof Uint16Array) { + colorBytes = convertUint16ArrayToUint8Array(data); + } else { + colorBytes = data; + } + return { colorSpace, colorsPerPixel, colorBytes, needSMask: false }; } +function convertUint16ArrayToUint8Array(data) { + // PNG/PDF expect MSB-first byte order. Since EcmaScript does not specify + // the byte order of Uint16Array, we need to use a DataView to ensure the + // correct byte order. + const sampleCount = data.length; + const out = new Uint8Array(sampleCount * 2); + const outView = new DataView(out.buffer, out.byteOffset, out.byteLength); + + for (let i = 0; i < sampleCount; i++) { + outView.setUint16(i * 2, data[i], false); + } + return out; +} + function readSample(view, sampleIndex, depth) { const bitIndex = sampleIndex * depth; const byteIndex = Math.floor(bitIndex / 8); diff --git a/test/reference/colortype_1_grayscale_16_bit_png.pdf b/test/reference/colortype_1_grayscale_16_bit_png.pdf index 9cf556b60..415951b25 100644 Binary files a/test/reference/colortype_1_grayscale_16_bit_png.pdf and b/test/reference/colortype_1_grayscale_16_bit_png.pdf differ diff --git a/test/reference/colortype_2_rgb_16_bit_png.pdf b/test/reference/colortype_2_rgb_16_bit_png.pdf index 264e1ca79..0bab24b23 100644 Binary files a/test/reference/colortype_2_rgb_16_bit_png.pdf and b/test/reference/colortype_2_rgb_16_bit_png.pdf differ diff --git a/test/reference/colortype_2_rgb_8_bit_png.pdf b/test/reference/colortype_2_rgb_8_bit_png.pdf index b0438f07a..c0c975f82 100644 Binary files a/test/reference/colortype_2_rgb_8_bit_png.pdf and b/test/reference/colortype_2_rgb_8_bit_png.pdf differ diff --git a/test/reference/colortype_3_indexed_multi_colour_alpha_4_bit_png.pdf b/test/reference/colortype_3_indexed_multi_colour_alpha_4_bit_png.pdf index 1890c1285..29d47b51f 100644 Binary files a/test/reference/colortype_3_indexed_multi_colour_alpha_4_bit_png.pdf and b/test/reference/colortype_3_indexed_multi_colour_alpha_4_bit_png.pdf differ diff --git a/test/reference/colortype_3_indexed_multi_colour_alpha_8_bit_png.pdf b/test/reference/colortype_3_indexed_multi_colour_alpha_8_bit_png.pdf index d14d0d9aa..68f612044 100644 Binary files a/test/reference/colortype_3_indexed_multi_colour_alpha_8_bit_png.pdf and b/test/reference/colortype_3_indexed_multi_colour_alpha_8_bit_png.pdf differ diff --git a/test/reference/colortype_3_indexed_single_colour_alpha_4_bit_png.pdf b/test/reference/colortype_3_indexed_single_colour_alpha_4_bit_png.pdf index 271f39fc5..95361a929 100644 Binary files a/test/reference/colortype_3_indexed_single_colour_alpha_4_bit_png.pdf and b/test/reference/colortype_3_indexed_single_colour_alpha_4_bit_png.pdf differ diff --git a/test/reference/colortype_3_indexed_single_colour_alpha_8_bit_png.pdf b/test/reference/colortype_3_indexed_single_colour_alpha_8_bit_png.pdf index b2a446ab1..dd92981b8 100644 Binary files a/test/reference/colortype_3_indexed_single_colour_alpha_8_bit_png.pdf and b/test/reference/colortype_3_indexed_single_colour_alpha_8_bit_png.pdf differ diff --git a/test/reference/colortype_4_grayscale_alpha_16_bit_png.pdf b/test/reference/colortype_4_grayscale_alpha_16_bit_png.pdf index e21fbcc73..ad907439e 100644 Binary files a/test/reference/colortype_4_grayscale_alpha_16_bit_png.pdf and b/test/reference/colortype_4_grayscale_alpha_16_bit_png.pdf differ diff --git a/test/reference/colortype_4_grayscale_alpha_8_bit_png.pdf b/test/reference/colortype_4_grayscale_alpha_8_bit_png.pdf index 36ef7dd2e..d12d34dbd 100644 Binary files a/test/reference/colortype_4_grayscale_alpha_8_bit_png.pdf and b/test/reference/colortype_4_grayscale_alpha_8_bit_png.pdf differ diff --git a/test/reference/colortype_6_rgba_16_bit_png.pdf b/test/reference/colortype_6_rgba_16_bit_png.pdf index 43a582ed8..6e365b8bb 100644 Binary files a/test/reference/colortype_6_rgba_16_bit_png.pdf and b/test/reference/colortype_6_rgba_16_bit_png.pdf differ diff --git a/test/reference/colortype_6_rgba_8_bit_png.pdf b/test/reference/colortype_6_rgba_8_bit_png.pdf index a69cf92c6..532567d54 100644 Binary files a/test/reference/colortype_6_rgba_8_bit_png.pdf and b/test/reference/colortype_6_rgba_8_bit_png.pdf differ diff --git a/test/reference/colortype_6_rgba_8_bit_png_NONE.pdf b/test/reference/colortype_6_rgba_8_bit_png_NONE.pdf index 95a3bce7e..801251c5e 100644 Binary files a/test/reference/colortype_6_rgba_8_bit_png_NONE.pdf and b/test/reference/colortype_6_rgba_8_bit_png_NONE.pdf differ diff --git a/test/reference/encrypted_withImage.pdf b/test/reference/encrypted_withImage.pdf index 9afd38fba..93b796299 100644 Binary files a/test/reference/encrypted_withImage.pdf and b/test/reference/encrypted_withImage.pdf differ diff --git a/test/reference/html-margin-page-break-image.pdf b/test/reference/html-margin-page-break-image.pdf index e2fb02128..b35341106 100644 Binary files a/test/reference/html-margin-page-break-image.pdf and b/test/reference/html-margin-page-break-image.pdf differ diff --git a/test/reference/images/1channels-16bit.png b/test/reference/images/1channels-16bit.png new file mode 100644 index 000000000..ea28c3c3c Binary files /dev/null and b/test/reference/images/1channels-16bit.png differ diff --git a/test/reference/images/1channels-8bit.png b/test/reference/images/1channels-8bit.png new file mode 100644 index 000000000..e35dde936 Binary files /dev/null and b/test/reference/images/1channels-8bit.png differ diff --git a/test/reference/images/2channels-16bit.png b/test/reference/images/2channels-16bit.png new file mode 100644 index 000000000..8ccc94ffd Binary files /dev/null and b/test/reference/images/2channels-16bit.png differ diff --git a/test/reference/images/2channels-8bit.png b/test/reference/images/2channels-8bit.png new file mode 100644 index 000000000..e9796b466 Binary files /dev/null and b/test/reference/images/2channels-8bit.png differ diff --git a/test/reference/images/3channels-16bit.png b/test/reference/images/3channels-16bit.png new file mode 100644 index 000000000..467331a3c Binary files /dev/null and b/test/reference/images/3channels-16bit.png differ diff --git a/test/reference/images/3channels-8bit.png b/test/reference/images/3channels-8bit.png new file mode 100644 index 000000000..79861dd9d Binary files /dev/null and b/test/reference/images/3channels-8bit.png differ diff --git a/test/reference/images/3channels-indexed-8bit.png b/test/reference/images/3channels-indexed-8bit.png new file mode 100644 index 000000000..e93ae76d5 Binary files /dev/null and b/test/reference/images/3channels-indexed-8bit.png differ diff --git a/test/reference/images/4channels-16bit.png b/test/reference/images/4channels-16bit.png new file mode 100644 index 000000000..d17019b05 Binary files /dev/null and b/test/reference/images/4channels-16bit.png differ diff --git a/test/reference/images/4channels-8bit.png b/test/reference/images/4channels-8bit.png new file mode 100644 index 000000000..ccbbb70b6 Binary files /dev/null and b/test/reference/images/4channels-8bit.png differ diff --git a/test/reference/images/4channels-indexed-8bit.png b/test/reference/images/4channels-indexed-8bit.png new file mode 100644 index 000000000..f3194e8e9 Binary files /dev/null and b/test/reference/images/4channels-indexed-8bit.png differ diff --git a/test/reference/png-1channels-16bit-MEDIUM.pdf b/test/reference/png-1channels-16bit-MEDIUM.pdf new file mode 100644 index 000000000..d1b184872 Binary files /dev/null and b/test/reference/png-1channels-16bit-MEDIUM.pdf differ diff --git a/test/reference/png-1channels-16bit-NONE.pdf b/test/reference/png-1channels-16bit-NONE.pdf new file mode 100644 index 000000000..5a50ad5b0 Binary files /dev/null and b/test/reference/png-1channels-16bit-NONE.pdf differ diff --git a/test/reference/png-1channels-8bit-MEDIUM.pdf b/test/reference/png-1channels-8bit-MEDIUM.pdf new file mode 100644 index 000000000..a2dc96ba0 Binary files /dev/null and b/test/reference/png-1channels-8bit-MEDIUM.pdf differ diff --git a/test/reference/png-1channels-8bit-NONE.pdf b/test/reference/png-1channels-8bit-NONE.pdf new file mode 100644 index 000000000..60fc165dc Binary files /dev/null and b/test/reference/png-1channels-8bit-NONE.pdf differ diff --git a/test/reference/png-2channels-16bit-MEDIUM.pdf b/test/reference/png-2channels-16bit-MEDIUM.pdf new file mode 100644 index 000000000..222038cca Binary files /dev/null and b/test/reference/png-2channels-16bit-MEDIUM.pdf differ diff --git a/test/reference/png-2channels-16bit-NONE.pdf b/test/reference/png-2channels-16bit-NONE.pdf new file mode 100644 index 000000000..59604afb0 Binary files /dev/null and b/test/reference/png-2channels-16bit-NONE.pdf differ diff --git a/test/reference/png-2channels-8bit-MEDIUM.pdf b/test/reference/png-2channels-8bit-MEDIUM.pdf new file mode 100644 index 000000000..c3026d11b Binary files /dev/null and b/test/reference/png-2channels-8bit-MEDIUM.pdf differ diff --git a/test/reference/png-2channels-8bit-NONE.pdf b/test/reference/png-2channels-8bit-NONE.pdf new file mode 100644 index 000000000..20db053a2 Binary files /dev/null and b/test/reference/png-2channels-8bit-NONE.pdf differ diff --git a/test/reference/png-3channels-16bit-MEDIUM.pdf b/test/reference/png-3channels-16bit-MEDIUM.pdf new file mode 100644 index 000000000..b8ae1a059 Binary files /dev/null and b/test/reference/png-3channels-16bit-MEDIUM.pdf differ diff --git a/test/reference/png-3channels-16bit-NONE.pdf b/test/reference/png-3channels-16bit-NONE.pdf new file mode 100644 index 000000000..423a640f9 Binary files /dev/null and b/test/reference/png-3channels-16bit-NONE.pdf differ diff --git a/test/reference/png-3channels-8bit-MEDIUM.pdf b/test/reference/png-3channels-8bit-MEDIUM.pdf new file mode 100644 index 000000000..657ba1af1 Binary files /dev/null and b/test/reference/png-3channels-8bit-MEDIUM.pdf differ diff --git a/test/reference/png-3channels-8bit-NONE.pdf b/test/reference/png-3channels-8bit-NONE.pdf new file mode 100644 index 000000000..e5d8b46bb Binary files /dev/null and b/test/reference/png-3channels-8bit-NONE.pdf differ diff --git a/test/reference/png-3channels-indexed-8bit-MEDIUM.pdf b/test/reference/png-3channels-indexed-8bit-MEDIUM.pdf new file mode 100644 index 000000000..b8ab18c7a Binary files /dev/null and b/test/reference/png-3channels-indexed-8bit-MEDIUM.pdf differ diff --git a/test/reference/png-3channels-indexed-8bit-NONE.pdf b/test/reference/png-3channels-indexed-8bit-NONE.pdf new file mode 100644 index 000000000..0396baa03 Binary files /dev/null and b/test/reference/png-3channels-indexed-8bit-NONE.pdf differ diff --git a/test/reference/png-4channels-16bit-MEDIUM.pdf b/test/reference/png-4channels-16bit-MEDIUM.pdf new file mode 100644 index 000000000..64a1db0c1 Binary files /dev/null and b/test/reference/png-4channels-16bit-MEDIUM.pdf differ diff --git a/test/reference/png-4channels-16bit-NONE.pdf b/test/reference/png-4channels-16bit-NONE.pdf new file mode 100644 index 000000000..95233ae44 Binary files /dev/null and b/test/reference/png-4channels-16bit-NONE.pdf differ diff --git a/test/reference/png-4channels-8bit-MEDIUM.pdf b/test/reference/png-4channels-8bit-MEDIUM.pdf new file mode 100644 index 000000000..9108d3cc1 Binary files /dev/null and b/test/reference/png-4channels-8bit-MEDIUM.pdf differ diff --git a/test/reference/png-4channels-8bit-NONE.pdf b/test/reference/png-4channels-8bit-NONE.pdf new file mode 100644 index 000000000..53deb9a22 Binary files /dev/null and b/test/reference/png-4channels-8bit-NONE.pdf differ diff --git a/test/reference/png-4channels-indexed-8bit-MEDIUM.pdf b/test/reference/png-4channels-indexed-8bit-MEDIUM.pdf new file mode 100644 index 000000000..ef6180aee Binary files /dev/null and b/test/reference/png-4channels-indexed-8bit-MEDIUM.pdf differ diff --git a/test/reference/png-4channels-indexed-8bit-NONE.pdf b/test/reference/png-4channels-indexed-8bit-NONE.pdf new file mode 100644 index 000000000..fd29ea5c5 Binary files /dev/null and b/test/reference/png-4channels-indexed-8bit-NONE.pdf differ diff --git a/test/specs/png.spec.js b/test/specs/png.spec.js index 10da65df3..4d0ac90ed 100644 --- a/test/specs/png.spec.js +++ b/test/specs/png.spec.js @@ -397,4 +397,63 @@ describe("Module: PNGSupport", () => { "addimage" ); }); + + describe("more PNGs", () => { + beforeAll(loadGlobals); + + function runTest(fileName, compression) { + const testName = fileName.replace(/\.png$/i, ""); + const pngData = loadBinaryResource("reference/images/" + fileName); + const doc = new jsPDF({ + unit: "pt", + format: [100, 100], + filters: [], + precision: 2 + }); + doc.addImage(pngData, "PNG", 0, 0, 100, 100, undefined, compression); + comparePdf(doc.output(), `png-${testName}-${compression}.pdf`); + } + + // 1 channel + it("1channels-8bit_NONE", () => runTest("1channels-8bit.png", "NONE")); + it("1channels-8bit_MEDIUM", () => runTest("1channels-8bit.png", "MEDIUM")); + + it("1channels-16bit_NONE", () => runTest("1channels-16bit.png", "NONE")); + it("1channels-16bit_MEDIUM", () => + runTest("1channels-16bit.png", "MEDIUM")); + + // 2 channels + it("2channels-8bit_NONE", () => runTest("2channels-8bit.png", "NONE")); + it("2channels-8bit_MEDIUM", () => runTest("2channels-8bit.png", "MEDIUM")); + + it("2channels-16bit_NONE", () => runTest("2channels-16bit.png", "NONE")); + it("2channels-16bit_MEDIUM", () => + runTest("2channels-16bit.png", "MEDIUM")); + + // 3 channels + it("3channels-8bit_NONE", () => runTest("3channels-8bit.png", "NONE")); + it("3channels-8bit_MEDIUM", () => runTest("3channels-8bit.png", "MEDIUM")); + + it("3channels-16bit_NONE", () => runTest("3channels-16bit.png", "NONE")); + it("3channels-16bit_MEDIUM", () => + runTest("3channels-16bit.png", "MEDIUM")); + + it("3channels-indexed-8bit_NONE", () => + runTest("3channels-indexed-8bit.png", "NONE")); + it("3channels-indexed-8bit_MEDIUM", () => + runTest("3channels-indexed-8bit.png", "MEDIUM")); + + // 4 channels + it("4channels-8bit_NONE", () => runTest("4channels-8bit.png", "NONE")); + it("4channels-8bit_MEDIUM", () => runTest("4channels-8bit.png", "MEDIUM")); + + it("4channels-16bit_NONE", () => runTest("4channels-16bit.png", "NONE")); + it("4channels-16bit_MEDIUM", () => + runTest("4channels-16bit.png", "MEDIUM")); + + it("4channels-indexed-8bit_NONE", () => + runTest("4channels-indexed-8bit.png", "NONE")); + it("4channels-indexed-8bit_MEDIUM", () => + runTest("4channels-indexed-8bit.png", "MEDIUM")); + }); });