From 60f267e9cb1890a15f280c651c0444686086e30a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luiz=20Am=C3=A9rico?= Date: Wed, 25 Sep 2019 22:25:21 -0300 Subject: [PATCH] Implement interlace based on #8 --- png-node.js | 210 ++++++++++++++++++++++++++++++++-------------------- 1 file changed, 128 insertions(+), 82 deletions(-) diff --git a/png-node.js b/png-node.js index ae04b1d..c1be760 100644 --- a/png-node.js +++ b/png-node.js @@ -185,113 +185,159 @@ module.exports = class PNG { throw err; } + const { width, height } = this; const pixelBytes = this.pixelBitlength / 8; - const scanlineLength = pixelBytes * this.width; - const pixels = new Buffer(scanlineLength * this.height); + const fullPixels = new Buffer(pixelBytes * width * height); const { length } = data; - let row = 0; let pos = 0; - let c = 0; - - while (pos < length) { - var byte, col, i, left, upper; - switch (data[pos++]) { - case 0: // None - for (i = 0; i < scanlineLength; i++) { - pixels[c++] = data[pos++]; - } - break; - case 1: // Sub - for (i = 0; i < scanlineLength; i++) { - byte = data[pos++]; - left = i < pixelBytes ? 0 : pixels[c - pixelBytes]; - pixels[c++] = (byte + left) % 256; - } - break; - - case 2: // Up - for (i = 0; i < scanlineLength; i++) { - byte = data[pos++]; - col = (i - (i % pixelBytes)) / pixelBytes; - upper = - row && - pixels[ - (row - 1) * scanlineLength + - col * pixelBytes + - (i % pixelBytes) - ]; - pixels[c++] = (upper + byte) % 256; - } - break; - - case 3: // Average - for (i = 0; i < scanlineLength; i++) { - byte = data[pos++]; - col = (i - (i % pixelBytes)) / pixelBytes; - left = i < pixelBytes ? 0 : pixels[c - pixelBytes]; - upper = - row && - pixels[ - (row - 1) * scanlineLength + - col * pixelBytes + - (i % pixelBytes) - ]; - pixels[c++] = (byte + Math.floor((left + upper) / 2)) % 256; - } - break; - - case 4: // Paeth - for (i = 0; i < scanlineLength; i++) { - var paeth, upperLeft; - byte = data[pos++]; - col = (i - (i % pixelBytes)) / pixelBytes; - left = i < pixelBytes ? 0 : pixels[c - pixelBytes]; - - if (row === 0) { - upper = upperLeft = 0; - } else { + function pass(x0, y0, dx, dy) { + let w = Math.ceil((width - x0) / dx); + let h = Math.ceil((height - y0) / dy); + const isFull = width === w && height === h; + const scanlineLength = pixelBytes * w; + const pixels = isFull ? fullPixels : new Buffer(scanlineLength * h); + let row = 0; + let c = 0; + while (row < h && pos < length) { + var byte, col, i, left, upper; + switch (data[pos++]) { + case 0: // None + for (i = 0; i < scanlineLength; i++) { + pixels[c++] = data[pos++]; + } + break; + + case 1: // Sub + for (i = 0; i < scanlineLength; i++) { + byte = data[pos++]; + left = i < pixelBytes ? 0 : pixels[c - pixelBytes]; + pixels[c++] = (byte + left) % 256; + } + break; + + case 2: // Up + for (i = 0; i < scanlineLength; i++) { + byte = data[pos++]; + col = (i - (i % pixelBytes)) / pixelBytes; upper = + row && pixels[ (row - 1) * scanlineLength + col * pixelBytes + (i % pixelBytes) ]; - upperLeft = - col && + pixels[c++] = (upper + byte) % 256; + } + break; + + case 3: // Average + for (i = 0; i < scanlineLength; i++) { + byte = data[pos++]; + col = (i - (i % pixelBytes)) / pixelBytes; + left = i < pixelBytes ? 0 : pixels[c - pixelBytes]; + upper = + row && pixels[ (row - 1) * scanlineLength + - (col - 1) * pixelBytes + + col * pixelBytes + (i % pixelBytes) ]; + pixels[c++] = (byte + Math.floor((left + upper) / 2)) % 256; } + break; + + case 4: // Paeth + for (i = 0; i < scanlineLength; i++) { + var paeth, upperLeft; + byte = data[pos++]; + col = (i - (i % pixelBytes)) / pixelBytes; + left = i < pixelBytes ? 0 : pixels[c - pixelBytes]; + + if (row === 0) { + upper = upperLeft = 0; + } else { + upper = + pixels[ + (row - 1) * scanlineLength + + col * pixelBytes + + (i % pixelBytes) + ]; + upperLeft = + col && + pixels[ + (row - 1) * scanlineLength + + (col - 1) * pixelBytes + + (i % pixelBytes) + ]; + } + + const p = left + upper - upperLeft; + const pa = Math.abs(p - left); + const pb = Math.abs(p - upper); + const pc = Math.abs(p - upperLeft); + + if (pa <= pb && pa <= pc) { + paeth = left; + } else if (pb <= pc) { + paeth = upper; + } else { + paeth = upperLeft; + } - const p = left + upper - upperLeft; - const pa = Math.abs(p - left); - const pb = Math.abs(p - upper); - const pc = Math.abs(p - upperLeft); - - if (pa <= pb && pa <= pc) { - paeth = left; - } else if (pb <= pc) { - paeth = upper; - } else { - paeth = upperLeft; + pixels[c++] = (byte + paeth) % 256; } + break; + + default: + throw new Error(`Invalid filter algorithm: ${data[pos - 1]}`); + } - pixels[c++] = (byte + paeth) % 256; + if (!isFull) { + var fullPos = ((y0 + row * dy) * width + x0) * pixelBytes; + var partPos = row * scanlineLength; + for (i = 0; i < w; i += 1) { + for (var j = 0; j < pixelBytes; j += 1) + fullPixels[fullPos++] = pixels[partPos++]; + fullPos += (dx - 1) * pixelBytes; } - break; + } - default: - throw new Error(`Invalid filter algorithm: ${data[pos - 1]}`); + row++; } + } - row++; + if (this.interlaceMethod === 1) { + /* + 1 6 4 6 2 6 4 6 + 7 7 7 7 7 7 7 7 + 5 6 5 6 5 6 5 6 + 7 7 7 7 7 7 7 7 + 3 6 4 6 3 6 4 6 + 7 7 7 7 7 7 7 7 + 5 6 5 6 5 6 5 6 + 7 7 7 7 7 7 7 7 + */ + pass(0, 0, 8, 8); // 1 + /* NOTE these seem to follow the pattern: + * pass(x, 0, 2*x, 2*x); + * pass(0, x, x, 2*x); + * with x being 4, 2, 1. + */ + pass(4, 0, 8, 8); // 2 + pass(0, 4, 4, 8); // 3 + + pass(2, 0, 4, 4); // 4 + pass(0, 2, 2, 4); // 5 + + pass(1, 0, 2, 2); // 6 + pass(0, 1, 1, 2); // 7 + } else { + pass(0, 0, 1, 1); } - return fn(pixels); + return fn(fullPixels); }); }