mirror of
https://github.com/danbulant/pngjs
synced 2026-06-18 14:01:08 +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) {
|
||||
|
||||
var leftOver = [];
|
||||
var i = 0;
|
||||
function split() {
|
||||
if (i === data.length) {
|
||||
throw new Error("Ran out of data");
|
||||
}
|
||||
var byte = data[i];
|
||||
i++;
|
||||
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) {
|
||||
var bits = bitRetriever(data, depth);
|
||||
}
|
||||
var pxData = new Buffer(width * height * 4);
|
||||
var pxPos = 0;
|
||||
var maxBit = depth >= 8 ? 255 : (Math.pow(2, depth) - 1);
|
||||
var rawPos = 0;
|
||||
var pixelData;
|
||||
var images;
|
||||
var getPxPos;
|
||||
|
||||
for(var y = 0; y < height; y++) {
|
||||
for(var x = 0; x < width; x++) {
|
||||
if (depth !== 8) {
|
||||
pixelData = bits.get(bpp);
|
||||
}
|
||||
for (var i = 0; i < 4; i++) {
|
||||
var idx = pixelBppMap[bpp][i];
|
||||
if (depth === 8) {
|
||||
pxData[pxPos] = idx !== 0xff ? data[idx + rawPos] : maxBit;
|
||||
} else {
|
||||
pxData[pxPos] = idx !== 0xff ? pixelData[idx] : maxBit;
|
||||
}
|
||||
pxPos++;
|
||||
}
|
||||
//console.log("R", pxData[pxPos - 4], "G", pxData[pxPos - 3], "B", pxData[pxPos - 2], "A", pxData[pxPos - 1]);
|
||||
rawPos += bpp;
|
||||
if (interlace) {
|
||||
images = interlaceUtils.getImagePasses(width, height);
|
||||
getPxPos = interlaceUtils.getInterlaceIterator(width, height);
|
||||
} else {
|
||||
var nonInterlacedPxPos = 0;
|
||||
getPxPos = function() {
|
||||
var returner = nonInterlacedPxPos;
|
||||
nonInterlacedPxPos += 4;
|
||||
return returner;
|
||||
};
|
||||
images = [{width: width, height: height}];
|
||||
}
|
||||
|
||||
for (var y = 0; y < height; y++) {
|
||||
for (var x = 0; x < width; x++) {
|
||||
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) {
|
||||
|
|
|
|||
|
|
@ -25,12 +25,12 @@ var util = require('util'),
|
|||
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);
|
||||
|
||||
var buffers = [];
|
||||
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),
|
||||
complete: function(width, height) {
|
||||
that.emit('complete', Buffer.concat(buffers), width, height)
|
||||
|
|
|
|||
|
|
@ -24,11 +24,11 @@ var SyncReader = require('./sync-reader'),
|
|||
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 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),
|
||||
write: function(buffer) {
|
||||
buffers.push(buffer);
|
||||
|
|
|
|||
|
|
@ -20,10 +20,17 @@
|
|||
|
||||
'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._height = height;
|
||||
|
|
@ -31,8 +38,6 @@ var Filter = module.exports = function(width, height, Bpp, depth, options, depen
|
|||
this._depth = depth;
|
||||
this._options = options;
|
||||
|
||||
this._line = 0;
|
||||
|
||||
if (!('filterType' in options) || options.filterType == -1) {
|
||||
options.filterType = [0, 1, 2, 3, 4];
|
||||
} 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.complete = dependencies.complete;
|
||||
|
||||
var byteWidth = this._width * this._Bpp;
|
||||
if (this._depth !== 8) {
|
||||
byteWidth = Math.ceil(byteWidth / (8 / this._depth));
|
||||
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
|
||||
});
|
||||
}
|
||||
this._byteWidth = byteWidth;
|
||||
};
|
||||
|
||||
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) {
|
||||
|
||||
var line = new Buffer(this._byteWidth);
|
||||
var currentImage = this._images[this._imageIndex];
|
||||
var line = new Buffer(currentImage.byteWidth);
|
||||
|
||||
var filter = rawData[0];
|
||||
|
||||
var xComparison = this._depth >= 8 ? ((this._depth === 16) ? this._Bpp * 2 : this._Bpp) : 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];
|
||||
switch(filter) {
|
||||
case 0:
|
||||
|
|
@ -106,14 +125,20 @@ Filter.prototype._reverseFilterLine = function(rawData) {
|
|||
//}
|
||||
}
|
||||
|
||||
this._line++;
|
||||
this._lastLine = line;
|
||||
this.write(line);
|
||||
|
||||
if (this._line < this._height) {
|
||||
this.read(this._byteWidth + 1, this._reverseFilterLine.bind(this));
|
||||
} else {
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
|
|
|||
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
|
||||
//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);
|
||||
|
||||
// compress it
|
||||
|
|
|
|||
|
|
@ -73,15 +73,17 @@ ParserAsync.prototype._inflateData = function(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._depth = depth;
|
||||
this._interlace = interlace;
|
||||
|
||||
this._filter = new FilterAsync(
|
||||
width, height,
|
||||
bpp,
|
||||
depth,
|
||||
interlace,
|
||||
this._options
|
||||
);
|
||||
};
|
||||
|
|
@ -100,7 +102,8 @@ ParserAsync.prototype._complete = function(data, width, height) {
|
|||
|
||||
data = bitmapper.dataToBitMap(data, width, height,
|
||||
this._bpp,
|
||||
this._depth);
|
||||
this._depth,
|
||||
this._interlace);
|
||||
|
||||
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._bpp,
|
||||
this._depth,
|
||||
this._interlace,
|
||||
this._options
|
||||
);
|
||||
|
||||
this._data = bitmapper.dataToBitMap(data, this._width, this._height,
|
||||
this._bpp,
|
||||
this._depth);
|
||||
this._depth,
|
||||
this._interlace);
|
||||
// todo yuck
|
||||
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);
|
||||
};
|
||||
|
||||
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._bpp = bpp;
|
||||
this._width = width;
|
||||
this._height = height;
|
||||
this._depth = depth;
|
||||
this._interlace = interlace;
|
||||
return this._data;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -180,14 +180,14 @@ Parser.prototype._parseIHDR = function(data) {
|
|||
this.error(new Error('Unsupported filter method'));
|
||||
return;
|
||||
}
|
||||
if (interlace != 0) {
|
||||
if (interlace != 0 && interlace !== 1) {
|
||||
this.error(new Error('Unsupported interlace method'));
|
||||
return;
|
||||
}
|
||||
|
||||
this._colorType = colorType;
|
||||
|
||||
this.createData(width, height, colorTypeToBppMap[this._colorType], depth);
|
||||
this.createData(width, height, colorTypeToBppMap[this._colorType], depth, interlace);
|
||||
|
||||
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) {
|
||||
|
||||
if (!file.match(/\.png$/i))
|
||||
if (!file.match(/.*\.png$/i))
|
||||
return;
|
||||
|
||||
var expectedError = false;
|
||||
if (file.match(/^x/) ||
|
||||
file.match(/^...i/) // interlace
|
||||
) {
|
||||
if (file.match(/^x/)) {
|
||||
expectedError = true;
|
||||
}
|
||||
|
||||
var data = fs.readFileSync(__dirname + '/in/' + file);
|
||||
try {
|
||||
console.log("Sync: parsing..", file);
|
||||
var png = PNG.sync.read(data);
|
||||
} catch (e) {
|
||||
if (!expectedError) {
|
||||
console.log("Unexpected error parsing.." + file);
|
||||
console.log(e);
|
||||
console.log(e.stack);
|
||||
}
|
||||
|
|
@ -30,7 +28,7 @@ fs.readdir(__dirname + '/in/', function(err, files) {
|
|||
}
|
||||
|
||||
if (expectedError) {
|
||||
console.log("Error expected, parsed fine", file);
|
||||
console.log("Error expected, parsed fine ..", file);
|
||||
}
|
||||
|
||||
var outpng = new PNG();
|
||||
|
|
|
|||
Loading…
Reference in a new issue