diff --git a/lib/bitmapper.js b/lib/bitmapper.js index af6dc98..1be31d8 100644 --- a/lib/bitmapper.js +++ b/lib/bitmapper.js @@ -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) { diff --git a/lib/filter-async.js b/lib/filter-async.js index 551ecbb..36d5438 100644 --- a/lib/filter-async.js +++ b/lib/filter-async.js @@ -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) diff --git a/lib/filter-sync.js b/lib/filter-sync.js index 5f04db9..4fe9880 100644 --- a/lib/filter-sync.js +++ b/lib/filter-sync.js @@ -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); diff --git a/lib/filter.js b/lib/filter.js index e5a599d..eb39c7a 100755 --- a/lib/filter.js +++ b/lib/filter.js @@ -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); } }; diff --git a/lib/interlace.js b/lib/interlace.js new file mode 100644 index 0000000..47ba8fa --- /dev/null +++ b/lib/interlace.js @@ -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] + } +]; \ No newline at end of file diff --git a/lib/packer.js b/lib/packer.js index 96605d4..f4313b1 100755 --- a/lib/packer.js +++ b/lib/packer.js @@ -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 diff --git a/lib/parser-async.js b/lib/parser-async.js index 17d0e9a..93bc41c 100644 --- a/lib/parser-async.js +++ b/lib/parser-async.js @@ -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); diff --git a/lib/parser-sync.js b/lib/parser-sync.js index e885816..abd1426 100644 --- a/lib/parser-sync.js +++ b/lib/parser-sync.js @@ -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; }; diff --git a/lib/parser.js b/lib/parser.js index a20d14c..16ca84b 100644 --- a/lib/parser.js +++ b/lib/parser.js @@ -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; diff --git a/test/test.js b/test/test.js index 531d73e..ac2ab5f 100644 --- a/test/test.js +++ b/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();