Interlace support

This commit is contained in:
Luke Page 2015-08-02 11:06:21 +01:00
parent 2d91824d0c
commit f7c70a92b4
10 changed files with 203 additions and 53 deletions

View file

@ -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) {

View file

@ -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)

View file

@ -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);

View file

@ -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
View 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]
}
];

View file

@ -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

View file

@ -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);

View file

@ -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;
};

View file

@ -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;

View file

@ -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();