diff --git a/demo/png-benchmark.js b/demo/png-benchmark.js new file mode 100644 index 000000000..1f621fe7b --- /dev/null +++ b/demo/png-benchmark.js @@ -0,0 +1,33 @@ +const PDFDocument = require('../'); +const fs = require('fs'); + +const doc = new PDFDocument(); + +// files with alpha channel -> uses zlib.deflate +const files = [ + 'test.png', + 'test3.png' +]; + +const filesData = files.map(fileName => { + return fs.readFileSync(`images/${fileName}`); +}); + +const iterationCount = 100; + +console.time('png-bench') + +for (let i = 0; i < iterationCount; i++) { + filesData.forEach(data => { + doc.image(data) + doc.addPage() + }) +} + +doc.on('data', () => {}) + +doc.on('end', () => { + console.timeEnd('png-bench'); +}); + +doc.end(); \ No newline at end of file diff --git a/lib/image/png.js b/lib/image/png.js index 394767db9..0e9acfb68 100644 --- a/lib/image/png.js +++ b/lib/image/png.js @@ -114,7 +114,7 @@ class PNGImage { const imgData = new Buffer(pixelCount * colorByteSize); const alphaChannel = new Buffer(pixelCount); - let i = (p = (a = 0)); + let i = p = a = 0; const len = pixels.length; while (i < len) { imgData[p++] = pixels[i++]; @@ -123,22 +123,13 @@ class PNGImage { alphaChannel[a++] = pixels[i++]; } - let done = 0; - zlib.deflate(imgData, (err, imgData1) => { - this.imgData = imgData1; - if (err) { throw err; } - if (++done === 2) { return this.finalize(); } - }); - - return zlib.deflate(alphaChannel, (err, alphaChannel1) => { - this.alphaChannel = alphaChannel1; - if (err) { throw err; } - if (++done === 2) { return this.finalize(); } - }); + this.imgData = zlib.deflateSync(imgData); + this.alphaChannel = zlib.deflateSync(alphaChannel); + return this.finalize(); }); } - loadIndexedAlphaChannel(fn) { + loadIndexedAlphaChannel() { const transparency = this.image.transparency.indexed; return this.image.decodePixels(pixels => { const alphaChannel = new Buffer(this.width * this.height); @@ -148,11 +139,8 @@ class PNGImage { alphaChannel[i++] = transparency[pixels[j]]; } - return zlib.deflate(alphaChannel, (err, alphaChannel1) => { - this.alphaChannel = alphaChannel1; - if (err) { throw err; } - return this.finalize(); - }); + this.alphaChannel = zlib.deflateSync(alphaChannel); + return this.finalize(); }); } } diff --git a/tests/images/fish.png b/tests/images/fish.png new file mode 100644 index 000000000..fca79c0b0 Binary files /dev/null and b/tests/images/fish.png differ diff --git a/tests/unit/png.spec.js b/tests/unit/png.spec.js new file mode 100644 index 000000000..809fc113c --- /dev/null +++ b/tests/unit/png.spec.js @@ -0,0 +1,170 @@ +const PDFDocument = require("../../lib/document").default; +const PDFReference = require("../../lib/reference").default; +const PNGImage = require("../../lib/image/png").default; +const fs = require("fs"); + +describe("PNGImage", () => { + let document; + + const createImage = fileName => { + const img = new PNGImage(fs.readFileSync(fileName), "I1"); + // noop data manipulation methods + img.loadIndexedAlphaChannel = () => { + if (img.image.hasAlphaChannel) { + img.alphaChannel = {}; + } + }; + img.splitAlphaChannel = () => { + if (img.image.hasAlphaChannel) { + img.alphaChannel = {}; + } + }; + img.embed(document); + img.finalize(); + return img; + }; + + beforeEach(() => { + document = new PDFDocument(); + }); + + test("RGB", () => { + // ImageWidth = 400 + // ImageHeight = 533 + // BitDepth = 8 + // ColorType = 2 (RGB) + // Compression = 0 + // Filter = 0 + // Interlace = 0 + + const img = createImage("./demo/images/test2.png"); + + expect(img.obj.data).toMatchObject({ + BitsPerComponent: 8, + ColorSpace: "DeviceRGB", + Filter: "FlateDecode", + Height: 533, + Length: 397011, + Subtype: "Image", + Type: "XObject", + Width: 400, + DecodeParms: expect.any(PDFReference) + }); + + expect(img.obj.data.DecodeParms.data).toMatchObject({ + BitsPerComponent: 8, + Colors: 3, + Columns: 400, + Predictor: 15 + }); + }); + + test("RGB with Alpha", () => { + // ImageWidth = 409 + // ImageHeight = 400 + // BitDepth = 8 + // ColorType = 6 (RGB with Alpha) + // Compression = 0 + // Filter = 0 + // Interlace = 0 + + const img = createImage("./tests/images/bee.png"); + + expect(img.obj.data).toMatchObject({ + BitsPerComponent: 8, + ColorSpace: "DeviceRGB", + Filter: "FlateDecode", + Height: 400, + Length: 47715, + Subtype: "Image", + Type: "XObject", + Width: 409, + SMask: expect.any(PDFReference) + }); + + expect(img.obj.data.SMask.data).toMatchObject({ + BitsPerComponent: 8, + ColorSpace: "DeviceGray", + Decode: [ + 0, + 1 + ], + Filter: "FlateDecode", + Height: 400, + Length: 16, + Subtype: "Image", + Type: "XObject", + Width: 409, + }); + }); + + test("Pallete", () => { + // ImageWidth = 980 + // ImageHeight = 540 + // BitDepth = 8 + // ColorType = 3 (Pallete) + // Compression = 0 + // Filter = 0 + // Interlace = 0 + + const img = createImage("./demo/images/test3.png"); + + expect(img.obj.data).toMatchObject({ + BitsPerComponent: 8, + ColorSpace: ["Indexed", "DeviceRGB", 255, expect.any(PDFReference)], + Filter: "FlateDecode", + Height: 540, + Length: 56682, + Subtype: "Image", + Type: "XObject", + Width: 980, + DecodeParms: expect.any(PDFReference) + }); + + expect(img.obj.data.DecodeParms.data).toMatchObject({ + BitsPerComponent: 8, + Colors: 1, + Columns: 980, + Predictor: 15 + }); + }); + + test("Grayscale with Alpha", () => { + // ImageWidth = 112 + // ImageHeight = 112 + // BitDepth = 8 + // ColorType = 4 (Grayscale with Alpha) + // Compression = 0 + // Filter = 0 + // Interlace = 0 + + const img = createImage("./tests/images/fish.png"); + + expect(img.obj.data).toMatchObject({ + BitsPerComponent: 8, + ColorSpace: "DeviceGray", + Filter: "FlateDecode", + Height: 112, + Length: 9922, + Subtype: "Image", + Type: "XObject", + Width: 112, + SMask: expect.any(PDFReference) + }); + + expect(img.obj.data.SMask.data).toMatchObject({ + BitsPerComponent: 8, + ColorSpace: "DeviceGray", + Decode: [ + 0, + 1 + ], + Filter: "FlateDecode", + Height: 112, + Length: 16, + Subtype: "Image", + Type: "XObject", + Width: 112, + }); + }); +});