png-async
Version:
A simple and non-blocking PNG encoder / decoder.
261 lines • 8.8 kB
JavaScript
"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