UNPKG

pngjs

Version:

PNG encoder/decoder in pure JS, supporting any bit size & interlace, async & sync with full test suite.

178 lines (156 loc) 4.8 kB
"use strict"; let interlaceUtils = require("./interlace"); let paethPredictor = require("./paeth-predictor"); function getByteWidth(width, bpp, depth) { let byteWidth = width * bpp; if (depth !== 8) { byteWidth = Math.ceil(byteWidth / (8 / depth)); } return byteWidth; } let Filter = (module.exports = function (bitmapInfo, dependencies) { let width = bitmapInfo.width; let height = bitmapInfo.height; let interlace = bitmapInfo.interlace; let bpp = bitmapInfo.bpp; let depth = bitmapInfo.depth; this.read = dependencies.read; this.write = dependencies.write; this.complete = dependencies.complete; this._imageIndex = 0; this._images = []; if (interlace) { let passes = interlaceUtils.getImagePasses(width, height); for (let i = 0; i < passes.length; i++) { this._images.push({ byteWidth: getByteWidth(passes[i].width, bpp, depth), height: passes[i].height, lineIndex: 0, }); } } else { this._images.push({ byteWidth: getByteWidth(width, bpp, depth), height: height, lineIndex: 0, }); } // when filtering the line we look at the pixel to the left // the spec also says it is done on a byte level regardless of the number of pixels // so if the depth is byte compatible (8 or 16) we subtract the bpp in order to compare back // a pixel rather than just a different byte part. However if we are sub byte, we ignore. if (depth === 8) { this._xComparison = bpp; } else if (depth === 16) { this._xComparison = bpp * 2; } else { this._xComparison = 1; } }); Filter.prototype.start = function () { this.read( this._images[this._imageIndex].byteWidth + 1, this._reverseFilterLine.bind(this) ); }; Filter.prototype._unFilterType1 = function ( rawData, unfilteredLine, byteWidth ) { let xComparison = this._xComparison; let xBiggerThan = xComparison - 1; for (let x = 0; x < byteWidth; x++) { let rawByte = rawData[1 + x]; let f1Left = x > xBiggerThan ? unfilteredLine[x - xComparison] : 0; unfilteredLine[x] = rawByte + f1Left; } }; Filter.prototype._unFilterType2 = function ( rawData, unfilteredLine, byteWidth ) { let lastLine = this._lastLine; for (let x = 0; x < byteWidth; x++) { let rawByte = rawData[1 + x]; let f2Up = lastLine ? lastLine[x] : 0; unfilteredLine[x] = rawByte + f2Up; } }; Filter.prototype._unFilterType3 = function ( rawData, unfilteredLine, byteWidth ) { let xComparison = this._xComparison; let xBiggerThan = xComparison - 1; let lastLine = this._lastLine; for (let x = 0; x < byteWidth; x++) { let rawByte = rawData[1 + x]; let f3Up = lastLine ? lastLine[x] : 0; let f3Left = x > xBiggerThan ? unfilteredLine[x - xComparison] : 0; let f3Add = Math.floor((f3Left + f3Up) / 2); unfilteredLine[x] = rawByte + f3Add; } }; Filter.prototype._unFilterType4 = function ( rawData, unfilteredLine, byteWidth ) { let xComparison = this._xComparison; let xBiggerThan = xComparison - 1; let lastLine = this._lastLine; for (let x = 0; x < byteWidth; x++) { let rawByte = rawData[1 + x]; let f4Up = lastLine ? lastLine[x] : 0; let f4Left = x > xBiggerThan ? unfilteredLine[x - xComparison] : 0; let f4UpLeft = x > xBiggerThan && lastLine ? lastLine[x - xComparison] : 0; let f4Add = paethPredictor(f4Left, f4Up, f4UpLeft); unfilteredLine[x] = rawByte + f4Add; } }; Filter.prototype._reverseFilterLine = function (rawData) { let filter = rawData[0]; let unfilteredLine; let currentImage = this._images[this._imageIndex]; let byteWidth = currentImage.byteWidth; if (filter === 0) { unfilteredLine = rawData.slice(1, byteWidth + 1); } else { unfilteredLine = Buffer.alloc(byteWidth); switch (filter) { case 1: this._unFilterType1(rawData, unfilteredLine, byteWidth); break; case 2: this._unFilterType2(rawData, unfilteredLine, byteWidth); break; case 3: this._unFilterType3(rawData, unfilteredLine, byteWidth); break; case 4: this._unFilterType4(rawData, unfilteredLine, byteWidth); break; default: throw new Error("Unrecognised filter type - " + filter); } } this.write(unfilteredLine); currentImage.lineIndex++; if (currentImage.lineIndex >= currentImage.height) { this._lastLine = null; this._imageIndex++; currentImage = this._images[this._imageIndex]; } else { this._lastLine = unfilteredLine; } if (currentImage) { // read, using the byte width that may be from the new current image this.read(currentImage.byteWidth + 1, this._reverseFilterLine.bind(this)); } else { this._lastLine = null; this.complete(); } };