pngjs icon indicating copy to clipboard operation
pngjs copied to clipboard

saving the same pixel data twice (or more) results in an invalid additional files

Open ccoenen opened this issue 3 years ago • 1 comments

I am saving a file to PNG, but the pixel data will keep updating (and I will need to save it over and over again).

Currently, the first file works out fine, and every subsequent file will be an invalid file. I also receive an error message after a few saved files ("info" lines are my own logger), this output was generated with node --trace-warnings index.js:

info: storing current image in public/stored/1618704065758.png
info: storing current image in public/stored/1618704066763.png
info: storing current image in public/stored/1618704067767.png
info: storing current image in public/stored/1618704068781.png
info: storing current image in public/stored/1618704069783.png
info: storing current image in public/stored/1618704070792.png
info: storing current image in public/stored/1618704071793.png
(node:16828) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 end listeners added to [Stream]. Use emitter.setMaxListeners() to increase limit
    at _addListener (node:events:451:17)
    at exports.PNG.addListener (node:events:467:10)
    at exports.PNG.Stream.pipe (node:internal/streams/legacy:38:12)
    at (my own code, redacted)
    at new Promise (<anonymous>)
    at (my own code, redacted)
    at Timeout._onTimeout ((my own code, redacted))
    at listOnTimeout (node:internal/timers:556:17)
    at processTimers (node:internal/timers:499:7)
(node:16828) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 close listeners added to [Stream]. Use emitter.setMaxListeners() to increase limit
    at _addListener (node:events:451:17)
    at exports.PNG.addListener (node:events:467:10)
    at exports.PNG.Stream.pipe (node:internal/streams/legacy:39:12)
    at (my own code, redacted)
    at new Promise (<anonymous>)
    at Canvas.store (my own code, redacted)
    at Timeout._onTimeout (my own code, redacted)
    at listOnTimeout (node:internal/timers:556:17)
    at processTimers (node:internal/timers:499:7)
info: storing current image in public/stored/1618704072801.png
... (info lines continue) ...

Here's a pretty minimal example to illustrate my problem:

import fs from 'fs';
import { PNG } from 'pngjs';

const width = 256;
const height = 256;
const canvas = new PNG({width, height});

for (let x = 0; x < width; x++) {
	for (let y = 0; y < height; y++) {
		// drawing something nice, just so that we have something to look at.
		canvas.data[(y * width + x) * 4 + 0] = x;
		canvas.data[(y * width + x) * 4 + 1] = y;
		canvas.data[(y * width + x) * 4 + 2] = 0;
		canvas.data[(y * width + x) * 4 + 3] = 0xFF; // FF => fully opaque.
	}
}

const out = fs.createWriteStream('file 1.png');
canvas.pack().pipe(out);
out.on('finish', () => {
	const out2 = fs.createWriteStream('file 2.png');
	canvas.pack().pipe(out2);
});

What currently happens

file 1.png will show a nice demo pattern and is about 1KB in size. file 2.png is broken and (usually) 33 bytes. If it is larger (which also happens) it's a multiple of 33 bytes.

What I would like it to do

I'd like it to store the pixel data over and over again. I would also be fine with a workaround.

ccoenen avatar Apr 17 '21 23:04 ccoenen

I'm not sure if there is a proper way to do this, but I ran into the same issue. I worked around it by just creating a copy of the PNG before I pack it with PNG.bitblt

import fs from 'fs';
import { PNG } from 'pngjs';

const width = 256;
const height = 256;
const canvas = new PNG({ width, height });

for (let x = 0; x < width; x++) {
    for (let y = 0; y < height; y++) {
        // drawing something nice, just so that we have something to look at.
        canvas.data[(y * width + x) * 4 + 0] = x;
        canvas.data[(y * width + x) * 4 + 1] = y;
        canvas.data[(y * width + x) * 4 + 2] = 0;
        canvas.data[(y * width + x) * 4 + 3] = 0xFF; // FF => fully opaque.
    }
}

const exportPNG = (png, fileName) => {
    const pngCopy = new PNG({ width: png.width, height: png.height });
    PNG.bitblt(png, pngCopy, 0, 0, png.width, png.height, 0, 0);
    pngCopy.pack().pipe(fs.createWriteStream(fileName));
}

exportPNG(png, 'file 1.png');
exportPNG(png, 'file 2.png');

Claytonn avatar Jun 23 '21 21:06 Claytonn