UNPKG

pngjs

Version:

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

1,912 lines (1,621 loc) 573 kB
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.png = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){ (function (Buffer){(function (){ "use strict"; let interlaceUtils = require("./interlace"); let pixelBppMapper = [ // 0 - dummy entry function () {}, // 1 - L // 0: 0, 1: 0, 2: 0, 3: 0xff function (pxData, data, pxPos, rawPos) { if (rawPos === data.length) { throw new Error("Ran out of data"); } let pixel = data[rawPos]; pxData[pxPos] = pixel; pxData[pxPos + 1] = pixel; pxData[pxPos + 2] = pixel; pxData[pxPos + 3] = 0xff; }, // 2 - LA // 0: 0, 1: 0, 2: 0, 3: 1 function (pxData, data, pxPos, rawPos) { if (rawPos + 1 >= data.length) { throw new Error("Ran out of data"); } let pixel = data[rawPos]; pxData[pxPos] = pixel; pxData[pxPos + 1] = pixel; pxData[pxPos + 2] = pixel; pxData[pxPos + 3] = data[rawPos + 1]; }, // 3 - RGB // 0: 0, 1: 1, 2: 2, 3: 0xff function (pxData, data, pxPos, rawPos) { if (rawPos + 2 >= data.length) { throw new Error("Ran out of data"); } pxData[pxPos] = data[rawPos]; pxData[pxPos + 1] = data[rawPos + 1]; pxData[pxPos + 2] = data[rawPos + 2]; pxData[pxPos + 3] = 0xff; }, // 4 - RGBA // 0: 0, 1: 1, 2: 2, 3: 3 function (pxData, data, pxPos, rawPos) { if (rawPos + 3 >= data.length) { throw new Error("Ran out of data"); } pxData[pxPos] = data[rawPos]; pxData[pxPos + 1] = data[rawPos + 1]; pxData[pxPos + 2] = data[rawPos + 2]; pxData[pxPos + 3] = data[rawPos + 3]; }, ]; let pixelBppCustomMapper = [ // 0 - dummy entry function () {}, // 1 - L // 0: 0, 1: 0, 2: 0, 3: 0xff function (pxData, pixelData, pxPos, maxBit) { let pixel = pixelData[0]; pxData[pxPos] = pixel; pxData[pxPos + 1] = pixel; pxData[pxPos + 2] = pixel; pxData[pxPos + 3] = maxBit; }, // 2 - LA // 0: 0, 1: 0, 2: 0, 3: 1 function (pxData, pixelData, pxPos) { let pixel = pixelData[0]; pxData[pxPos] = pixel; pxData[pxPos + 1] = pixel; pxData[pxPos + 2] = pixel; pxData[pxPos + 3] = pixelData[1]; }, // 3 - RGB // 0: 0, 1: 1, 2: 2, 3: 0xff function (pxData, pixelData, pxPos, maxBit) { pxData[pxPos] = pixelData[0]; pxData[pxPos + 1] = pixelData[1]; pxData[pxPos + 2] = pixelData[2]; pxData[pxPos + 3] = maxBit; }, // 4 - RGBA // 0: 0, 1: 1, 2: 2, 3: 3 function (pxData, pixelData, pxPos) { pxData[pxPos] = pixelData[0]; pxData[pxPos + 1] = pixelData[1]; pxData[pxPos + 2] = pixelData[2]; pxData[pxPos + 3] = pixelData[3]; }, ]; function bitRetriever(data, depth) { let leftOver = []; let i = 0; function split() { if (i === data.length) { throw new Error("Ran out of data"); } let byte = data[i]; i++; let byte8, byte7, byte6, byte5, byte4, byte3, byte2, byte1; switch (depth) { default: throw new Error("unrecognised depth"); case 16: byte2 = data[i]; i++; leftOver.push((byte << 8) + byte2); break; case 4: byte2 = byte & 0x0f; byte1 = byte >> 4; leftOver.push(byte1, byte2); break; case 2: byte4 = byte & 3; byte3 = (byte >> 2) & 3; byte2 = (byte >> 4) & 3; byte1 = (byte >> 6) & 3; leftOver.push(byte1, byte2, byte3, byte4); break; case 1: byte8 = byte & 1; byte7 = (byte >> 1) & 1; byte6 = (byte >> 2) & 1; byte5 = (byte >> 3) & 1; byte4 = (byte >> 4) & 1; byte3 = (byte >> 5) & 1; byte2 = (byte >> 6) & 1; byte1 = (byte >> 7) & 1; leftOver.push(byte1, byte2, byte3, byte4, byte5, byte6, byte7, byte8); break; } } return { get: function (count) { while (leftOver.length < count) { split(); } let returner = leftOver.slice(0, count); leftOver = leftOver.slice(count); return returner; }, resetAfterLine: function () { leftOver.length = 0; }, end: function () { if (i !== data.length) { throw new Error("extra data found"); } }, }; } function mapImage8Bit(image, pxData, getPxPos, bpp, data, rawPos) { // eslint-disable-line max-params let imageWidth = image.width; let imageHeight = image.height; let imagePass = image.index; for (let y = 0; y < imageHeight; y++) { for (let x = 0; x < imageWidth; x++) { let pxPos = getPxPos(x, y, imagePass); pixelBppMapper[bpp](pxData, data, pxPos, rawPos); rawPos += bpp; //eslint-disable-line no-param-reassign } } return rawPos; } function mapImageCustomBit(image, pxData, getPxPos, bpp, bits, maxBit) { // eslint-disable-line max-params let imageWidth = image.width; let imageHeight = image.height; let imagePass = image.index; for (let y = 0; y < imageHeight; y++) { for (let x = 0; x < imageWidth; x++) { let pixelData = bits.get(bpp); let pxPos = getPxPos(x, y, imagePass); pixelBppCustomMapper[bpp](pxData, pixelData, pxPos, maxBit); } bits.resetAfterLine(); } } exports.dataToBitMap = function (data, bitmapInfo) { let width = bitmapInfo.width; let height = bitmapInfo.height; let depth = bitmapInfo.depth; let bpp = bitmapInfo.bpp; let interlace = bitmapInfo.interlace; let bits; if (depth !== 8) { bits = bitRetriever(data, depth); } let pxData; if (depth <= 8) { pxData = Buffer.alloc(width * height * 4); } else { pxData = new Uint16Array(width * height * 4); } let maxBit = Math.pow(2, depth) - 1; let rawPos = 0; let images; let getPxPos; if (interlace) { images = interlaceUtils.getImagePasses(width, height); getPxPos = interlaceUtils.getInterlaceIterator(width, height); } else { let nonInterlacedPxPos = 0; getPxPos = function () { let returner = nonInterlacedPxPos; nonInterlacedPxPos += 4; return returner; }; images = [{ width: width, height: height }]; } for (let imageIndex = 0; imageIndex < images.length; imageIndex++) { if (depth === 8) { rawPos = mapImage8Bit( images[imageIndex], pxData, getPxPos, bpp, data, rawPos ); } else { mapImageCustomBit( images[imageIndex], pxData, getPxPos, bpp, bits, maxBit ); } } if (depth === 8) { if (rawPos !== data.length) { throw new Error("extra data found"); } } else { bits.end(); } return pxData; }; }).call(this)}).call(this,require("buffer").Buffer) },{"./interlace":11,"buffer":32}],2:[function(require,module,exports){ (function (Buffer){(function (){ "use strict"; let constants = require("./constants"); module.exports = function (dataIn, width, height, options) { let outHasAlpha = [constants.COLORTYPE_COLOR_ALPHA, constants.COLORTYPE_ALPHA].indexOf( options.colorType ) !== -1; if (options.colorType === options.inputColorType) { let bigEndian = (function () { let buffer = new ArrayBuffer(2); new DataView(buffer).setInt16(0, 256, true /* littleEndian */); // Int16Array uses the platform's endianness. return new Int16Array(buffer)[0] !== 256; })(); // If no need to convert to grayscale and alpha is present/absent in both, take a fast route if (options.bitDepth === 8 || (options.bitDepth === 16 && bigEndian)) { return dataIn; } } // map to a UInt16 array if data is 16bit, fix endianness below let data = options.bitDepth !== 16 ? dataIn : new Uint16Array(dataIn.buffer); let maxValue = 255; let inBpp = constants.COLORTYPE_TO_BPP_MAP[options.inputColorType]; if (inBpp === 4 && !options.inputHasAlpha) { inBpp = 3; } let outBpp = constants.COLORTYPE_TO_BPP_MAP[options.colorType]; if (options.bitDepth === 16) { maxValue = 65535; outBpp *= 2; } let outData = Buffer.alloc(width * height * outBpp); let inIndex = 0; let outIndex = 0; let bgColor = options.bgColor || {}; if (bgColor.red === undefined) { bgColor.red = maxValue; } if (bgColor.green === undefined) { bgColor.green = maxValue; } if (bgColor.blue === undefined) { bgColor.blue = maxValue; } function getRGBA() { let red; let green; let blue; let alpha = maxValue; switch (options.inputColorType) { case constants.COLORTYPE_COLOR_ALPHA: alpha = data[inIndex + 3]; red = data[inIndex]; green = data[inIndex + 1]; blue = data[inIndex + 2]; break; case constants.COLORTYPE_COLOR: red = data[inIndex]; green = data[inIndex + 1]; blue = data[inIndex + 2]; break; case constants.COLORTYPE_ALPHA: alpha = data[inIndex + 1]; red = data[inIndex]; green = red; blue = red; break; case constants.COLORTYPE_GRAYSCALE: red = data[inIndex]; green = red; blue = red; break; default: throw new Error( "input color type:" + options.inputColorType + " is not supported at present" ); } if (options.inputHasAlpha) { if (!outHasAlpha) { alpha /= maxValue; red = Math.min( Math.max(Math.round((1 - alpha) * bgColor.red + alpha * red), 0), maxValue ); green = Math.min( Math.max(Math.round((1 - alpha) * bgColor.green + alpha * green), 0), maxValue ); blue = Math.min( Math.max(Math.round((1 - alpha) * bgColor.blue + alpha * blue), 0), maxValue ); } } return { red: red, green: green, blue: blue, alpha: alpha }; } for (let y = 0; y < height; y++) { for (let x = 0; x < width; x++) { let rgba = getRGBA(data, inIndex); switch (options.colorType) { case constants.COLORTYPE_COLOR_ALPHA: case constants.COLORTYPE_COLOR: if (options.bitDepth === 8) { outData[outIndex] = rgba.red; outData[outIndex + 1] = rgba.green; outData[outIndex + 2] = rgba.blue; if (outHasAlpha) { outData[outIndex + 3] = rgba.alpha; } } else { outData.writeUInt16BE(rgba.red, outIndex); outData.writeUInt16BE(rgba.green, outIndex + 2); outData.writeUInt16BE(rgba.blue, outIndex + 4); if (outHasAlpha) { outData.writeUInt16BE(rgba.alpha, outIndex + 6); } } break; case constants.COLORTYPE_ALPHA: case constants.COLORTYPE_GRAYSCALE: { // Convert to grayscale and alpha let grayscale = (rgba.red + rgba.green + rgba.blue) / 3; if (options.bitDepth === 8) { outData[outIndex] = grayscale; if (outHasAlpha) { outData[outIndex + 1] = rgba.alpha; } } else { outData.writeUInt16BE(grayscale, outIndex); if (outHasAlpha) { outData.writeUInt16BE(rgba.alpha, outIndex + 2); } } break; } default: throw new Error("unrecognised color Type " + options.colorType); } inIndex += inBpp; outIndex += outBpp; } } return outData; }; }).call(this)}).call(this,require("buffer").Buffer) },{"./constants":4,"buffer":32}],3:[function(require,module,exports){ (function (process,Buffer){(function (){ "use strict"; let util = require("util"); let Stream = require("stream"); let ChunkStream = (module.exports = function () { Stream.call(this); this._buffers = []; this._buffered = 0; this._reads = []; this._paused = false; this._encoding = "utf8"; this.writable = true; }); util.inherits(ChunkStream, Stream); ChunkStream.prototype.read = function (length, callback) { this._reads.push({ length: Math.abs(length), // if length < 0 then at most this length allowLess: length < 0, func: callback, }); process.nextTick( function () { this._process(); // its paused and there is not enought data then ask for more if (this._paused && this._reads && this._reads.length > 0) { this._paused = false; this.emit("drain"); } }.bind(this) ); }; ChunkStream.prototype.write = function (data, encoding) { if (!this.writable) { this.emit("error", new Error("Stream not writable")); return false; } let dataBuffer; if (Buffer.isBuffer(data)) { dataBuffer = data; } else { dataBuffer = Buffer.from(data, encoding || this._encoding); } this._buffers.push(dataBuffer); this._buffered += dataBuffer.length; this._process(); // ok if there are no more read requests if (this._reads && this._reads.length === 0) { this._paused = true; } return this.writable && !this._paused; }; ChunkStream.prototype.end = function (data, encoding) { if (data) { this.write(data, encoding); } this.writable = false; // already destroyed if (!this._buffers) { return; } // enqueue or handle end if (this._buffers.length === 0) { this._end(); } else { this._buffers.push(null); this._process(); } }; ChunkStream.prototype.destroySoon = ChunkStream.prototype.end; ChunkStream.prototype._end = function () { if (this._reads.length > 0) { this.emit("error", new Error("Unexpected end of input")); } this.destroy(); }; ChunkStream.prototype.destroy = function () { if (!this._buffers) { return; } this.writable = false; this._reads = null; this._buffers = null; this.emit("close"); }; ChunkStream.prototype._processReadAllowingLess = function (read) { // ok there is any data so that we can satisfy this request this._reads.shift(); // == read // first we need to peek into first buffer let smallerBuf = this._buffers[0]; // ok there is more data than we need if (smallerBuf.length > read.length) { this._buffered -= read.length; this._buffers[0] = smallerBuf.slice(read.length); read.func.call(this, smallerBuf.slice(0, read.length)); } else { // ok this is less than maximum length so use it all this._buffered -= smallerBuf.length; this._buffers.shift(); // == smallerBuf read.func.call(this, smallerBuf); } }; ChunkStream.prototype._processRead = function (read) { this._reads.shift(); // == read let pos = 0; let count = 0; let data = Buffer.alloc(read.length); // create buffer for all data while (pos < read.length) { let buf = this._buffers[count++]; let len = Math.min(buf.length, read.length - pos); buf.copy(data, pos, 0, len); pos += len; // last buffer wasn't used all so just slice it and leave if (len !== buf.length) { this._buffers[--count] = buf.slice(len); } } // remove all used buffers if (count > 0) { this._buffers.splice(0, count); } this._buffered -= read.length; read.func.call(this, data); }; ChunkStream.prototype._process = function () { try { // as long as there is any data and read requests while (this._buffered > 0 && this._reads && this._reads.length > 0) { let read = this._reads[0]; // read any data (but no more than length) if (read.allowLess) { this._processReadAllowingLess(read); } else if (this._buffered >= read.length) { // ok we can meet some expectations this._processRead(read); } else { // not enought data to satisfy first request in queue // so we need to wait for more break; } } if (this._buffers && !this.writable) { this._end(); } } catch (ex) { this.emit("error", ex); } }; }).call(this)}).call(this,require('_process'),require("buffer").Buffer) },{"_process":63,"buffer":32,"stream":65,"util":84}],4:[function(require,module,exports){ "use strict"; module.exports = { PNG_SIGNATURE: [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a], TYPE_IHDR: 0x49484452, TYPE_IEND: 0x49454e44, TYPE_IDAT: 0x49444154, TYPE_PLTE: 0x504c5445, TYPE_tRNS: 0x74524e53, // eslint-disable-line camelcase TYPE_gAMA: 0x67414d41, // eslint-disable-line camelcase // color-type bits COLORTYPE_GRAYSCALE: 0, COLORTYPE_PALETTE: 1, COLORTYPE_COLOR: 2, COLORTYPE_ALPHA: 4, // e.g. grayscale and alpha // color-type combinations COLORTYPE_PALETTE_COLOR: 3, COLORTYPE_COLOR_ALPHA: 6, COLORTYPE_TO_BPP_MAP: { 0: 1, 2: 3, 3: 1, 4: 2, 6: 4, }, GAMMA_DIVISION: 100000, }; },{}],5:[function(require,module,exports){ "use strict"; let crcTable = []; (function () { for (let i = 0; i < 256; i++) { let currentCrc = i; for (let j = 0; j < 8; j++) { if (currentCrc & 1) { currentCrc = 0xedb88320 ^ (currentCrc >>> 1); } else { currentCrc = currentCrc >>> 1; } } crcTable[i] = currentCrc; } })(); let CrcCalculator = (module.exports = function () { this._crc = -1; }); CrcCalculator.prototype.write = function (data) { for (let i = 0; i < data.length; i++) { this._crc = crcTable[(this._crc ^ data[i]) & 0xff] ^ (this._crc >>> 8); } return true; }; CrcCalculator.prototype.crc32 = function () { return this._crc ^ -1; }; CrcCalculator.crc32 = function (buf) { let crc = -1; for (let i = 0; i < buf.length; i++) { crc = crcTable[(crc ^ buf[i]) & 0xff] ^ (crc >>> 8); } return crc ^ -1; }; },{}],6:[function(require,module,exports){ (function (Buffer){(function (){ "use strict"; let paethPredictor = require("./paeth-predictor"); function filterNone(pxData, pxPos, byteWidth, rawData, rawPos) { for (let x = 0; x < byteWidth; x++) { rawData[rawPos + x] = pxData[pxPos + x]; } } function filterSumNone(pxData, pxPos, byteWidth) { let sum = 0; let length = pxPos + byteWidth; for (let i = pxPos; i < length; i++) { sum += Math.abs(pxData[i]); } return sum; } function filterSub(pxData, pxPos, byteWidth, rawData, rawPos, bpp) { for (let x = 0; x < byteWidth; x++) { let left = x >= bpp ? pxData[pxPos + x - bpp] : 0; let val = pxData[pxPos + x] - left; rawData[rawPos + x] = val; } } function filterSumSub(pxData, pxPos, byteWidth, bpp) { let sum = 0; for (let x = 0; x < byteWidth; x++) { let left = x >= bpp ? pxData[pxPos + x - bpp] : 0; let val = pxData[pxPos + x] - left; sum += Math.abs(val); } return sum; } function filterUp(pxData, pxPos, byteWidth, rawData, rawPos) { for (let x = 0; x < byteWidth; x++) { let up = pxPos > 0 ? pxData[pxPos + x - byteWidth] : 0; let val = pxData[pxPos + x] - up; rawData[rawPos + x] = val; } } function filterSumUp(pxData, pxPos, byteWidth) { let sum = 0; let length = pxPos + byteWidth; for (let x = pxPos; x < length; x++) { let up = pxPos > 0 ? pxData[x - byteWidth] : 0; let val = pxData[x] - up; sum += Math.abs(val); } return sum; } function filterAvg(pxData, pxPos, byteWidth, rawData, rawPos, bpp) { for (let x = 0; x < byteWidth; x++) { let left = x >= bpp ? pxData[pxPos + x - bpp] : 0; let up = pxPos > 0 ? pxData[pxPos + x - byteWidth] : 0; let val = pxData[pxPos + x] - ((left + up) >> 1); rawData[rawPos + x] = val; } } function filterSumAvg(pxData, pxPos, byteWidth, bpp) { let sum = 0; for (let x = 0; x < byteWidth; x++) { let left = x >= bpp ? pxData[pxPos + x - bpp] : 0; let up = pxPos > 0 ? pxData[pxPos + x - byteWidth] : 0; let val = pxData[pxPos + x] - ((left + up) >> 1); sum += Math.abs(val); } return sum; } function filterPaeth(pxData, pxPos, byteWidth, rawData, rawPos, bpp) { for (let x = 0; x < byteWidth; x++) { let left = x >= bpp ? pxData[pxPos + x - bpp] : 0; let up = pxPos > 0 ? pxData[pxPos + x - byteWidth] : 0; let upleft = pxPos > 0 && x >= bpp ? pxData[pxPos + x - (byteWidth + bpp)] : 0; let val = pxData[pxPos + x] - paethPredictor(left, up, upleft); rawData[rawPos + x] = val; } } function filterSumPaeth(pxData, pxPos, byteWidth, bpp) { let sum = 0; for (let x = 0; x < byteWidth; x++) { let left = x >= bpp ? pxData[pxPos + x - bpp] : 0; let up = pxPos > 0 ? pxData[pxPos + x - byteWidth] : 0; let upleft = pxPos > 0 && x >= bpp ? pxData[pxPos + x - (byteWidth + bpp)] : 0; let val = pxData[pxPos + x] - paethPredictor(left, up, upleft); sum += Math.abs(val); } return sum; } let filters = { 0: filterNone, 1: filterSub, 2: filterUp, 3: filterAvg, 4: filterPaeth, }; let filterSums = { 0: filterSumNone, 1: filterSumSub, 2: filterSumUp, 3: filterSumAvg, 4: filterSumPaeth, }; module.exports = function (pxData, width, height, options, bpp) { let filterTypes; if (!("filterType" in options) || options.filterType === -1) { filterTypes = [0, 1, 2, 3, 4]; } else if (typeof options.filterType === "number") { filterTypes = [options.filterType]; } else { throw new Error("unrecognised filter types"); } if (options.bitDepth === 16) { bpp *= 2; } let byteWidth = width * bpp; let rawPos = 0; let pxPos = 0; let rawData = Buffer.alloc((byteWidth + 1) * height); let sel = filterTypes[0]; for (let y = 0; y < height; y++) { if (filterTypes.length > 1) { // find best filter for this line (with lowest sum of values) let min = Infinity; for (let i = 0; i < filterTypes.length; i++) { let sum = filterSums[filterTypes[i]](pxData, pxPos, byteWidth, bpp); if (sum < min) { sel = filterTypes[i]; min = sum; } } } rawData[rawPos] = sel; rawPos++; filters[sel](pxData, pxPos, byteWidth, rawData, rawPos, bpp); rawPos += byteWidth; pxPos += byteWidth; } return rawData; }; }).call(this)}).call(this,require("buffer").Buffer) },{"./paeth-predictor":15,"buffer":32}],7:[function(require,module,exports){ (function (Buffer){(function (){ "use strict"; let util = require("util"); let ChunkStream = require("./chunkstream"); let Filter = require("./filter-parse"); let FilterAsync = (module.exports = function (bitmapInfo) { ChunkStream.call(this); let buffers = []; let that = this; this._filter = new Filter(bitmapInfo, { read: this.read.bind(this), write: function (buffer) { buffers.push(buffer); }, complete: function () { that.emit("complete", Buffer.concat(buffers)); }, }); this._filter.start(); }); util.inherits(FilterAsync, ChunkStream); }).call(this)}).call(this,require("buffer").Buffer) },{"./chunkstream":3,"./filter-parse":9,"buffer":32,"util":84}],8:[function(require,module,exports){ (function (Buffer){(function (){ "use strict"; let SyncReader = require("./sync-reader"); let Filter = require("./filter-parse"); exports.process = function (inBuffer, bitmapInfo) { let outBuffers = []; let reader = new SyncReader(inBuffer); let filter = new Filter(bitmapInfo, { read: reader.read.bind(reader), write: function (bufferPart) { outBuffers.push(bufferPart); }, complete: function () {}, }); filter.start(); reader.process(); return Buffer.concat(outBuffers); }; }).call(this)}).call(this,require("buffer").Buffer) },{"./filter-parse":9,"./sync-reader":22,"buffer":32}],9:[function(require,module,exports){ (function (Buffer){(function (){ "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(); } }; }).call(this)}).call(this,require("buffer").Buffer) },{"./interlace":11,"./paeth-predictor":15,"buffer":32}],10:[function(require,module,exports){ (function (Buffer){(function (){ "use strict"; function dePalette(indata, outdata, width, height, palette) { let pxPos = 0; // use values from palette for (let y = 0; y < height; y++) { for (let x = 0; x < width; x++) { let color = palette[indata[pxPos]]; if (!color) { throw new Error("index " + indata[pxPos] + " not in palette"); } for (let i = 0; i < 4; i++) { outdata[pxPos + i] = color[i]; } pxPos += 4; } } } function replaceTransparentColor(indata, outdata, width, height, transColor) { let pxPos = 0; for (let y = 0; y < height; y++) { for (let x = 0; x < width; x++) { let makeTrans = false; if (transColor.length === 1) { if (transColor[0] === indata[pxPos]) { makeTrans = true; } } else if ( transColor[0] === indata[pxPos] && transColor[1] === indata[pxPos + 1] && transColor[2] === indata[pxPos + 2] ) { makeTrans = true; } if (makeTrans) { for (let i = 0; i < 4; i++) { outdata[pxPos + i] = 0; } } pxPos += 4; } } } function scaleDepth(indata, outdata, width, height, depth) { let maxOutSample = 255; let maxInSample = Math.pow(2, depth) - 1; let pxPos = 0; for (let y = 0; y < height; y++) { for (let x = 0; x < width; x++) { for (let i = 0; i < 4; i++) { outdata[pxPos + i] = Math.floor( (indata[pxPos + i] * maxOutSample) / maxInSample + 0.5 ); } pxPos += 4; } } } module.exports = function (indata, imageData, skipRescale = false) { let depth = imageData.depth; let width = imageData.width; let height = imageData.height; let colorType = imageData.colorType; let transColor = imageData.transColor; let palette = imageData.palette; let outdata = indata; // only different for 16 bits if (colorType === 3) { // paletted dePalette(indata, outdata, width, height, palette); } else { if (transColor) { replaceTransparentColor(indata, outdata, width, height, transColor); } // if it needs scaling if (depth !== 8 && !skipRescale) { // if we need to change the buffer size if (depth === 16) { outdata = Buffer.alloc(width * height * 4); } scaleDepth(indata, outdata, width, height, depth); } } return outdata; }; }).call(this)}).call(this,require("buffer").Buffer) },{"buffer":32}],11:[function(require,module,exports){ "use strict"; // Adam 7 // 0 1 2 3 4 5 6 7 // 0 x 6 4 6 x 6 4 6 // 1 7 7 7 7 7 7 7 7 // 2 5 6 5 6 5 6 5 6 // 3 7 7 7 7 7 7 7 7 // 4 3 6 4 6 3 6 4 6 // 5 7 7 7 7 7 7 7 7 // 6 5 6 5 6 5 6 5 6 // 7 7 7 7 7 7 7 7 7 let imagePasses = [ { // pass 1 - 1px x: [0], y: [0], }, { // pass 2 - 1px x: [4], y: [0], }, { // pass 3 - 2px x: [0, 4], y: [4], }, { // pass 4 - 4px x: [2, 6], y: [0, 4], }, { // pass 5 - 8px x: [0, 2, 4, 6], y: [2, 6], }, { // pass 6 - 16px x: [1, 3, 5, 7], y: [0, 2, 4, 6], }, { // pass 7 - 32px x: [0, 1, 2, 3, 4, 5, 6, 7], y: [1, 3, 5, 7], }, ]; exports.getImagePasses = function (width, height) { let images = []; let xLeftOver = width % 8; let yLeftOver = height % 8; let xRepeats = (width - xLeftOver) / 8; let yRepeats = (height - yLeftOver) / 8; for (let i = 0; i < imagePasses.length; i++) { let pass = imagePasses[i]; let passWidth = xRepeats * pass.x.length; let passHeight = yRepeats * pass.y.length; for (let j = 0; j < pass.x.length; j++) { if (pass.x[j] < xLeftOver) { passWidth++; } else { break; } } for (let j = 0; j < pass.y.length; j++) { if (pass.y[j] < yLeftOver) { passHeight++; } else { break; } } if (passWidth > 0 && passHeight > 0) { images.push({ width: passWidth, height: passHeight, index: i }); } } return images; }; exports.getInterlaceIterator = function (width) { return function (x, y, pass) { let outerXLeftOver = x % imagePasses[pass].x.length; let outerX = ((x - outerXLeftOver) / imagePasses[pass].x.length) * 8 + imagePasses[pass].x[outerXLeftOver]; let outerYLeftOver = y % imagePasses[pass].y.length; let outerY = ((y - outerYLeftOver) / imagePasses[pass].y.length) * 8 + imagePasses[pass].y[outerYLeftOver]; return outerX * 4 + outerY * width * 4; }; }; },{}],12:[function(require,module,exports){ (function (Buffer){(function (){ "use strict"; let util = require("util"); let Stream = require("stream"); let constants = require("./constants"); let Packer = require("./packer"); let PackerAsync = (module.exports = function (opt) { Stream.call(this); let options = opt || {}; this._packer = new Packer(options); this._deflate = this._packer.createDeflate(); this.readable = true; }); util.inherits(PackerAsync, Stream); PackerAsync.prototype.pack = function (data, width, height, gamma) { // Signature this.emit("data", Buffer.from(constants.PNG_SIGNATURE)); this.emit("data", this._packer.packIHDR(width, height)); if (gamma) { this.emit("data", this._packer.packGAMA(gamma)); } let filteredData = this._packer.filterData(data, width, height); // compress it this._deflate.on("error", this.emit.bind(this, "error")); this._deflate.on( "data", function (compressedData) { this.emit("data", this._packer.packIDAT(compressedData)); }.bind(this) ); this._deflate.on( "end", function () { this.emit("data", this._packer.packIEND()); this.emit("end"); }.bind(this) ); this._deflate.end(filteredData); }; }).call(this)}).call(this,require("buffer").Buffer) },{"./constants":4,"./packer":14,"buffer":32,"stream":65,"util":84}],13:[function(require,module,exports){ (function (Buffer){(function (){ "use strict"; let hasSyncZlib = true; let zlib = require("zlib"); if (!zlib.deflateSync) { hasSyncZlib = false; } let constants = require("./constants"); let Packer = require("./packer"); module.exports = function (metaData, opt) { if (!hasSyncZlib) { throw new Error( "To use the sync capability of this library in old node versions, please pin pngjs to v2.3.0" ); } let options = opt || {}; let packer = new Packer(options); let chunks = []; // Signature chunks.push(Buffer.from(constants.PNG_SIGNATURE)); // Header chunks.push(packer.packIHDR(metaData.width, metaData.height)); if (metaData.gamma) { chunks.push(packer.packGAMA(metaData.gamma)); } let filteredData = packer.filterData( metaData.data, metaData.width, metaData.height ); // compress it let compressedData = zlib.deflateSync( filteredData, packer.getDeflateOptions() ); filteredData = null; if (!compressedData || !compressedData.length) { throw new Error("bad png - invalid compressed data response"); } chunks.push(packer.packIDAT(compressedData)); // End chunks.push(packer.packIEND()); return Buffer.concat(chunks); }; }).call(this)}).call(this,require("buffer").Buffer) },{"./constants":4,"./packer":14,"buffer":32,"zlib":31}],14:[function(require,module,exports){ (function (Buffer){(function (){ "use strict"; let constants = require("./constants"); let CrcStream = require("./crc"); let bitPacker = require("./bitpacker"); let filter = require("./filter-pack"); let zlib = require("zlib"); let Packer = (module.exports = function (options) { this._options = options; options.deflateChunkSize = options.deflateChunkSize || 32 * 1024; options.deflateLevel = options.deflateLevel != null ? options.deflateLevel : 9; options.deflateStrategy = options.deflateStrategy != null ? options.deflateStrategy : 3; options.inputHasAlpha = options.inputHasAlpha != null ? options.inputHasAlpha : true; options.deflateFactory = options.deflateFactory || zlib.createDeflate; options.bitDepth = options.bitDepth || 8; // This is outputColorType options.colorType = typeof options.colorType === "number" ? options.colorType : constants.COLORTYPE_COLOR_ALPHA; options.inputColorType = typeof options.inputColorType === "number" ? options.inputColorType : constants.COLORTYPE_COLOR_ALPHA; if ( [ constants.COLORTYPE_GRAYSCALE, constants.COLORTYPE_COLOR, constants.COLORTYPE_COLOR_ALPHA, constants.COLORTYPE_ALPHA, ].indexOf(options.colorType) === -1 ) { throw new Error( "option color type:" + options.colorType + " is not supported at present" ); } if ( [ constants.COLORTYPE_GRAYSCALE, constants.COLORTYPE_COLOR, constants.COLORTYPE_COLOR_ALPHA, constants.COLORTYPE_ALPHA, ].indexOf(options.inputColorType) === -1 ) { throw new Error( "option input color type:" + options.inputColorType + " is not supported at present" ); } if (options.bitDepth !== 8 && options.bitDepth !== 16) { throw new Error( "option bit depth:" + options.bitDepth + " is not supported at present" ); } }); Packer.prototype.getDeflateOptions = function () { return { chunkSize: this._options.deflateChunkSize, level: this._options.deflateLevel, strategy: this._options.deflateStrategy, }; }; Packer.prototype.createDeflate = function () { return this._options.deflateFactory(this.getDeflateOptions()); }; Packer.prototype.filterData = function (data, width, height) { // convert to correct format for filtering (e.g. right bpp and bit depth) let packedData = bitPacker(data, width, height, this._options); // filter pixel data let bpp = constants.COLORTYPE_TO_BPP_MAP[this._options.colorType]; let filteredData = filter(packedData, width, height, this._options, bpp); return filteredData; }; Packer.prototype._packChunk = function (type, data) { let len = data ? data.length : 0; let buf = Buffer.alloc(len + 12); buf.writeUInt32BE(len, 0); buf.writeUInt32BE(type, 4); if (data) { data.copy(buf, 8); } buf.writeInt32BE( CrcStream.crc32(buf.slice(4, buf.length - 4)), buf.length - 4 ); return buf; }; Packer.prototype.packGAMA = function (gamma) { let buf = Buffer.alloc(4); buf.writeUInt32BE(Math.floor(gamma * constants.GAMMA_DIVISION), 0); return this._packChunk(constants.TYPE_gAMA, buf); }; Packer.prototype.packIHDR = function (width, height) { let buf = Buffer.alloc(13); buf.writeUInt32BE(width, 0); buf.writeUInt32BE(height, 4); buf[8] = this._options.bitDepth; // Bit depth buf[9] = this._options.colorType; // colorType buf[10] = 0; // compression buf[11] = 0; // filter buf[12] = 0; // interlace return this._packChunk(constants.TYPE_IHDR, buf); }; Packer.prototype.packIDAT = function (data) { return this._packChunk(constants.TYPE_IDAT, data); }; Packer.prototype.packIEND = function () { return this._packChunk(constants.TYPE_IEND, null); }; }).call(this)}).call(this,require("buffer").Buffer) },{"./bitpacker":2,"./constants":4,"./crc":5,"./filter-pack":6,"buffer":32,"zlib":31}],15:[function(require,module,exports){ "use strict"; module.exports = function paethPredictor(left, above, upLeft) { let paeth = left + above - upLeft; let pLeft = Math.abs(paeth - left); let pAbove = Math.abs(paeth - above); let pUpLeft = Math.abs(paeth - upLeft); if (pLeft <= pAbove && pLeft <= pUpLeft) { return left; } if (pAbove <= pUpLeft) { return above; } return upLeft; }; },{}],16:[function(require,module,exports){ "use strict"; let util = require("util"); let zlib = require("zlib"); let ChunkStream = require("./chunkstream"); let FilterAsync = require("./filter-parse-async"); let Parser = require("./parser"); let bitmapper = require("./bitmapper"); let formatNormaliser = require("./format-normaliser"); let ParserAsync = (module.exports = function (options) { ChunkStream.call(this); this._parser = new Parser(options, { read: this.read.bind(this), error: this._handleError.bind(this), metadata: this._handleMetaData.bind(this), gamma: this.emit.bind(this, "gamma"), palette: this._handlePalette.bind(this), transColor: this._handleTransColor.bind(this), finished: this._finished.bind(this), inflateData: this._inflateData.bind(this), simpleTransparency: this._simpleTransparency.bind(this), headersFinished: this._headersFinished.bind(this), }); this._options = options; this.writable = true; this._parser.start(); }); util.inherits(ParserAsync, ChunkStream); ParserAsync.prototype._handleError = function (err) { this.emit("error", err); this.writable = false; this.destroy(); if (this._inflate && this._inflate.destroy) { this._inflate.destroy(); } if (this._filter) { this._filter.destroy(); // For backward compatibility with Node 7 and below. // Suppress errors due to _inflate calling write() even after // it's destroy()'ed. this._filter.on("error", function () {}); } this.errord = true; }; ParserAsync.prototype._inflateData = function (data) { if (!this._inflate) { if (this._bitmapInfo.interlace) { this._inflate = zlib.createInflate(); this._inflate.on("error", this.emit.bind(this, "error")); this._filter.on("complete", this._complete.bind(this)); this._inflate.pipe(this._filter); } else { let rowSize = ((this._bitmapInfo.width * this._bitmapInfo.bpp * this._bitmapInfo.depth + 7) >> 3) + 1; let imageSize = rowSize * this._bitmapInfo.height; let chunkSize = Math.max(imageSize, zlib.Z_MIN_CHUNK); this._inflate = zlib.createInflate({ chunkSize: chunkSize }); let leftToInflate = imageSize; let emitError = this.emit.bind(this, "error"); this._inflate.on("error", function (err) { if (!leftToInflate) { return; } emitError(err); }); this._filter.on("complete", this._complete.bind(this)); let filterWrite = this._filter.write.bind(this._filter); this._inflate.on("data", function (chunk) { if (!leftToInflate) { return; } if (chunk.length > leftToInflate) { chunk = chunk.slice(0, leftToInflate); } leftToInflate -= chunk.length; filterWrite(chunk); }); this._inflate.on("end", this._filter.end.bind(this._filter)); } } this._inflate.write(data); }; ParserAsync.prototype._handleMetaData = function (metaData) { this._metaData = metaData; this._bitmapInfo = Object.create(metaData); this._filter = new FilterAsync(this._bitmapInfo); }; ParserAsync.prototype._handleTransColor = function (transColor) { this._bitmapInfo.transColor = transColor; }; ParserAsync.prototype._handlePalette = function (palette) { this._bitmapInfo.palette = palette; }; ParserAsync.prototype._simpleTransparency = function () { this._metaData.alpha = true; }; ParserAsync.prototype._headersFinished = function () { // Up until this point, we don't know if we have a tRNS chunk (alpha) // so we can't emit metadata any earlier this.emit("metadata", this._metaData); }; ParserAsync.prototype._finished = function () { if (this.errord) { return; } if (!this._inflate) { this.emit("error", "No Inflate block"); } else { // no more data to inflate this._inflate.end(); } }; ParserAsync.prototype._complete = function (filteredData) { if (this.errord) { return; } let normalisedBitmapData; try { let bitmapData = bitmapper.dataToBitMap(filteredData, this._bitmapInfo); normalisedBitmapData = formatNormaliser( bitmapData, this._bitmapInfo, this._options.skipRescale ); bitmapData = null; } catch (ex) { this._handleError(ex); return; } this.emit("parsed", normalisedBitmapData); }; },{"./bitmapper":1,"./chunkstream":3,"./filter-parse-async":7,"./format-normaliser":10,"./parser":18,"util":84,"zlib":31}],17:[function(require,module,exports){ (function (Buffer){(function (){ "use strict"; let hasSyncZlib = true; let zlib = require("zlib"); let inflateSync = require("./sync-inflate"); if (!zlib.deflateSync) { hasSyncZlib = false; } let SyncReader = require("./sync-reader"); let FilterSync = require("./filter-parse-sync"); let Parser = require("./parser"); let bitmapper = require("./bitmapper"); let formatNormaliser = require("./format-normaliser"); module.exports = function (buffer, options) { if (!hasSyncZlib) { throw new Error( "To use the sync capability of this library in old node versions, please pin pngjs to v2.3.0" ); } let err; function handleError(_err_) { err = _err_; } let metaData; function handleMetaData(_metaData_) { metaData = _metaData_; } function handleTransColor(transColor) { metaData.transColor = transColor; } function handlePalette(palette) { metaData.palette = palette; } function handleSimpleTransparency() { metaData.alpha = true; } let gamma; function handleGamma(_gamma_) { gamma = _gamma_; } let inflateDataList = []; function handleInflateData(inflatedData) { inflateDataList.push(inflatedData); } let reader = new SyncReader(buffer); let parser = new Parser(options, { read: reader.read.bind(reader), error: handleError, metadata: handleMetaData, gamma: handleGamma, palette: handlePalette, transColor: handleTransColor, inflateData: handleInflateData, simpleTransparency: handleSimpleTransparency, }); parser.start(); reader.process(); if (err) { throw err; } //join together the inflate datas let inflateData = Buffer.concat(inflateDataList); inflateDataList.length = 0; let inflatedData; if (metaData.interlace) { inflatedData = zlib.inflateSync(inflateData); } else { let rowSize = ((metaData.width * metaData.bpp * metaData.depth + 7) >> 3) + 1; let imageSize = rowSize * metaData.height; inflatedData = inflateSync(inflateData, { chunkSize: imageSize, maxLength: imageSize, }); } inflateData = null; if (!inflatedData || !inflatedData.length) { throw new Error("bad png - invalid inflate data response"); } let unfilteredData = FilterSync.process(inflatedData, metaData); inflateData = null; let bitmapData = bitmapper.dataToBitMap(unfilteredData, metaData); unfilteredData = null; let normalisedBitmapData = formatNormaliser( bitmapData, metaData, options.skipRescale ); metaData.data = normalisedBitmapData; metaData.gamma = gamma || 0; return metaData; }; }).call(this)}).call(this,require("buffer").Buffer) },{"./bitmapper":1,"./filter-parse-sync":8,"./format-normaliser":10,"./parser":18,"./sync-inflate":21,"./sync-reader":22,"buffer":32,"zlib":31}],18:[function(require,module,exports){ (function (Buffer){(function (){ "use strict"; let constants = require("./constants"); let CrcCalculator = require("./crc"); let Parser = (module.exports = function (options, dependencies) { this._options = options; options.checkCRC = options.checkCRC !== false; this._hasIHDR = false; this._hasIEND = false; this._emittedHeadersFinished = false; // input flags/metadata this._palette = []; this._colorType = 0; this._chunks = {}; this._chunks[constants.TYPE_IHDR] = this._handleIHDR.bind(this); this._chunks[constants.TYPE_IEND] = this._handleIEND.bind(this); this._chunks[constants.TYPE_IDAT] = this._handleIDAT.bind(this); this._chunks[constants.TYPE_PLTE] = this._handlePLTE.bind(this); this._chunks[constants.TYPE_tRNS] = this._handleTRNS.bind(this); this._chunks[constants.TYPE_gAMA] = this._handleGAMA.bind(this); this.read = dependencies.read; this.error = dependencies.error; this.metadata = dependencies.metadata; this.gamma = dependencies.gamma; this.transColor = dependencies.transColor; this.palette = dependencies.palette; this.parsed = dependencies.parsed; this.inflateData = dependencies.inflateData; this.finished = dependencies.finished; this.simpleTransparency = dependencies.simpleTransparency; this.headersFinished = dependencies.headersFinished || function () {}; }); Parser.prototype.start = function () { this.read(constants.PNG_SIGNATURE.length, this._parseSignature.bind(this)); }; Parser.prototype._parseSignature = function (data) { let signature = constants.PNG_SIGNATURE; for (let i = 0; i < signature.length; i++) { if (data[i] !== signature[i]) { this.error(new Error("Invalid file signature")); return; } } this.read(8, this._parseChunkBegin.bind(this)); };