"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
var pako_1 = __importDefault(require("pako"));
var PNG = /** @class */ (function () {
    function PNG(data) {
        var _this = this;
        this.decode = function () {
            var retVal = new Uint8Array(_this.width * _this.height * 4);
            var pixels = _this.decodePixels();
            _this.copyImageDataToBuffer(retVal, pixels);
            return retVal;
        };
        this.copyImageDataToBuffer = function (imageData, pixels) {
            var colors = _this.colors;
            var palette;
            var alpha = _this.hasAlphaChannel;
            if (_this.palette.length) {
                palette = _this.decodePalette();
                colors = 4;
                alpha = true;
            }
            var data = imageData;
            var length = data.length;
            var input = palette || pixels;
            var i = 0;
            var j = 0;
            if (colors === 1) {
                while (i < length) {
                    var k = palette ? pixels[i / 4] * 4 : j;
                    var v = input[k++];
                    data[i++] = v;
                    data[i++] = v;
                    data[i++] = v;
                    data[i++] = alpha ? input[k++] : 255;
                    j = k;
                }
            }
            else {
                while (i < length) {
                    var k = palette ? pixels[i / 4] * 4 : j;
                    data[i++] = input[k++];
                    data[i++] = input[k++];
                    data[i++] = input[k++];
                    data[i++] = alpha ? input[k++] : 255;
                    j = k;
                }
            }
        };
        this.decodePixels = function () {
            var data = pako_1.default.inflate(_this.imgData);
            var pixelBytes = _this.pixelBitlength / 8;
            var scanlineLength = pixelBytes * _this.width;
            var pixels = new Uint8Array(scanlineLength * _this.height);
            var length = data.length;
            var row = 0;
            var pos = 0;
            var c = 0;
            while (pos < length) {
                switch (data[pos++]) {
                    // None
                    case 0: {
                        for (var i = 0; i < scanlineLength; i++) {
                            pixels[c++] = data[pos++];
                        }
                        break;
                    }
                    // Sub
                    case 1: {
                        for (var i = 0; i < scanlineLength; i++) {
                            var byte = data[pos++];
                            var left = i < pixelBytes ? 0 : pixels[c - pixelBytes];
                            pixels[c++] = (byte + left) % 256;
                        }
                        break;
                    }
                    // Up
                    case 2: {
                        for (var i = 0; i < scanlineLength; i++) {
                            var byte = data[pos++];
                            var col = (i - (i % pixelBytes)) / pixelBytes;
                            var pixelsIdx = (row - 1) * scanlineLength + col * pixelBytes + (i % pixelBytes);
                            var upper = row && pixels[pixelsIdx];
                            pixels[c++] = (upper + byte) % 256;
                        }
                        break;
                    }
                    // Average
                    case 3: {
                        for (var i = 0; i < scanlineLength; i++) {
                            var byte = data[pos++];
                            var col = (i - (i % pixelBytes)) / pixelBytes;
                            var left = i < pixelBytes ? 0 : pixels[c - pixelBytes];
                            var pixelsIdx = (row - 1) * scanlineLength + col * pixelBytes + (i % pixelBytes);
                            var upper = row && pixels[pixelsIdx];
                            pixels[c++] = (byte + Math.floor((left + upper) / 2)) % 256;
                        }
                        break;
                    }
                    // Paeth
                    case 4: {
                        for (var i = 0; i < scanlineLength; i++) {
                            var byte = data[pos++];
                            var col = (i - (i % pixelBytes)) / pixelBytes;
                            var left = i < pixelBytes ? 0 : pixels[c - pixelBytes];
                            var upper = void 0;
                            var upperLeft = void 0;
                            if (row === 0) {
                                upper = upperLeft = 0;
                            }
                            else {
                                var upperIdx = (row - 1) * scanlineLength +
                                    col * pixelBytes +
                                    (i % pixelBytes);
                                var upperLeftIdx = (row - 1) * scanlineLength +
                                    (col - 1) * pixelBytes +
                                    (i % pixelBytes);
                                upper = pixels[upperIdx];
                                upperLeft = col && pixels[upperLeftIdx];
                            }
                            var p = left + upper - upperLeft;
                            var pa = Math.abs(p - left);
                            var pb = Math.abs(p - upper);
                            var pc = Math.abs(p - upperLeft);
                            var paeth = void 0;
                            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;
        };
        this.read = function (numBytes) {
            var results = [];
            for (var i = 0; i < numBytes; i++) {
                results[i] = _this.data[_this.pos++];
            }
            return results;
        };
        this.readUInt32 = function () {
            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;
        };
        this.decodePalette = function () {
            var palette = _this.palette;
            var transparency = _this.transparency.indexed || [];
            var retVal = new Uint8Array(transparency.length + palette.length);
            var pos = 0;
            var c = 0;
            for (var i = 0; i < palette.length; i++) {
                retVal[pos++] = palette[i];
                retVal[pos++] = palette[i + 1];
                retVal[pos++] = palette[i + 2];
                var temp = transparency[c++];
                retVal[pos++] = temp !== undefined ? temp : 255;
            }
            return retVal;
        };
        this.data = data;
        this.pos = 8; // Skip the default header;
        this.palette = [];
        var imgDataBuff = [];
        this.transparency = {};
        this.text = {};
        while (true) {
            var chunkSize = this.readUInt32();
            var section = '';
            for (var i = 0; i < 4; i++) {
                section += String.fromCharCode(this.data[this.pos++]);
            }
            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 (var i = 0; i < chunkSize; i++) {
                        imgDataBuff.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;
                            if (short > 0) {
                                for (var i = 0; i < 255; 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, text.slice(0, index));
                    this.text[key] = String.fromCharCode.apply(String, text.slice(index + 1));
                    break;
                }
                case 'IEND': {
                    // We've got everything we need!
                    var colorTypeMap = {
                        0: 1,
                        3: 1,
                        4: 1,
                        2: 3,
                        6: 3,
                    };
                    this.colors = colorTypeMap[this.colorType];
                    this.hasAlphaChannel = [4, 6].includes(this.colorType);
                    var colors = this.colors + (this.hasAlphaChannel ? 1 : 0);
                    this.pixelBitlength = this.bits * colors;
                    this.colorSpace = {
                        1: 'DeviceGray',
                        3: 'DeviceRGB',
                    }[this.colors];
                    this.imgData = new Uint8Array(imgDataBuff);
                    return;
                }
                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 PNG file');
            }
        }
    }
    PNG.load = function (data) { return new PNG(data); };
    return PNG;
}());
exports.default = PNG;
