"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();
|
}
|
};
|