'use strict'; var interlaceUtils = require('./interlace'); var paethPredictor = require('./paeth-predictor'); function getByteWidth(width, bpp, depth) { var byteWidth = width * bpp; if (depth !== 8) { byteWidth = Math.ceil(byteWidth / (8 / depth)); } return byteWidth; } var Filter = module.exports = function(bitmapInfo, dependencies) { var width = bitmapInfo.width; var height = bitmapInfo.height; var interlace = bitmapInfo.interlace; var bpp = bitmapInfo.bpp; var depth = bitmapInfo.depth; this.read = dependencies.read; this.write = dependencies.write; this.complete = dependencies.complete; this._imageIndex = 0; this._images = []; if (interlace) { var passes = interlaceUtils.getImagePasses(width, height); for (var 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) { var xComparison = this._xComparison; var xBiggerThan = xComparison - 1; for (var x = 0; x < byteWidth; x++) { var rawByte = rawData[1 + x]; var f1Left = x > xBiggerThan ? unfilteredLine[x - xComparison] : 0; unfilteredLine[x] = rawByte + f1Left; } }; Filter.prototype._unFilterType2 = function(rawData, unfilteredLine, byteWidth) { var lastLine = this._lastLine; for (var x = 0; x < byteWidth; x++) { var rawByte = rawData[1 + x]; var f2Up = lastLine ? lastLine[x] : 0; unfilteredLine[x] = rawByte + f2Up; } }; Filter.prototype._unFilterType3 = function(rawData, unfilteredLine, byteWidth) { var xComparison = this._xComparison; var xBiggerThan = xComparison - 1; var lastLine = this._lastLine; for (var x = 0; x < byteWidth; x++) { var rawByte = rawData[1 + x]; var f3Up = lastLine ? lastLine[x] : 0; var f3Left = x > xBiggerThan ? unfilteredLine[x - xComparison] : 0; var f3Add = Math.floor((f3Left + f3Up) / 2); unfilteredLine[x] = rawByte + f3Add; } }; Filter.prototype._unFilterType4 = function(rawData, unfilteredLine, byteWidth) { var xComparison = this._xComparison; var xBiggerThan = xComparison - 1; var lastLine = this._lastLine; for (var x = 0; x < byteWidth; x++) { var rawByte = rawData[1 + x]; var f4Up = lastLine ? lastLine[x] : 0; var f4Left = x > xBiggerThan ? unfilteredLine[x - xComparison] : 0; var f4UpLeft = x > xBiggerThan && lastLine ? lastLine[x - xComparison] : 0; var f4Add = paethPredictor(f4Left, f4Up, f4UpLeft); unfilteredLine[x] = rawByte + f4Add; } }; Filter.prototype._reverseFilterLine = function(rawData) { var filter = rawData[0]; var unfilteredLine; var currentImage = this._images[this._imageIndex]; var byteWidth = currentImage.byteWidth; if (filter === 0) { unfilteredLine = rawData.slice(1, byteWidth + 1); } else { unfilteredLine = new Buffer(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(); } };