mirror of
https://github.com/danbulant/pngjs
synced 2026-07-09 05:00:39 +00:00
Tidy up filtering.
This commit is contained in:
parent
6607d71c04
commit
adf5216a49
12 changed files with 394 additions and 331 deletions
|
|
@ -4,8 +4,8 @@ clone_depth: 10
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
matrix:
|
matrix:
|
||||||
- nodejs_version: 0.12
|
- nodejs_version: '0.12'
|
||||||
- nodejs_version: 2.x
|
- nodejs_version: ''
|
||||||
|
|
||||||
install:
|
install:
|
||||||
- ps: Install-Product node $env:nodejs_version
|
- ps: Install-Product node $env:nodejs_version
|
||||||
|
|
|
||||||
186
lib/filter-pack.js
Normal file
186
lib/filter-pack.js
Normal file
|
|
@ -0,0 +1,186 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var paethPredictor = require('./paeth-predictor');
|
||||||
|
|
||||||
|
function filterNone(pxData, pxPos, byteWidth, rawData, rawPos) {
|
||||||
|
|
||||||
|
pxData.copy(rawData, rawPos, pxPos, pxPos + byteWidth);
|
||||||
|
}
|
||||||
|
|
||||||
|
function filterSumNone(pxData, pxPos, byteWidth) {
|
||||||
|
|
||||||
|
var sum = 0;
|
||||||
|
var length = pxPos + byteWidth;
|
||||||
|
|
||||||
|
for (var i = pxPos; i < length; i++) {
|
||||||
|
sum += Math.abs(pxData[i]);
|
||||||
|
}
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
function filterSub(pxData, pxPos, byteWidth, rawData, rawPos) {
|
||||||
|
|
||||||
|
for (var x = 0; x < byteWidth; x++) {
|
||||||
|
|
||||||
|
var left = x >= 4 ? pxData[pxPos + x - 4] : 0;
|
||||||
|
var val = pxData[pxPos + x] - left;
|
||||||
|
|
||||||
|
rawData[rawPos + x] = val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function filterSumSub(pxData, pxPos, byteWidth) {
|
||||||
|
|
||||||
|
var sum = 0;
|
||||||
|
for (var x = 0; x < byteWidth; x++) {
|
||||||
|
|
||||||
|
var left = x >= 4 ? pxData[pxPos + x - 4] : 0;
|
||||||
|
var val = pxData[pxPos + x] - left;
|
||||||
|
|
||||||
|
sum += Math.abs(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
function filterUp(pxData, pxPos, byteWidth, rawData, rawPos) {
|
||||||
|
|
||||||
|
for (var x = 0; x < byteWidth; x++) {
|
||||||
|
|
||||||
|
var up = pxPos > 0 ? pxData[pxPos + x - byteWidth] : 0;
|
||||||
|
var val = pxData[pxPos + x] - up;
|
||||||
|
|
||||||
|
rawData[rawPos + x] = val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function filterSumUp(pxData, pxPos, byteWidth) {
|
||||||
|
|
||||||
|
var sum = 0;
|
||||||
|
var length = pxPos + byteWidth;
|
||||||
|
for (var x = pxPos; x < length; x++) {
|
||||||
|
|
||||||
|
var up = pxPos > 0 ? pxData[x - byteWidth] : 0;
|
||||||
|
var val = pxData[x] - up;
|
||||||
|
|
||||||
|
sum += Math.abs(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
function filterAvg(pxData, pxPos, byteWidth, rawData, rawPos) {
|
||||||
|
|
||||||
|
for (var x = 0; x < byteWidth; x++) {
|
||||||
|
|
||||||
|
var left = x >= 4 ? pxData[pxPos + x - 4] : 0;
|
||||||
|
var up = pxPos > 0 ? pxData[pxPos + x - byteWidth] : 0;
|
||||||
|
var val = pxData[pxPos + x] - ((left + up) >> 1);
|
||||||
|
|
||||||
|
rawData[rawPos + x] = val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function filterSumAvg(pxData, pxPos, byteWidth) {
|
||||||
|
|
||||||
|
var sum = 0;
|
||||||
|
for (var x = 0; x < byteWidth; x++) {
|
||||||
|
|
||||||
|
var left = x >= 4 ? pxData[pxPos + x - 4] : 0;
|
||||||
|
var up = pxPos > 0 ? pxData[pxPos + x - byteWidth] : 0;
|
||||||
|
var val = pxData[pxPos + x] - ((left + up) >> 1);
|
||||||
|
|
||||||
|
sum += Math.abs(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
function filterPaeth(pxData, pxPos, byteWidth, rawData, rawPos) {
|
||||||
|
|
||||||
|
for (var x = 0; x < byteWidth; x++) {
|
||||||
|
|
||||||
|
var left = x >= 4 ? pxData[pxPos + x - 4] : 0;
|
||||||
|
var up = pxPos > 0 ? pxData[pxPos + x - byteWidth] : 0;
|
||||||
|
var upleft = pxPos > 0 && x >= 4 ? pxData[pxPos + x - (byteWidth + 4)] : 0;
|
||||||
|
var val = pxData[pxPos + x] - paethPredictor(left, up, upleft);
|
||||||
|
|
||||||
|
rawData[rawPos + x] = val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function filterSumPaeth(pxData, pxPos, byteWidth) {
|
||||||
|
|
||||||
|
var sum = 0;
|
||||||
|
for (var x = 0; x < byteWidth; x++) {
|
||||||
|
|
||||||
|
var left = x >= 4 ? pxData[pxPos + x - 4] : 0;
|
||||||
|
var up = pxPos > 0 ? pxData[pxPos + x - byteWidth] : 0;
|
||||||
|
var upleft = pxPos > 0 && x >= 4 ? pxData[pxPos + x - (byteWidth + 4)] : 0;
|
||||||
|
var val = pxData[pxPos + x] - paethPredictor(left, up, upleft);
|
||||||
|
|
||||||
|
sum += Math.abs(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
var filters = {
|
||||||
|
0: filterNone,
|
||||||
|
1: filterSub,
|
||||||
|
2: filterUp,
|
||||||
|
3: filterAvg,
|
||||||
|
4: filterPaeth
|
||||||
|
};
|
||||||
|
|
||||||
|
var filterSums = {
|
||||||
|
0: filterSumNone,
|
||||||
|
1: filterSumSub,
|
||||||
|
2: filterSumUp,
|
||||||
|
3: filterSumAvg,
|
||||||
|
4: filterSumPaeth
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = function(pxData, width, height, options) {
|
||||||
|
|
||||||
|
var filterTypes;
|
||||||
|
if (!('filterType' in options) || options.filterType === -1) {
|
||||||
|
filterTypes = [0, 1, 2, 3, 4];
|
||||||
|
}
|
||||||
|
else if (typeof options.filterType === 'number') {
|
||||||
|
filterTypes = [options.filterType];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw new Error('unrecognised filter types');
|
||||||
|
}
|
||||||
|
|
||||||
|
var byteWidth = width << 2;
|
||||||
|
var rawPos = 0;
|
||||||
|
var pxPos = 0;
|
||||||
|
var rawData = new Buffer((byteWidth + 1) * height);
|
||||||
|
var sel = filterTypes[0];
|
||||||
|
|
||||||
|
for (var y = 0; y < height; y++) {
|
||||||
|
|
||||||
|
if (filterTypes.length > 1) {
|
||||||
|
// find best filter for this line (with lowest sum of values)
|
||||||
|
var min = Infinity;
|
||||||
|
|
||||||
|
for (var i = 0; i < filterTypes.length; i++) {
|
||||||
|
var sum = filterSums[filterTypes[i]](pxData, pxPos, byteWidth);
|
||||||
|
|
||||||
|
if (sum < min) {
|
||||||
|
sel = filterTypes[i];
|
||||||
|
min = sum;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rawData[rawPos] = sel;
|
||||||
|
rawPos++;
|
||||||
|
filters[sel](pxData, pxPos, byteWidth, rawData, rawPos);
|
||||||
|
rawPos += byteWidth;
|
||||||
|
pxPos += byteWidth;
|
||||||
|
}
|
||||||
|
return rawData;
|
||||||
|
};
|
||||||
|
|
@ -2,15 +2,15 @@
|
||||||
|
|
||||||
var util = require('util');
|
var util = require('util');
|
||||||
var ChunkStream = require('./chunkstream');
|
var ChunkStream = require('./chunkstream');
|
||||||
var Filter = require('./filter');
|
var Filter = require('./filter-parse');
|
||||||
|
|
||||||
|
|
||||||
var FilterAsync = module.exports = function(width, height, Bpp, depth, interlace, options) {
|
var FilterAsync = module.exports = function(width, height, Bpp, depth, interlace) {
|
||||||
ChunkStream.call(this);
|
ChunkStream.call(this);
|
||||||
|
|
||||||
var buffers = [];
|
var buffers = [];
|
||||||
var that = this;
|
var that = this;
|
||||||
this._filter = new Filter(width, height, Bpp, depth, interlace, options, {
|
this._filter = new Filter(width, height, Bpp, depth, interlace, {
|
||||||
read: this.read.bind(this),
|
read: this.read.bind(this),
|
||||||
complete: function() {
|
complete: function() {
|
||||||
that.emit('complete', Buffer.concat(buffers), width, height);
|
that.emit('complete', Buffer.concat(buffers), width, height);
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var SyncReader = require('./sync-reader');
|
var SyncReader = require('./sync-reader');
|
||||||
var Filter = require('./filter');
|
var Filter = require('./filter-parse');
|
||||||
|
|
||||||
|
|
||||||
exports.process = function(inBuffer, width, height, Bpp, depth, interlace, options) {
|
exports.process = function(inBuffer, width, height, Bpp, depth, interlace) {
|
||||||
|
|
||||||
var outBuffers = [];
|
var outBuffers = [];
|
||||||
var reader = new SyncReader(inBuffer);
|
var reader = new SyncReader(inBuffer);
|
||||||
var filter = new Filter(width, height, Bpp, depth, interlace, options, {
|
var filter = new Filter(width, height, Bpp, depth, interlace, {
|
||||||
read: reader.read.bind(reader),
|
read: reader.read.bind(reader),
|
||||||
write: function(bufferPart) {
|
write: function(bufferPart) {
|
||||||
outBuffers.push(bufferPart);
|
outBuffers.push(bufferPart);
|
||||||
169
lib/filter-parse.js
Normal file
169
lib/filter-parse.js
Normal file
|
|
@ -0,0 +1,169 @@
|
||||||
|
'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(width, height, Bpp, depth, interlace, dependencies) {
|
||||||
|
|
||||||
|
this._width = width;
|
||||||
|
this._height = height;
|
||||||
|
this._Bpp = Bpp; //TODO rename
|
||||||
|
this._depth = 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 = this._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.complete(this._width, this._height);
|
||||||
|
}
|
||||||
|
};
|
||||||
308
lib/filter.js
308
lib/filter.js
|
|
@ -1,308 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
var interlaceUtils = require('./interlace');
|
|
||||||
|
|
||||||
function getByteWidth(width, bpp, depth) {
|
|
||||||
var byteWidth = width * bpp;
|
|
||||||
if (depth !== 8) {
|
|
||||||
byteWidth = Math.ceil(byteWidth / (8 / depth));
|
|
||||||
}
|
|
||||||
return byteWidth;
|
|
||||||
}
|
|
||||||
|
|
||||||
function paethPredictor(left, above, upLeft) {
|
|
||||||
|
|
||||||
var paeth = left + above - upLeft;
|
|
||||||
var pLeft = Math.abs(paeth - left);
|
|
||||||
var pAbove = Math.abs(paeth - above);
|
|
||||||
var pUpLeft = Math.abs(paeth - upLeft);
|
|
||||||
|
|
||||||
if (pLeft <= pAbove && pLeft <= pUpLeft) {
|
|
||||||
return left;
|
|
||||||
}
|
|
||||||
if (pAbove <= pUpLeft) {
|
|
||||||
return above;
|
|
||||||
}
|
|
||||||
return upLeft;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
var Filter = module.exports = function(width, height, Bpp, depth, interlace, options, dependencies) {
|
|
||||||
|
|
||||||
this._width = width;
|
|
||||||
this._height = height;
|
|
||||||
this._Bpp = Bpp; //TODO rename
|
|
||||||
this._depth = depth;
|
|
||||||
this._options = options;
|
|
||||||
|
|
||||||
if (!('filterType' in options) || options.filterType === -1) {
|
|
||||||
options.filterType = [0, 1, 2, 3, 4];
|
|
||||||
}
|
|
||||||
else if (typeof options.filterType === 'number') {
|
|
||||||
options.filterType = [options.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 = 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
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Filter.prototype.start = function() {
|
|
||||||
this.read(this._images[this._imageIndex].byteWidth + 1, this._reverseFilterLine.bind(this));
|
|
||||||
};
|
|
||||||
|
|
||||||
Filter.prototype._reverseFilterLine = function(rawData) {
|
|
||||||
|
|
||||||
var currentImage = this._images[this._imageIndex];
|
|
||||||
var line = new Buffer(currentImage.byteWidth);
|
|
||||||
|
|
||||||
var filter = rawData[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.
|
|
||||||
var xComparison;
|
|
||||||
if (this._depth === 8) {
|
|
||||||
xComparison = this._Bpp;
|
|
||||||
}
|
|
||||||
else if (this._depth === 16) {
|
|
||||||
xComparison = this._Bpp * 2;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
xComparison = 1;
|
|
||||||
}
|
|
||||||
var xBiggerThan = xComparison - 1;
|
|
||||||
|
|
||||||
for (var x = 0; x < currentImage.byteWidth; x++) {
|
|
||||||
var rawByte = rawData[1 + x];
|
|
||||||
switch (filter) {
|
|
||||||
case 0:
|
|
||||||
line[x] = rawByte;
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
var f1Left = x > xBiggerThan ? line[x - xComparison] : 0;
|
|
||||||
line[x] = rawByte + f1Left;
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
var f2Up = this._lastLine ? this._lastLine[x] : 0;
|
|
||||||
line[x] = rawByte + f2Up;
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
var f3Up = this._lastLine ? this._lastLine[x] : 0;
|
|
||||||
var f3Left = x > xBiggerThan ? line[x - xComparison] : 0;
|
|
||||||
var f3Add = Math.floor((f3Left + f3Up) / 2);
|
|
||||||
line[x] = rawByte + f3Add;
|
|
||||||
break;
|
|
||||||
case 4:
|
|
||||||
var f4Up = this._lastLine ? this._lastLine[x] : 0;
|
|
||||||
var f4Left = x > xBiggerThan ? line[x - xComparison] : 0;
|
|
||||||
var f4UpLeft = x > xBiggerThan && this._lastLine
|
|
||||||
? this._lastLine[x - xComparison] : 0;
|
|
||||||
var f4Add = paethPredictor(f4Left, f4Up, f4UpLeft);
|
|
||||||
line[x] = rawByte + f4Add;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new Error('Unrecognised filter type - ' + filter);
|
|
||||||
}
|
|
||||||
|
|
||||||
//if (x === 5) {
|
|
||||||
// console.log("R", line[3], "G", line[4], "B", line[5]);
|
|
||||||
//}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.write(line);
|
|
||||||
|
|
||||||
currentImage.lineIndex++;
|
|
||||||
if (currentImage.lineIndex >= currentImage.height) {
|
|
||||||
this._lastLine = null;
|
|
||||||
this._imageIndex++;
|
|
||||||
currentImage = this._images[this._imageIndex];
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
this._lastLine = line;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currentImage) {
|
|
||||||
this.read(currentImage.byteWidth + 1, this._reverseFilterLine.bind(this));
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
this.complete(this._width, this._height);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
//TODO pull out
|
|
||||||
Filter.prototype.filter = function(pxData) {
|
|
||||||
|
|
||||||
var rawData = new Buffer(((this._width << 2) + 1) * this._height);
|
|
||||||
|
|
||||||
for (var y = 0; y < this._height; y++) {
|
|
||||||
|
|
||||||
// find best filter for this line (with lowest sum of values)
|
|
||||||
var filterTypes = this._options.filterType;
|
|
||||||
var min = Infinity;
|
|
||||||
var sel = 0;
|
|
||||||
|
|
||||||
for (var i = 0; i < filterTypes.length; i++) {
|
|
||||||
var sum = this._filters[filterTypes[i]](pxData, y, null);
|
|
||||||
if (sum < min) {
|
|
||||||
sel = filterTypes[i];
|
|
||||||
min = sum;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this._filters[sel](pxData, y, rawData);
|
|
||||||
}
|
|
||||||
return rawData;
|
|
||||||
};
|
|
||||||
|
|
||||||
Filter.prototype._filterNone = function(pxData, y, rawData) {
|
|
||||||
|
|
||||||
var pxRowLength = this._width << 2;
|
|
||||||
var rawRowLength = pxRowLength + 1;
|
|
||||||
var sum = 0;
|
|
||||||
|
|
||||||
if (!rawData) {
|
|
||||||
for (var 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;
|
|
||||||
};
|
|
||||||
|
|
||||||
Filter.prototype._filterSub = function(pxData, y, rawData) {
|
|
||||||
|
|
||||||
var pxRowLength = this._width << 2;
|
|
||||||
var rawRowLength = pxRowLength + 1;
|
|
||||||
var sum = 0;
|
|
||||||
|
|
||||||
if (rawData) {
|
|
||||||
rawData[y * rawRowLength] = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var x = 0; x < pxRowLength; x++) {
|
|
||||||
|
|
||||||
var left = x >= 4 ? pxData[y * pxRowLength + x - 4] : 0;
|
|
||||||
var val = pxData[y * pxRowLength + x] - left;
|
|
||||||
|
|
||||||
if (!rawData) {
|
|
||||||
sum += Math.abs(val);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
rawData[y * rawRowLength + 1 + x] = val;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return sum;
|
|
||||||
};
|
|
||||||
|
|
||||||
Filter.prototype._filterUp = function(pxData, y, rawData) {
|
|
||||||
|
|
||||||
var pxRowLength = this._width << 2;
|
|
||||||
var rawRowLength = pxRowLength + 1;
|
|
||||||
var sum = 0;
|
|
||||||
|
|
||||||
if (rawData) {
|
|
||||||
rawData[y * rawRowLength] = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var x = 0; x < pxRowLength; x++) {
|
|
||||||
|
|
||||||
var up = y > 0 ? pxData[(y - 1) * pxRowLength + x] : 0;
|
|
||||||
var val = pxData[y * pxRowLength + x] - up;
|
|
||||||
|
|
||||||
if (!rawData) {
|
|
||||||
sum += Math.abs(val);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
rawData[y * rawRowLength + 1 + x] = val;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return sum;
|
|
||||||
};
|
|
||||||
|
|
||||||
Filter.prototype._filterAvg = function(pxData, y, rawData) {
|
|
||||||
|
|
||||||
var pxRowLength = this._width << 2;
|
|
||||||
var rawRowLength = pxRowLength + 1;
|
|
||||||
var sum = 0;
|
|
||||||
|
|
||||||
if (rawData) {
|
|
||||||
rawData[y * rawRowLength] = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var x = 0; x < pxRowLength; x++) {
|
|
||||||
|
|
||||||
var left = x >= 4 ? pxData[y * pxRowLength + x - 4] : 0;
|
|
||||||
var up = y > 0 ? pxData[(y - 1) * pxRowLength + x] : 0;
|
|
||||||
var val = pxData[y * pxRowLength + x] - ((left + up) >> 1);
|
|
||||||
|
|
||||||
if (!rawData) {
|
|
||||||
sum += Math.abs(val);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
rawData[y * rawRowLength + 1 + x] = val;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return sum;
|
|
||||||
};
|
|
||||||
|
|
||||||
Filter.prototype._filterPaeth = function(pxData, y, rawData) {
|
|
||||||
|
|
||||||
var pxRowLength = this._width << 2;
|
|
||||||
var rawRowLength = pxRowLength + 1;
|
|
||||||
var sum = 0;
|
|
||||||
|
|
||||||
if (rawData) {
|
|
||||||
rawData[y * rawRowLength] = 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var x = 0; x < pxRowLength; x++) {
|
|
||||||
|
|
||||||
var left = x >= 4 ? pxData[y * pxRowLength + x - 4] : 0;
|
|
||||||
var up = y > 0 ? pxData[(y - 1) * pxRowLength + x] : 0;
|
|
||||||
var upLeft = x >= 4 && y > 0 ? pxData[(y - 1) * pxRowLength + x - 4] : 0;
|
|
||||||
var val = pxData[y * pxRowLength + x] - paethPredictor(left, up, upLeft);
|
|
||||||
|
|
||||||
if (!rawData) {
|
|
||||||
sum += Math.abs(val);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
rawData[y * rawRowLength + 1 + x] = val;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return sum;
|
|
||||||
};
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
var util = require('util');
|
var util = require('util');
|
||||||
var Stream = require('stream');
|
var Stream = require('stream');
|
||||||
var zlib = require('zlib');
|
var zlib = require('zlib');
|
||||||
var Filter = require('./filter');
|
var filter = require('./filter-pack');
|
||||||
var CrcStream = require('./crc');
|
var CrcStream = require('./crc');
|
||||||
var constants = require('./constants');
|
var constants = require('./constants');
|
||||||
|
|
||||||
|
|
@ -34,9 +34,7 @@ Packer.prototype.pack = function(data, width, height, gamma) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// filter pixel data
|
// filter pixel data
|
||||||
//TODO {}
|
var filteredData = filter(data, width, height, this._options);
|
||||||
var filter = new Filter(width, height, 4, 8, false, this._options, {});
|
|
||||||
var filteredData = filter.filter(data);
|
|
||||||
|
|
||||||
// compress it
|
// compress it
|
||||||
var deflate = zlib.createDeflate({
|
var deflate = zlib.createDeflate({
|
||||||
|
|
|
||||||
17
lib/paeth-predictor.js
Normal file
17
lib/paeth-predictor.js
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
module.exports = function paethPredictor(left, above, upLeft) {
|
||||||
|
|
||||||
|
var paeth = left + above - upLeft;
|
||||||
|
var pLeft = Math.abs(paeth - left);
|
||||||
|
var pAbove = Math.abs(paeth - above);
|
||||||
|
var pUpLeft = Math.abs(paeth - upLeft);
|
||||||
|
|
||||||
|
if (pLeft <= pAbove && pLeft <= pUpLeft) {
|
||||||
|
return left;
|
||||||
|
}
|
||||||
|
if (pAbove <= pUpLeft) {
|
||||||
|
return above;
|
||||||
|
}
|
||||||
|
return upLeft;
|
||||||
|
};
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
var util = require('util');
|
var util = require('util');
|
||||||
var zlib = require('zlib');
|
var zlib = require('zlib');
|
||||||
var ChunkStream = require('./chunkstream');
|
var ChunkStream = require('./chunkstream');
|
||||||
var FilterAsync = require('./filter-async');
|
var FilterAsync = require('./filter-parse-async');
|
||||||
var Parser = require('./parser');
|
var Parser = require('./parser');
|
||||||
var bitmapper = require('./bitmapper');
|
var bitmapper = require('./bitmapper');
|
||||||
var formatNormaliser = require('./format-normaliser');
|
var formatNormaliser = require('./format-normaliser');
|
||||||
|
|
@ -65,8 +65,7 @@ ParserAsync.prototype._handleBitmapInfo = function(width, height, bpp, depth, in
|
||||||
width, height,
|
width, height,
|
||||||
bpp,
|
bpp,
|
||||||
depth,
|
depth,
|
||||||
interlace,
|
interlace
|
||||||
this._options
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
var zlib = require('zlib');
|
var zlib = require('zlib');
|
||||||
var SyncReader = require('./sync-reader');
|
var SyncReader = require('./sync-reader');
|
||||||
var FilterSync = require('./filter-sync');
|
var FilterSync = require('./filter-parse-sync');
|
||||||
var Parser = require('./parser');
|
var Parser = require('./parser');
|
||||||
var bitmapper = require('./bitmapper');
|
var bitmapper = require('./bitmapper');
|
||||||
var formatNormaliser = require('./format-normaliser');
|
var formatNormaliser = require('./format-normaliser');
|
||||||
|
|
@ -16,7 +16,7 @@ module.exports = function(buffer, options) {
|
||||||
err = _err_;
|
err = _err_;
|
||||||
}
|
}
|
||||||
|
|
||||||
var data, bpp, width, height, depth, interlace;
|
var bpp, width, height, depth, interlace;
|
||||||
function handleBitmapInfo(_width_, _height_, _bpp_, _depth_, _interlace_) {
|
function handleBitmapInfo(_width_, _height_, _bpp_, _depth_, _interlace_) {
|
||||||
|
|
||||||
bpp = _bpp_;
|
bpp = _bpp_;
|
||||||
|
|
@ -24,8 +24,6 @@ module.exports = function(buffer, options) {
|
||||||
height = _height_;
|
height = _height_;
|
||||||
depth = _depth_;
|
depth = _depth_;
|
||||||
interlace = _interlace_;
|
interlace = _interlace_;
|
||||||
|
|
||||||
data = new Buffer(width * height * 4);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var metaData;
|
var metaData;
|
||||||
|
|
@ -78,8 +76,7 @@ module.exports = function(buffer, options) {
|
||||||
height,
|
height,
|
||||||
bpp,
|
bpp,
|
||||||
depth,
|
depth,
|
||||||
interlace,
|
interlace
|
||||||
options
|
|
||||||
);
|
);
|
||||||
inflateData = null;
|
inflateData = null;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ var PNG = exports.PNG = function(options) {
|
||||||
this.gamma = 0;
|
this.gamma = 0;
|
||||||
this.readable = this.writable = true;
|
this.readable = this.writable = true;
|
||||||
|
|
||||||
this._parser = new Parser(options || {});
|
this._parser = new Parser(options);
|
||||||
|
|
||||||
this._parser.on('error', this.emit.bind(this, 'error'));
|
this._parser.on('error', this.emit.bind(this, 'error'));
|
||||||
this._parser.on('close', this._handleClose.bind(this));
|
this._parser.on('close', this._handleClose.bind(this));
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,12 @@ test('outputs background, created from scratch', function (t) {
|
||||||
var out = fs.readFileSync(__dirname + '/bg.png');
|
var out = fs.readFileSync(__dirname + '/bg.png');
|
||||||
var ref = fs.readFileSync(__dirname + '/bg-ref.png');
|
var ref = fs.readFileSync(__dirname + '/bg-ref.png');
|
||||||
|
|
||||||
t.ok(bufferEqual(out, ref), "compares with working file ok");
|
var isBufferEqual = bufferEqual(out, ref);
|
||||||
|
t.ok(isBufferEqual, "compares with working file ok");
|
||||||
|
|
||||||
|
if (!isBufferEqual) {
|
||||||
|
console.log(out.length, ref.length);
|
||||||
|
}
|
||||||
|
|
||||||
t.end();
|
t.end();
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue