mirror of
https://github.com/danbulant/pngjs
synced 2026-07-05 11:10:35 +00:00
Interlace support
This commit is contained in:
parent
2d91824d0c
commit
f7c70a92b4
10 changed files with 203 additions and 53 deletions
|
|
@ -1,8 +1,13 @@
|
||||||
|
var interlaceUtils = require('./interlace');
|
||||||
|
|
||||||
function bitRetriever(data, depth) {
|
function bitRetriever(data, depth) {
|
||||||
|
|
||||||
var leftOver = [];
|
var leftOver = [];
|
||||||
var i = 0;
|
var i = 0;
|
||||||
function split() {
|
function split() {
|
||||||
|
if (i === data.length) {
|
||||||
|
throw new Error("Ran out of data");
|
||||||
|
}
|
||||||
var byte = data[i];
|
var byte = data[i];
|
||||||
i++;
|
i++;
|
||||||
switch(depth) {
|
switch(depth) {
|
||||||
|
|
@ -60,36 +65,67 @@ function bitRetriever(data, depth) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.dataToBitMap = function(data, width, height, bpp, depth) {
|
exports.dataToBitMap = function(data, width, height, bpp, depth, interlace) {
|
||||||
|
|
||||||
if (depth !== 8) {
|
if (depth !== 8) {
|
||||||
var bits = bitRetriever(data, depth);
|
var bits = bitRetriever(data, depth);
|
||||||
}
|
}
|
||||||
var pxData = new Buffer(width * height * 4);
|
var pxData = new Buffer(width * height * 4);
|
||||||
var pxPos = 0;
|
|
||||||
var maxBit = depth >= 8 ? 255 : (Math.pow(2, depth) - 1);
|
var maxBit = depth >= 8 ? 255 : (Math.pow(2, depth) - 1);
|
||||||
var rawPos = 0;
|
var rawPos = 0;
|
||||||
var pixelData;
|
var pixelData;
|
||||||
|
var images;
|
||||||
|
var getPxPos;
|
||||||
|
|
||||||
for(var y = 0; y < height; y++) {
|
if (interlace) {
|
||||||
for(var x = 0; x < width; x++) {
|
images = interlaceUtils.getImagePasses(width, height);
|
||||||
if (depth !== 8) {
|
getPxPos = interlaceUtils.getInterlaceIterator(width, height);
|
||||||
pixelData = bits.get(bpp);
|
} else {
|
||||||
}
|
var nonInterlacedPxPos = 0;
|
||||||
for (var i = 0; i < 4; i++) {
|
getPxPos = function() {
|
||||||
var idx = pixelBppMap[bpp][i];
|
var returner = nonInterlacedPxPos;
|
||||||
if (depth === 8) {
|
nonInterlacedPxPos += 4;
|
||||||
pxData[pxPos] = idx !== 0xff ? data[idx + rawPos] : maxBit;
|
return returner;
|
||||||
} else {
|
};
|
||||||
pxData[pxPos] = idx !== 0xff ? pixelData[idx] : maxBit;
|
images = [{width: width, height: height}];
|
||||||
}
|
}
|
||||||
pxPos++;
|
|
||||||
}
|
for (var y = 0; y < height; y++) {
|
||||||
//console.log("R", pxData[pxPos - 4], "G", pxData[pxPos - 3], "B", pxData[pxPos - 2], "A", pxData[pxPos - 1]);
|
for (var x = 0; x < width; x++) {
|
||||||
rawPos += bpp;
|
pxData[(y * 4 * width) + (x * 4) + 0] = 255;
|
||||||
|
pxData[(y * 4 * width) + (x * 4) + 1] = 0;
|
||||||
|
pxData[(y * 4 * width) + (x * 4) + 2] = 0;
|
||||||
|
pxData[(y * 4 * width) + (x * 4) + 3] = 255;
|
||||||
}
|
}
|
||||||
if (depth !== 8) {
|
}
|
||||||
bits.resetAfterLine();
|
|
||||||
|
for(var imageIndex = 0; imageIndex < images.length; imageIndex++) {
|
||||||
|
var imageWidth = images[imageIndex].width;
|
||||||
|
var imageHeight = images[imageIndex].height;
|
||||||
|
for (var y = 0; y < imageHeight; y++) {
|
||||||
|
for (var x = 0; x < imageWidth; x++) {
|
||||||
|
if (depth !== 8) {
|
||||||
|
pixelData = bits.get(bpp);
|
||||||
|
}
|
||||||
|
var pxPos = getPxPos(x, y, imageIndex);
|
||||||
|
//console.log(x,y,imageIndex, pxPos);
|
||||||
|
for (var i = 0; i < 4; i++) {
|
||||||
|
var idx = pixelBppMap[bpp][i];
|
||||||
|
if (depth === 8) {
|
||||||
|
if (i === data.length) {
|
||||||
|
throw new Error("Ran out of data");
|
||||||
|
}
|
||||||
|
pxData[pxPos + i] = idx !== 0xff ? data[idx + rawPos] : maxBit;
|
||||||
|
} else {
|
||||||
|
pxData[pxPos + i] = idx !== 0xff ? pixelData[idx] : maxBit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//console.log("R", pxData[pxPos], "G", pxData[pxPos + 1], "B", pxData[pxPos + 2], "A", pxData[pxPos + 3]);
|
||||||
|
rawPos += bpp;
|
||||||
|
}
|
||||||
|
if (depth !== 8) {
|
||||||
|
bits.resetAfterLine();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (depth === 8) {
|
if (depth === 8) {
|
||||||
|
|
|
||||||
|
|
@ -25,12 +25,12 @@ var util = require('util'),
|
||||||
Filter = require('./filter');
|
Filter = require('./filter');
|
||||||
|
|
||||||
|
|
||||||
var FilterAsync = module.exports = function(width, height, Bpp, depth, options) {
|
var FilterAsync = module.exports = function(width, height, Bpp, depth, interlace, options) {
|
||||||
ChunkStream.call(this);
|
ChunkStream.call(this);
|
||||||
|
|
||||||
var buffers = [];
|
var buffers = [];
|
||||||
var that = this;
|
var that = this;
|
||||||
this._filter = new Filter(width, height, Bpp, depth, options, {
|
this._filter = new Filter(width, height, Bpp, depth, interlace, options, {
|
||||||
read: this.read.bind(this),
|
read: this.read.bind(this),
|
||||||
complete: function(width, height) {
|
complete: function(width, height) {
|
||||||
that.emit('complete', Buffer.concat(buffers), width, height)
|
that.emit('complete', Buffer.concat(buffers), width, height)
|
||||||
|
|
|
||||||
|
|
@ -24,11 +24,11 @@ var SyncReader = require('./sync-reader'),
|
||||||
Filter = require('./filter');
|
Filter = require('./filter');
|
||||||
|
|
||||||
|
|
||||||
exports.process = function(buffer, width, height, Bpp, depth, options) {
|
exports.process = function(buffer, width, height, Bpp, depth, interlace, options) {
|
||||||
|
|
||||||
var buffers = [];
|
var buffers = [];
|
||||||
var reader = new SyncReader(buffer);
|
var reader = new SyncReader(buffer);
|
||||||
var filter = new Filter(width, height, Bpp, depth, options, {
|
var filter = new Filter(width, height, Bpp, depth, interlace, options, {
|
||||||
read: reader.read.bind(reader),
|
read: reader.read.bind(reader),
|
||||||
write: function(buffer) {
|
write: function(buffer) {
|
||||||
buffers.push(buffer);
|
buffers.push(buffer);
|
||||||
|
|
|
||||||
|
|
@ -20,10 +20,17 @@
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var util = require('util');
|
var interlaceUtils = require('./interlace');
|
||||||
|
|
||||||
|
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, options, dependencies) {
|
var Filter = module.exports = function(width, height, Bpp, depth, interlace, options, dependencies) {
|
||||||
|
|
||||||
this._width = width;
|
this._width = width;
|
||||||
this._height = height;
|
this._height = height;
|
||||||
|
|
@ -31,8 +38,6 @@ var Filter = module.exports = function(width, height, Bpp, depth, options, depen
|
||||||
this._depth = depth;
|
this._depth = depth;
|
||||||
this._options = options;
|
this._options = options;
|
||||||
|
|
||||||
this._line = 0;
|
|
||||||
|
|
||||||
if (!('filterType' in options) || options.filterType == -1) {
|
if (!('filterType' in options) || options.filterType == -1) {
|
||||||
options.filterType = [0, 1, 2, 3, 4];
|
options.filterType = [0, 1, 2, 3, 4];
|
||||||
} else if (typeof options.filterType == 'number') {
|
} else if (typeof options.filterType == 'number') {
|
||||||
|
|
@ -51,27 +56,41 @@ var Filter = module.exports = function(width, height, Bpp, depth, options, depen
|
||||||
this.write = dependencies.write;
|
this.write = dependencies.write;
|
||||||
this.complete = dependencies.complete;
|
this.complete = dependencies.complete;
|
||||||
|
|
||||||
var byteWidth = this._width * this._Bpp;
|
this._imageIndex = 0;
|
||||||
if (this._depth !== 8) {
|
this._images = [];
|
||||||
byteWidth = Math.ceil(byteWidth / (8 / this._depth));
|
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
|
||||||
|
});
|
||||||
}
|
}
|
||||||
this._byteWidth = byteWidth;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Filter.prototype.start = function() {
|
Filter.prototype.start = function() {
|
||||||
this.read(this._byteWidth + 1, this._reverseFilterLine.bind(this));
|
this.read(this._images[this._imageIndex].byteWidth + 1, this._reverseFilterLine.bind(this));
|
||||||
};
|
};
|
||||||
|
|
||||||
Filter.prototype._reverseFilterLine = function(rawData) {
|
Filter.prototype._reverseFilterLine = function(rawData) {
|
||||||
|
|
||||||
var line = new Buffer(this._byteWidth);
|
var currentImage = this._images[this._imageIndex];
|
||||||
|
var line = new Buffer(currentImage.byteWidth);
|
||||||
|
|
||||||
var filter = rawData[0];
|
var filter = rawData[0];
|
||||||
|
|
||||||
var xComparison = this._depth >= 8 ? ((this._depth === 16) ? this._Bpp * 2 : this._Bpp) : 1;
|
var xComparison = this._depth >= 8 ? ((this._depth === 16) ? this._Bpp * 2 : this._Bpp) : 1;
|
||||||
var xBiggerThan = xComparison - 1;
|
var xBiggerThan = xComparison - 1;
|
||||||
|
|
||||||
for (var x = 0; x < this._byteWidth; x++) {
|
for (var x = 0; x < currentImage.byteWidth; x++) {
|
||||||
var rawByte = rawData[1 + x];
|
var rawByte = rawData[1 + x];
|
||||||
switch(filter) {
|
switch(filter) {
|
||||||
case 0:
|
case 0:
|
||||||
|
|
@ -106,14 +125,20 @@ Filter.prototype._reverseFilterLine = function(rawData) {
|
||||||
//}
|
//}
|
||||||
}
|
}
|
||||||
|
|
||||||
this._line++;
|
|
||||||
this._lastLine = line;
|
|
||||||
this.write(line);
|
this.write(line);
|
||||||
|
|
||||||
if (this._line < this._height) {
|
currentImage.lineIndex++;
|
||||||
this.read(this._byteWidth + 1, this._reverseFilterLine.bind(this));
|
if (currentImage.lineIndex >= currentImage.height) {
|
||||||
} else {
|
|
||||||
this._lastLine = null;
|
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);
|
this.complete(this._width, this._height);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
85
lib/interlace.js
Normal file
85
lib/interlace.js
Normal file
|
|
@ -0,0 +1,85 @@
|
||||||
|
exports.getImagePasses = function(width, height) {
|
||||||
|
var images = [];
|
||||||
|
var xLeftOver = width % 8;
|
||||||
|
var yLeftOver = height % 8;
|
||||||
|
var xRepeats = (width - xLeftOver) / 8;
|
||||||
|
var yRepeats = (width - yLeftOver) / 8;
|
||||||
|
for(var i = 0; i < imagePasses.length; i++) {
|
||||||
|
var pass = imagePasses[i];
|
||||||
|
var passWidth = xRepeats * pass.x.length;
|
||||||
|
var passHeight = yRepeats * pass.y.length;
|
||||||
|
for(var j = 0; j < pass.x.length; j++) {
|
||||||
|
if (pass.x[j] < xLeftOver) {
|
||||||
|
passWidth++;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for(j = 0; j < pass.y.length; j++) {
|
||||||
|
if (pass.y[j] < yLeftOver) {
|
||||||
|
passHeight++;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (passWidth > 0 && passHeight > 0) {
|
||||||
|
images.push({ width: passWidth, height: passHeight});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return images;
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.getInterlaceIterator = function(width, height) {
|
||||||
|
return function(x, y, pass) {
|
||||||
|
var outerXLeftOver = x % imagePasses[pass].x.length;
|
||||||
|
var outerX = (((x - outerXLeftOver)/ imagePasses[pass].x.length) * 8) + imagePasses[pass].x[outerXLeftOver];
|
||||||
|
var outerYLeftOver = y % imagePasses[pass].y.length;
|
||||||
|
var outerY = (((y - outerYLeftOver) / imagePasses[pass].y.length) * 8) + imagePasses[pass].y[outerYLeftOver];
|
||||||
|
return (outerX * 4) + (outerY * width * 4);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Adam 7
|
||||||
|
// 0 1 2 3 4 5 6 7
|
||||||
|
// 0 x 6 4 6 x 6 4 6
|
||||||
|
// 1 7 7 7 7 7 7 7 7
|
||||||
|
// 2 5 6 5 6 5 6 5 6
|
||||||
|
// 3 7 7 7 7 7 7 7 7
|
||||||
|
// 4 3 6 4 6 3 6 4 6
|
||||||
|
// 5 7 7 7 7 7 7 7 7
|
||||||
|
// 6 5 6 5 6 5 6 5 6
|
||||||
|
// 7 7 7 7 7 7 7 7 7
|
||||||
|
|
||||||
|
|
||||||
|
var imagePasses = [
|
||||||
|
{ // pass 1 - 1px
|
||||||
|
x: [0],
|
||||||
|
y: [0]
|
||||||
|
},
|
||||||
|
{ // pass 2 - 1px
|
||||||
|
x: [4],
|
||||||
|
y: [0]
|
||||||
|
},
|
||||||
|
{ // pass 3 - 2px
|
||||||
|
x: [0, 4],
|
||||||
|
y: [4]
|
||||||
|
},
|
||||||
|
{ // pass 4 - 4px
|
||||||
|
x: [2, 6],
|
||||||
|
y: [0, 4]
|
||||||
|
},
|
||||||
|
{ // pass 5 - 8px
|
||||||
|
x: [0, 2, 4, 6],
|
||||||
|
y: [2, 6]
|
||||||
|
},
|
||||||
|
{ // pass 6 - 16px
|
||||||
|
x: [1, 3, 5, 7],
|
||||||
|
y: [0, 2, 4, 6]
|
||||||
|
},
|
||||||
|
{ // pass 7 - 32px
|
||||||
|
x: [0, 1, 2, 3, 4, 5, 6, 7],
|
||||||
|
y: [1, 3, 5, 7]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
@ -51,7 +51,7 @@ Packer.prototype.pack = function(data, width, height) {
|
||||||
|
|
||||||
// filter pixel data
|
// filter pixel data
|
||||||
//TODO {}
|
//TODO {}
|
||||||
var filter = new Filter(width, height, 4, 8, this._options, {});
|
var filter = new Filter(width, height, 4, 8, false, this._options, {});
|
||||||
var data = filter.filter(data);
|
var data = filter.filter(data);
|
||||||
|
|
||||||
// compress it
|
// compress it
|
||||||
|
|
|
||||||
|
|
@ -73,15 +73,17 @@ ParserAsync.prototype._inflateData = function(data) {
|
||||||
this._inflate.write(data);
|
this._inflate.write(data);
|
||||||
};
|
};
|
||||||
|
|
||||||
ParserAsync.prototype._createData = function(width, height, bpp, depth) {
|
ParserAsync.prototype._createData = function(width, height, bpp, depth, interlace) {
|
||||||
|
|
||||||
this._bpp = bpp;
|
this._bpp = bpp;
|
||||||
this._depth = depth;
|
this._depth = depth;
|
||||||
|
this._interlace = interlace;
|
||||||
|
|
||||||
this._filter = new FilterAsync(
|
this._filter = new FilterAsync(
|
||||||
width, height,
|
width, height,
|
||||||
bpp,
|
bpp,
|
||||||
depth,
|
depth,
|
||||||
|
interlace,
|
||||||
this._options
|
this._options
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
@ -100,7 +102,8 @@ ParserAsync.prototype._complete = function(data, width, height) {
|
||||||
|
|
||||||
data = bitmapper.dataToBitMap(data, width, height,
|
data = bitmapper.dataToBitMap(data, width, height,
|
||||||
this._bpp,
|
this._bpp,
|
||||||
this._depth);
|
this._depth,
|
||||||
|
this._interlace);
|
||||||
|
|
||||||
data = this._parser.reverseFiltered(data, this._depth, width, height);
|
data = this._parser.reverseFiltered(data, this._depth, width, height);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -57,12 +57,14 @@ var ParserSync = module.exports = function(buffer, options) {
|
||||||
this._width, this._height,
|
this._width, this._height,
|
||||||
this._bpp,
|
this._bpp,
|
||||||
this._depth,
|
this._depth,
|
||||||
|
this._interlace,
|
||||||
this._options
|
this._options
|
||||||
);
|
);
|
||||||
|
|
||||||
this._data = bitmapper.dataToBitMap(data, this._width, this._height,
|
this._data = bitmapper.dataToBitMap(data, this._width, this._height,
|
||||||
this._bpp,
|
this._bpp,
|
||||||
this._depth);
|
this._depth,
|
||||||
|
this._interlace);
|
||||||
// todo yuck
|
// todo yuck
|
||||||
this.data = this._parser.reverseFiltered(this._data, this._depth, this._width, this._height);
|
this.data = this._parser.reverseFiltered(this._data, this._depth, this._width, this._height);
|
||||||
};
|
};
|
||||||
|
|
@ -83,11 +85,12 @@ ParserSync.prototype._inflateData = function(data) {
|
||||||
this._inflateDataList.push(data);
|
this._inflateDataList.push(data);
|
||||||
};
|
};
|
||||||
|
|
||||||
ParserSync.prototype._createData = function(width, height, bpp, depth) {
|
ParserSync.prototype._createData = function(width, height, bpp, depth, interlace) {
|
||||||
this._data = new Buffer(width * height * 4);
|
this._data = new Buffer(width * height * 4);
|
||||||
this._bpp = bpp;
|
this._bpp = bpp;
|
||||||
this._width = width;
|
this._width = width;
|
||||||
this._height = height;
|
this._height = height;
|
||||||
this._depth = depth;
|
this._depth = depth;
|
||||||
|
this._interlace = interlace;
|
||||||
return this._data;
|
return this._data;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -180,14 +180,14 @@ Parser.prototype._parseIHDR = function(data) {
|
||||||
this.error(new Error('Unsupported filter method'));
|
this.error(new Error('Unsupported filter method'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (interlace != 0) {
|
if (interlace != 0 && interlace !== 1) {
|
||||||
this.error(new Error('Unsupported interlace method'));
|
this.error(new Error('Unsupported interlace method'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this._colorType = colorType;
|
this._colorType = colorType;
|
||||||
|
|
||||||
this.createData(width, height, colorTypeToBppMap[this._colorType], depth);
|
this.createData(width, height, colorTypeToBppMap[this._colorType], depth, interlace);
|
||||||
|
|
||||||
this._hasIHDR = true;
|
this._hasIHDR = true;
|
||||||
|
|
||||||
|
|
|
||||||
10
test/test.js
10
test/test.js
|
|
@ -7,22 +7,20 @@ fs.readdir(__dirname + '/in/', function(err, files) {
|
||||||
|
|
||||||
files.forEach(function(file) {
|
files.forEach(function(file) {
|
||||||
|
|
||||||
if (!file.match(/\.png$/i))
|
if (!file.match(/.*\.png$/i))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var expectedError = false;
|
var expectedError = false;
|
||||||
if (file.match(/^x/) ||
|
if (file.match(/^x/)) {
|
||||||
file.match(/^...i/) // interlace
|
|
||||||
) {
|
|
||||||
expectedError = true;
|
expectedError = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
var data = fs.readFileSync(__dirname + '/in/' + file);
|
var data = fs.readFileSync(__dirname + '/in/' + file);
|
||||||
try {
|
try {
|
||||||
console.log("Sync: parsing..", file);
|
|
||||||
var png = PNG.sync.read(data);
|
var png = PNG.sync.read(data);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (!expectedError) {
|
if (!expectedError) {
|
||||||
|
console.log("Unexpected error parsing.." + file);
|
||||||
console.log(e);
|
console.log(e);
|
||||||
console.log(e.stack);
|
console.log(e.stack);
|
||||||
}
|
}
|
||||||
|
|
@ -30,7 +28,7 @@ fs.readdir(__dirname + '/in/', function(err, files) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (expectedError) {
|
if (expectedError) {
|
||||||
console.log("Error expected, parsed fine", file);
|
console.log("Error expected, parsed fine ..", file);
|
||||||
}
|
}
|
||||||
|
|
||||||
var outpng = new PNG();
|
var outpng = new PNG();
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue