UNPKG

png-async

Version:

A simple and non-blocking PNG encoder / decoder.

261 lines 8.8 kB
"use strict"; const ChunkStream = require("./chunk-stream"); const pixelBppMap = { 1: { 0: 0, 1: 0, 2: 0, 3: 0xff }, 2: { 0: 0, 1: 0, 2: 0, 3: 1 }, 3: { 0: 0, 1: 1, 2: 2, 3: 0xff }, 4: { 0: 0, 1: 1, 2: 2, 3: 3 } }; function PaethPredictor(left, above, upLeft) { const p = left + above - upLeft; const pLeft = Math.abs(p - left); const pAbove = Math.abs(p - above); const pUpLeft = Math.abs(p - upLeft); if (pLeft <= pAbove && pLeft <= pUpLeft) { return left; } else if (pAbove <= pUpLeft) { return above; } else { return upLeft; } } ; class Filter extends ChunkStream { constructor(width, height, bpp, data, option) { super(); this._width = width; this._height = height; this._bpp = bpp; this._data = data; this._option = option; this._line = 0; if (option.filterType === undefined || option.filterType === -1) { this._filterTypes = [0, 1, 2, 3, 4]; } else if (typeof option.filterType === "number") { this._filterTypes = [option.filterType]; } this._filters = { 0: this._filterNone.bind(this), 1: this._filterSub.bind(this), 2: this._filterUp.bind(this), 3: this._filterAvg.bind(this), 4: this._filterPaeth.bind(this) }; this.read(this._width * bpp + 1, this._reverseFilterLine.bind(this)); } filter() { const pxData = this._data; const rawData = Buffer.alloc(((this._width << 2) + 1) * this._height); let i, l, y, min, sel, sum; for (y = 0; y < this._height; y++) { // find best filter for this line (with lowest sum of values) min = Infinity; sel = 0; for (i = 0, l = this._filterTypes.length; i < l; i++) { sum = this._filters[this._filterTypes[i]](pxData, y, null); if (sum < min) { sel = this._filterTypes[i]; min = sum; } } this._filters[sel](pxData, y, rawData); } return rawData; } _reverseFilterLine(rawData) { const pxData = this._data; const pxLineLength = this._width << 2; const pxRowPos = this._line * pxLineLength; const filter = rawData[0]; let i, x, pxPos, rawPos, idx, left, up, add, upLeft; if (filter === 0) { for (x = 0; x < this._width; x++) { pxPos = pxRowPos + (x << 2); rawPos = 1 + x * this._bpp; for (i = 0; i < 4; i++) { idx = pixelBppMap[this._bpp][i]; pxData[pxPos + i] = idx !== 0xff ? rawData[rawPos + idx] : 0xff; } } } else if (filter === 1) { for (x = 0; x < this._width; x++) { pxPos = pxRowPos + (x << 2); rawPos = 1 + x * this._bpp; for (i = 0; i < 4; i++) { idx = pixelBppMap[this._bpp][i]; left = x > 0 ? pxData[pxPos + i - 4] : 0; pxData[pxPos + i] = idx !== 0xff ? rawData[rawPos + idx] + left : 0xff; } } } else if (filter === 2) { for (x = 0; x < this._width; x++) { pxPos = pxRowPos + (x << 2); rawPos = 1 + x * this._bpp; for (i = 0; i < 4; i++) { idx = pixelBppMap[this._bpp][i]; up = this._line > 0 ? pxData[pxPos - pxLineLength + i] : 0; pxData[pxPos + i] = idx !== 0xff ? rawData[rawPos + idx] + up : 0xff; } } } else if (filter === 3) { for (x = 0; x < this._width; x++) { pxPos = pxRowPos + (x << 2); rawPos = 1 + x * this._bpp; for (i = 0; i < 4; i++) { idx = pixelBppMap[this._bpp][i]; left = x > 0 ? pxData[pxPos + i - 4] : 0; up = this._line > 0 ? pxData[pxPos - pxLineLength + i] : 0; add = Math.floor((left + up) / 2); pxData[pxPos + i] = idx !== 0xff ? rawData[rawPos + idx] + add : 0xff; } } } else if (filter === 4) { for (x = 0; x < this._width; x++) { pxPos = pxRowPos + (x << 2); rawPos = 1 + x * this._bpp; for (i = 0; i < 4; i++) { idx = pixelBppMap[this._bpp][i]; left = x > 0 ? pxData[pxPos + i - 4] : 0; up = this._line > 0 ? pxData[pxPos - pxLineLength + i] : 0; upLeft = x > 0 && this._line > 0 ? pxData[pxPos - pxLineLength + i - 4] : 0; add = PaethPredictor(left, up, upLeft); pxData[pxPos + i] = idx !== 0xff ? rawData[rawPos + idx] + add : 0xff; } } } this._line++; if (this._line < this._height) { this.read(this._width * this._bpp + 1, this._reverseFilterLine.bind(this)); } else { this.emit("complete", this._data, this._width, this._height); } } _filterNone(pxData, y, rawData) { const pxRowLength = this._width << 2; const rawRowLength = pxRowLength + 1; let sum = 0; if (!rawData) { for (let x = 0; x < pxRowLength; x++) { sum += Math.abs(pxData[y * pxRowLength + x]); } } else { rawData[y * rawRowLength] = 0; pxData.copy(rawData, rawRowLength * y + 1, pxRowLength * y, pxRowLength * (y + 1)); } return sum; } _filterSub(pxData, y, rawData) { const pxRowLength = this._width << 2; const rawRowLength = pxRowLength + 1; let sum = 0; let left, val; if (rawData) { rawData[y * rawRowLength] = 1; } for (let x = 0; x < pxRowLength; x++) { left = x >= 4 ? pxData[y * pxRowLength + x - 4] : 0; val = pxData[y * pxRowLength + x] - left; if (!rawData) { sum += Math.abs(val); } else { rawData[y * rawRowLength + 1 + x] = val; } } return sum; } _filterUp(pxData, y, rawData) { const pxRowLength = this._width << 2; const rawRowLength = pxRowLength + 1; let sum = 0; let up, val; if (rawData) { rawData[y * rawRowLength] = 2; } for (let x = 0; x < pxRowLength; x++) { up = y > 0 ? pxData[(y - 1) * pxRowLength + x] : 0; val = pxData[y * pxRowLength + x] - up; if (!rawData) { sum += Math.abs(val); } else { rawData[y * rawRowLength + 1 + x] = val; } } return sum; } _filterAvg(pxData, y, rawData) { const pxRowLength = this._width << 2; const rawRowLength = pxRowLength + 1; let sum = 0; let left, up, val; if (rawData) { rawData[y * rawRowLength] = 3; } for (let x = 0; x < pxRowLength; x++) { left = x >= 4 ? pxData[y * pxRowLength + x - 4] : 0; up = y > 0 ? pxData[(y - 1) * pxRowLength + x] : 0; val = pxData[y * pxRowLength + x] - ((left + up) >> 1); if (!rawData) { sum += Math.abs(val); } else { rawData[y * rawRowLength + 1 + x] = val; } } return sum; } _filterPaeth(pxData, y, rawData) { const pxRowLength = this._width << 2; const rawRowLength = pxRowLength + 1; let sum = 0; let left, up, upLeft, val; if (rawData) { rawData[y * rawRowLength] = 4; } for (let x = 0; x < pxRowLength; x++) { left = x >= 4 ? pxData[y * pxRowLength + x - 4] : 0; up = y > 0 ? pxData[(y - 1) * pxRowLength + x] : 0; upLeft = x >= 4 && y > 0 ? pxData[(y - 1) * pxRowLength + x - 4] : 0; val = pxData[y * pxRowLength + x] - PaethPredictor(left, up, upLeft); if (!rawData) { sum += Math.abs(val); } else { rawData[y * rawRowLength + 1 + x] = val; } } return sum; } } module.exports = Filter; //# sourceMappingURL=filter.js.map