'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; } var fs = _interopDefault(require('fs')); var zlib = _interopDefault(require('zlib')); var util = require('util'); var range = function range(left, right, inclusive) { var range = []; var ascending = left < right; var end = !inclusive ? right : ascending ? right + 1 : right - 1; for (var i = left; ascending ? i < end : i > end; ascending ? i++ : i--) { range.push(i); } return range; }; var classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; var createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); var toConsumableArray = function (arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) arr2[i] = arr[i]; return arr2; } else { return Array.from(arr); } }; var inflateAsync = util.promisify(zlib.inflate); var readFileAsync = util.promisify(fs.readFile); var IndexedPNG = function () { function IndexedPNG(data) { classCallCheck(this, IndexedPNG); this.data = data; this.pos = 8; // Skip the default header this.palette = []; this.imgData = []; this.transparency = {}; this.text = {}; this.process(); } createClass(IndexedPNG, [{ key: 'process', value: function process() { var _this = this; var i = void 0; while (true) { var end; var chunkSize = this.readUInt32(); var section = function () { var result = []; for (i = 0; i < 4; i++) { result.push(String.fromCharCode(_this.data[_this.pos++])); } return result; }().join(''); switch (section) { case 'IHDR': // we can grab interesting values from here (like width, height, etc) this.width = this.readUInt32(); this.height = this.readUInt32(); this.bits = this.data[this.pos++]; this.colorType = this.data[this.pos++]; this.compressionMethod = this.data[this.pos++]; this.filterMethod = this.data[this.pos++]; this.interlaceMethod = this.data[this.pos++]; break; case 'PLTE': this.palette = this.read(chunkSize); break; case 'IDAT': for (i = 0, end = chunkSize; i < end; i++) { this.imgData.push(this.data[this.pos++]); } break; case 'tRNS': // This chunk can only occur once and it must occur after the // PLTE chunk and before the IDAT chunk. this.transparency = {}; switch (this.colorType) { case 3: // Indexed color, RGB. Each byte in this chunk is an alpha for // the palette index in the PLTE ("palette") chunk up until the // last non-opaque entry. Set up an array, stretching over all // palette entries which will be 0 (opaque) or 1 (transparent). this.transparency.indexed = this.read(chunkSize); //var short = 255 - this.transparency.indexed.length; var short = this.transparency.indexed.length - 1; if (short > 0) { var asc, end1; for (i = 0, end1 = short, asc = 0 <= end1; asc ? i < end1 : i > end1; asc ? i++ : i--) { this.transparency.indexed.push(255); } } break; case 0: // Greyscale. Corresponding to entries in the PLTE chunk. // Grey is two bytes, range 0 .. (2 ^ bit-depth) - 1 this.transparency.grayscale = this.read(chunkSize)[0]; break; case 2: // True color with proper alpha channel. this.transparency.rgb = this.read(chunkSize); break; } break; case 'tEXt': var text = this.read(chunkSize); var index = text.indexOf(0); var key = String.fromCharCode.apply(String, toConsumableArray(Array.from(text.slice(0, index) || []))); this.text[key] = String.fromCharCode.apply(String, toConsumableArray(Array.from(text.slice(index + 1) || []))); break; case 'IEND': // we've got everything we need! this.colors = function () { switch (_this.colorType) { case 0: case 3: case 4: return 1; case 2: case 6: return 3; } }(); this.hasAlphaChannel = [4, 6].includes(this.colorType); var colors = this.colors + (this.hasAlphaChannel ? 1 : 0); this.pixelBitlength = this.bits * colors; this.colorSpace = function () { switch (_this.colors) { case 1: return 'DeviceGray'; case 3: return 'DeviceRGB'; } }(); this.imgData = Buffer.from(this.imgData); return; break; default: // unknown (or unimportant) section, skip it this.pos += chunkSize; } this.pos += 4; // Skip the CRC if (this.pos > this.data.length) { throw new Error("Incomplete or corrupt IndexedPNG file"); } } } }, { key: 'read', value: function read(bytes) { var _this2 = this; return range(0, bytes, false).map(function (i) { return _this2.data[_this2.pos++]; }); } }, { key: 'readUInt32', value: function readUInt32() { var b1 = this.data[this.pos++] << 24; var b2 = this.data[this.pos++] << 16; var b3 = this.data[this.pos++] << 8; var b4 = this.data[this.pos++]; return b1 | b2 | b3 | b4; } }, { key: 'readUInt16', value: function readUInt16() { var b1 = this.data[this.pos++] << 8; var b2 = this.data[this.pos++]; return b1 | b2; } }, { key: 'decodePixels', value: async function decodePixels() { var data = void 0; try { data = await inflateAsync(this.imgData); } catch (err) { throw err; } var pixelBytes = this.pixelBitlength / 8; var scanlineLength = pixelBytes * this.width; var pixels = Buffer.allocUnsafe(scanlineLength * this.height); var _data = data, length = _data.length; var row = 0; var pos = 0; var c = 0; while (pos < length) { var byte, col, i, left, upper; var end; var end1; var end2; var end3; var end4; switch (data[pos++]) { case 0: // None for (i = 0, end = scanlineLength; i < end; i++) { pixels[c++] = data[pos++]; } break; case 1: // Sub for (i = 0, end1 = scanlineLength; i < end1; i++) { byte = data[pos++]; left = i < pixelBytes ? 0 : pixels[c - pixelBytes]; pixels[c++] = (byte + left) % 256; } break; case 2: // Up for (i = 0, end2 = scanlineLength; i < end2; 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, end3 = scanlineLength; i < end3; 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, end4 = scanlineLength; i < end4; 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]; } var p = left + upper - upperLeft; var pa = Math.abs(p - left); var pb = Math.abs(p - upper); var 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]); } row++; } return pixels; } }, { key: 'decodePalette', value: function decodePalette() { var palette = this.palette; var transparency = this.transparency.indexed || []; var ret = Buffer.allocUnsafe(palette.length / 3 * 4); var pos = 0; var length = palette.length; var c = 0; for (var i = 0, end = palette.length; i < end; i += 3) { var left; ret[pos++] = palette[i]; ret[pos++] = palette[i + 1]; ret[pos++] = palette[i + 2]; ret[pos++] = (left = transparency[c++]) != null ? left : 255; } return ret; } }, { key: 'toPNGData', value: async function toPNGData(options) { var palette = options.palette || this.decodedPalette; if (!this.decodedPixels) { await this.decode(); } if (options.clip) { // Ensure some sane defaults if (options.clip.x == undefined) options.clip.x = 0; if (options.clip.y == undefined) options.clip.y = 0; if (options.clip.w == undefined) options.clip.w = this.width - options.clip.x; if (options.clip.h == undefined) options.clip.h = this.height - options.clip.y; // Now check for user errors. if (options.clip.x < 0 || options.clip.x >= this.width) throw new Error("clip.x is out of bounds"); if (options.clip.y < 0 || options.clip.y >= this.height) throw new Error("clip.y is out of bounds"); if (options.clip.w <= 0 || options.clip.w > this.width) throw new Error("clip.w is out of bounds"); if (options.clip.h <= 0 || options.clip.h > this.height) throw new Error("clip.h is out of bounds"); // Now we can get our clipped array. var pixels = new Uint8ClampedArray(options.clip.w * options.clip.h * 4); for (var x = 0; x < options.clip.w; x++) { for (var y = 0; y < options.clip.h; y++) { var i = (x + y * options.clip.w) * 4; var index = this.decodedPixels[x + options.clip.x + (y + options.clip.y) * this.width] * 4; pixels[i++] = palette[index]; pixels[i++] = palette[index + 1]; pixels[i++] = palette[index + 2]; pixels[i++] = palette[index + 3]; } } return { pixels: pixels, width: options.clip.w }; } else { // Allocate RGBA buffer var _pixels = new Uint8ClampedArray(this.decodedPixels.length * 4); var j = 0; for (var _i = 0; _i < this.decodedPixels.length; _i++) { var _index = this.decodedPixels[_i] * 4; _pixels[j++] = palette[_index]; // R _pixels[j++] = palette[_index + 1]; // G _pixels[j++] = palette[_index + 2]; // B _pixels[j++] = palette[_index + 3]; // A } return { pixels: _pixels, width: this.width }; } } }, { key: 'toImageData', value: async function toImageData(options) { var data = await this.toPNGData(options); return new ImageData(data.pixels, data.width); } }, { key: 'decode', value: async function decode() { this.decodedPalette = this.decodePalette(); this.decodedPixels = await this.decodePixels(); } }]); return IndexedPNG; }(); exports['default'] = IndexedPNG;