diff --git a/.gitignore b/.gitignore
index 24cd046..3c3629e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1 @@
node_modules
-*.png
diff --git a/examples/test/img/PngSuite.LICENSE b/examples/test/img/PngSuite.LICENSE
new file mode 100644
index 0000000..8d4d1d0
--- /dev/null
+++ b/examples/test/img/PngSuite.LICENSE
@@ -0,0 +1,9 @@
+PngSuite
+--------
+
+Permission to use, copy, modify and distribute these images for any
+purpose and without fee is hereby granted.
+
+
+(c) Willem van Schaik, 1996, 2011
+
diff --git a/examples/test/img/PngSuite.README b/examples/test/img/PngSuite.README
new file mode 100644
index 0000000..3ef8f24
--- /dev/null
+++ b/examples/test/img/PngSuite.README
@@ -0,0 +1,25 @@
+ PNGSUITE
+----------------
+
+ testset for PNG-(de)coders
+ created by Willem van Schaik
+------------------------------------
+
+This is a collection of graphics images created to test the png applications
+like viewers, converters and editors. All (as far as that is possible)
+formats supported by the PNG standard are represented.
+
+The suite consists of the following files:
+
+- PngSuite.README - this file
+- PngSuite.LICENSE - the PngSuite is freeware
+- PngSuite.png - image with PngSuite logo
+- PngSuite.tgz - archive of all PNG testfiles
+- PngSuite.zip - same in .zip format for PCs
+
+
+--------
+ (c) Willem van Schaik
+ willem@schaik.com
+ Calgary, April 2011
+
diff --git a/examples/test/img/basn0g08.png b/examples/test/img/basn0g08.png
new file mode 100644
index 0000000..23c8237
Binary files /dev/null and b/examples/test/img/basn0g08.png differ
diff --git a/examples/test/img/basn2c08.png b/examples/test/img/basn2c08.png
new file mode 100644
index 0000000..db5ad15
Binary files /dev/null and b/examples/test/img/basn2c08.png differ
diff --git a/examples/test/img/basn3p08.png b/examples/test/img/basn3p08.png
new file mode 100644
index 0000000..0ddad07
Binary files /dev/null and b/examples/test/img/basn3p08.png differ
diff --git a/examples/test/img/basn4a08.png b/examples/test/img/basn4a08.png
new file mode 100644
index 0000000..3e13052
Binary files /dev/null and b/examples/test/img/basn4a08.png differ
diff --git a/examples/test/img/basn6a08.png b/examples/test/img/basn6a08.png
new file mode 100644
index 0000000..e608738
Binary files /dev/null and b/examples/test/img/basn6a08.png differ
diff --git a/examples/test/img/f00n0g08.png b/examples/test/img/f00n0g08.png
new file mode 100644
index 0000000..45a0075
Binary files /dev/null and b/examples/test/img/f00n0g08.png differ
diff --git a/examples/test/img/f00n2c08.png b/examples/test/img/f00n2c08.png
new file mode 100644
index 0000000..d6a1fff
Binary files /dev/null and b/examples/test/img/f00n2c08.png differ
diff --git a/examples/test/img/f01n0g08.png b/examples/test/img/f01n0g08.png
new file mode 100644
index 0000000..4a1107b
Binary files /dev/null and b/examples/test/img/f01n0g08.png differ
diff --git a/examples/test/img/f01n2c08.png b/examples/test/img/f01n2c08.png
new file mode 100644
index 0000000..26fee95
Binary files /dev/null and b/examples/test/img/f01n2c08.png differ
diff --git a/examples/test/img/f02n0g08.png b/examples/test/img/f02n0g08.png
new file mode 100644
index 0000000..bfe410c
Binary files /dev/null and b/examples/test/img/f02n0g08.png differ
diff --git a/examples/test/img/f02n2c08.png b/examples/test/img/f02n2c08.png
new file mode 100644
index 0000000..e590f12
Binary files /dev/null and b/examples/test/img/f02n2c08.png differ
diff --git a/examples/test/img/f03n0g08.png b/examples/test/img/f03n0g08.png
new file mode 100644
index 0000000..ed01e29
Binary files /dev/null and b/examples/test/img/f03n0g08.png differ
diff --git a/examples/test/img/f03n2c08.png b/examples/test/img/f03n2c08.png
new file mode 100644
index 0000000..7581150
Binary files /dev/null and b/examples/test/img/f03n2c08.png differ
diff --git a/examples/test/img/f04n0g08.png b/examples/test/img/f04n0g08.png
new file mode 100644
index 0000000..663fdae
Binary files /dev/null and b/examples/test/img/f04n0g08.png differ
diff --git a/examples/test/img/f04n2c08.png b/examples/test/img/f04n2c08.png
new file mode 100644
index 0000000..3c8b511
Binary files /dev/null and b/examples/test/img/f04n2c08.png differ
diff --git a/examples/test/img/tbrn2c08.png b/examples/test/img/tbrn2c08.png
new file mode 100644
index 0000000..5cca0d6
Binary files /dev/null and b/examples/test/img/tbrn2c08.png differ
diff --git a/examples/test/img/tp0n0g08.png b/examples/test/img/tp0n0g08.png
new file mode 100644
index 0000000..333465f
Binary files /dev/null and b/examples/test/img/tp0n0g08.png differ
diff --git a/examples/test/img/tp0n2c08.png b/examples/test/img/tp0n2c08.png
new file mode 100644
index 0000000..fc6e42c
Binary files /dev/null and b/examples/test/img/tp0n2c08.png differ
diff --git a/examples/test/img/tp0n3p08.png b/examples/test/img/tp0n3p08.png
new file mode 100644
index 0000000..69a69e5
Binary files /dev/null and b/examples/test/img/tp0n3p08.png differ
diff --git a/examples/test/img/tp1n3p08.png b/examples/test/img/tp1n3p08.png
new file mode 100644
index 0000000..a6c9f35
Binary files /dev/null and b/examples/test/img/tp1n3p08.png differ
diff --git a/examples/test/list.html b/examples/test/list.html
new file mode 100644
index 0000000..1e3aa12
--- /dev/null
+++ b/examples/test/list.html
@@ -0,0 +1,38 @@
+
+
+
+ PNG Test
+
+
+
+ Basic
+
grayscale
+
color
+
paletted
+
grayscale + alpha
+
color + alpha
+
+ Image filtering
+
grayscale, filter 0
+
color, filter 0
+
grayscale, filter 1
+
color, filter 1
+
grayscale, filter 2
+
color, filter 2
+
grayscale, filter 3
+
color, filter 3
+
grayscale, filter 4
+
color, filter 4
+
+ Transparency
+
grayscale, not transparent
+
color, not transparent
+
paletted, not transparent
+
color, transparent
+
paletted, transparent
+
+
diff --git a/examples/test/test.js b/examples/test/test.js
new file mode 100644
index 0000000..5e59d6d
--- /dev/null
+++ b/examples/test/test.js
@@ -0,0 +1,36 @@
+
+var fs = require('fs'),
+ PNG = require('pngjs').PNG;
+
+
+fs.readdir(__dirname + '/img/', function(err, files) {
+ if (err) throw err;
+
+ for (var i = 0; i < files.length; i++) {
+
+ if (!files[i].match(/\.png$/i))
+ continue;
+
+ fs.createReadStream(__dirname + '/img/' + files[i])
+ .pipe(new PNG())
+ .on('parsed', function() {
+
+ if (this.gamma) {
+ for (var y = 0; y < this.height; y++) {
+ for (var x = 0; x < this.width; x++) {
+ var idx = (this.width * y + x) << 2;
+
+ for (var i = 0; i < 3; i++) {
+ var sample = this.data[idx + i] / 255;
+ sample = Math.pow(sample, 1 / this.gamma);
+ sample = Math.pow(sample, 1 / 2.2);
+ this.data[idx + i] = Math.round(sample * 255);
+ }
+ }
+ }
+ }
+
+ this.pack();
+ }).pipe(fs.createWriteStream(__dirname + '/out/' + files[i]));
+ }
+});
diff --git a/examples/test2.js b/examples/test2.js
index fe46892..466bd1b 100755
--- a/examples/test2.js
+++ b/examples/test2.js
@@ -5,7 +5,7 @@ var fs = require('fs'),
var png = new PNG({
- filterType: 4
+ filterType: -1
}),
src = fs.createReadStream(process.argv[2]),
dst = fs.createWriteStream(process.argv[3] || 'out.png');
diff --git a/lib/filter.js b/lib/filter.js
index 379a65f..3b8ec7e 100644
--- a/lib/filter.js
+++ b/lib/filter.js
@@ -55,58 +55,112 @@ Filter.prototype.prepare = function(width, height, type) {
this.height = height;
};
-Filter.prototype.unfilter = function(rawData) {
+Filter.prototype.unfilter = function(rawData, Bpp) {
var pxLineLength = this.width << 2,
- rawLineLength = pxLineLength + 1,
+ rawLineLength = this.width * Bpp + 1,
pxData = new Buffer(pxLineLength * this.height);
for (var y = 0; y < this.height; y++) {
var rawRowPos = rawLineLength * y + 1,
pxRowPos = y * pxLineLength,
- pxUpRowPos = pxRowPos - pxLineLength,
filter = rawData[rawRowPos - 1];
+
if (filter == 0) {
- rawData.copy(pxData, pxRowPos, rawRowPos, rawRowPos + pxLineLength);
+ for (var x = 0; x < this.width; x++) {
+ var pxPos = pxRowPos + (x << 2),
+ rawPos = rawRowPos + x * Bpp;
+
+ for (var i = 0; i < Bpp; i++)
+ pxData[pxPos + i] = rawData[rawPos + i];
+ }
} else if (filter == 1) {
- for (var x = 0; x < pxLineLength; x++) {
+ for (var x = 0; x < this.width; x++) {
+ var pxPos = pxRowPos + (x << 2),
+ rawPos = rawRowPos + x * Bpp;
- var left = x >= 4 ? pxData[pxRowPos + x - 4] : 0;
- pxData[pxRowPos + x] = rawData[rawRowPos + x] + left;
+ for (var i = 0; i < Bpp; i++) {
+ var left = x > 0 ? pxData[pxPos + i - 4] : 0;
+ pxData[pxPos + i] = rawData[rawPos + i] + left;
+ }
}
} else if (filter == 2) {
- for (var x = 0; x < pxLineLength; x++) {
+ for (var x = 0; x < this.width; x++) {
+ var pxPos = pxRowPos + (x << 2),
+ rawPos = rawRowPos + x * Bpp;
- var up = y > 0 ? pxData[pxUpRowPos + x] : 0;
- pxData[pxRowPos + x] = rawData[rawRowPos + x] + up;
+ for (var i = 0; i < Bpp; i++) {
+ var up = y > 0 ? pxData[pxPos - pxLineLength + i] : 0;
+ pxData[pxPos + i] = rawData[rawPos + i] + up;
+ }
}
} else if (filter == 3) {
- for (var x = 0; x < pxLineLength; x++) {
+ for (var x = 0; x < this.width; x++) {
+ var pxPos = pxRowPos + (x << 2),
+ rawPos = rawRowPos + x * Bpp;
- var left = x >= 4 ? pxData[pxRowPos + x - 4] : 0,
- up = y > 0 ? pxData[pxUpRowPos + x] : 0;
+ for (var i = 0; i < Bpp; i++) {
+ var left = x > 0 ? pxData[pxPos + i - 4] : 0,
+ up = y > 0 ? pxData[pxPos - pxLineLength + i] : 0;
- pxData[pxRowPos + x] = rawData[rawRowPos + x]
- + Math.floor((left + up) / 2);
+ pxData[pxPos + i] = rawData[rawPos + i]
+ + Math.floor((left + up) / 2);
+ }
}
} else if (filter == 4) {
- for (var x = 0; x < pxLineLength; x++) {
+ for (var x = 0; x < this.width; x++) {
+ var pxPos = pxRowPos + (x << 2),
+ rawPos = rawRowPos + x * Bpp;
- var left = x >= 4 ? pxData[pxRowPos + x - 4] : 0,
- up = y > 0 ? pxData[pxUpRowPos + x] : 0,
- upLeft = x >= 4 && y > 0 ? pxData[pxUpRowPos + x - 4] : 0;
+ for (var i = 0; i < Bpp; i++) {
+ var left = x > 0 ? pxData[pxPos + i - 4] : 0,
+ up = y > 0 ? pxData[pxPos - pxLineLength + i] : 0,
+ upLeft = x > 0 && y > 0
+ ? pxData[pxPos - pxLineLength + i - 4] : 0;
- pxData[pxRowPos + x] = rawData[rawRowPos + x]
- + PaethPredictor(left, up, upLeft)
+ pxData[pxPos + i] = rawData[rawPos + i]
+ + PaethPredictor(left, up, upLeft)
+ }
}
}
}
+
+ // expand data to 32 bit
+ for (var y = 0; y < this.height; y++) {
+ var pxRowPos = y * pxLineLength;
+
+ if (Bpp == 1) { // L
+ for (var x = 0; x < this.width; x++) {
+ var pxPos = pxRowPos + (x << 2);
+
+ pxData[pxPos + 1] = pxData[pxPos + 2] = pxData[pxPos];
+ pxData[pxPos + 3] = 0xff;
+ }
+
+ } else if (Bpp == 2) { // LA
+ for (var x = 0; x < this.width; x++) {
+ var pxPos = pxRowPos + (x << 2);
+
+ pxData[pxPos + 3] = pxData[pxPos + 1];
+ pxData[pxPos + 1] = pxData[pxPos + 2] = pxData[pxPos];
+ }
+
+ } else if (Bpp == 3) { // RGB
+ for (var x = 0; x < this.width; x++) {
+ var pxPos = pxRowPos + (x << 2);
+
+ pxData[pxPos + 3] = 0xff;
+ }
+
+ } // else RGBA
+ }
+
return pxData;
};
diff --git a/lib/parser.js b/lib/parser.js
index ed7b3bf..08117d7 100644
--- a/lib/parser.js
+++ b/lib/parser.js
@@ -32,6 +32,13 @@ var signature = [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a];
var TYPE_IHDR = 0x49484452;
var TYPE_IEND = 0x49454e44;
var TYPE_IDAT = 0x49444154;
+var TYPE_PLTE = 0x504c5445;
+var TYPE_tRNS = 0x74524e53;
+var TYPE_gAMA = 0x67414d41;
+
+var COLOR_PALETTE = 1;
+var COLOR_COLOR = 2;
+var COLOR_ALPHA = 4;
var Parser = module.exports = function(options) {
@@ -43,10 +50,17 @@ var Parser = module.exports = function(options) {
this._hasIHDR = false;
this._hasIEND = false;
+ // input flags
+ this._palette = [];
+ this._colorType = 0;
+
this._chunks = {};
this._chunks[TYPE_IHDR] = this._parseIHDR.bind(this);
this._chunks[TYPE_IEND] = this._parseIEND.bind(this);
this._chunks[TYPE_IDAT] = this._parseIDAT.bind(this);
+ this._chunks[TYPE_PLTE] = this._parsePLTE.bind(this);
+ this._chunks[TYPE_tRNS] = this._parseTRNS.bind(this);
+ this._chunks[TYPE_gAMA] = this._parseGAMA.bind(this);
this._compress = new Compress(options);
this._filter = new Filter(options);
@@ -60,9 +74,7 @@ Parser.prototype._initCompress = function() {
this._compress.on('error', this.emit.bind(this, 'error'));
this._compress.on('deflated', this._packData.bind(this));
- this._compress.on('inflated', function(data) {
- this.emit('parsed', this._filter.unfilter(data));
- }.bind(this));
+ this._compress.on('inflated', this._unfilter.bind(this));
};
Parser.prototype._parse = function(data) {
@@ -111,6 +123,47 @@ Parser.prototype._packData = function(data) {
this.emit('end');
};
+Parser.prototype._unfilter = function(data) {
+
+ // expand data to 32 bit depending on colorType
+ if (this._colorType == 0) { // L
+ data = this._filter.unfilter(data, 1); // 1 Bpp
+
+ } else if (this._colorType == 2) { // RGB
+ data = this._filter.unfilter(data, 3); // 3 Bpp
+
+ } else if (this._colorType == 3) { // I
+ data = this._filter.unfilter(data, 1); // 1 Bpp
+
+ // use values fom palette
+ var pxLineLength = this.width << 2;
+
+ for (var y = 0; y < this.height; y++) {
+ var pxRowPos = y * pxLineLength;
+
+ for (var x = 0; x < this.width; x++) {
+ var pxPos = pxRowPos + (x << 2),
+ color = this._palette[data[pxPos]];
+
+ for (var i = 0; i < 4; i++)
+ data[pxPos + i] = color[i];
+ }
+ }
+
+ } else if (this._colorType == 4) { // LA
+ data = this._filter.unfilter(data, 2); // 2 Bpp
+
+ } else if (this._colorType == 6) { // RGBA
+ data = this._filter.unfilter(data, 4); // 4 Bpp
+
+ } else throw new Error('Unsupported color type');
+
+ this.emit('parsed', data);
+};
+
+
+
+
Parser.prototype._parseChunk = function(data, idx) {
if (this._hasIEND)
@@ -154,6 +207,8 @@ Parser.prototype._parseChunk = function(data, idx) {
} else if (!ancillary)
throw new Error('Unsupported critical chunk type ' + name);
+ // else
+ // console.log('Ignoring chunk', name, type.toString(16));
return idx;
};
@@ -189,12 +244,12 @@ Parser.prototype._parseIHDR = function(data) {
// );
if (depth != 8)
- throw new Error('Unsupported bit depth');
- if (colorType != 6)
- throw new Error('Unsupported color type');
+ throw new Error('Unsupported bit depth ' + depth);
if (interlace != 0)
throw new Error('Unsupported interlace method');
+ this._colorType = colorType;
+
this._compress.prepareInflate(compr);
this._filter.prepare(width, height, filter);
@@ -218,7 +273,48 @@ Parser.prototype._packIHDR = function(width, height) {
};
+Parser.prototype._parsePLTE = function(data) {
+
+ var entries = Math.floor(data.length / 3);
+ // console.log('Palette:', entries);
+
+ for (var i = 0; i < entries; i++) {
+ this._palette.push([
+ data.readUInt8(i * 3),
+ data.readUInt8(i * 3 + 1),
+ data.readUInt8(i * 3 + 2 ),
+ 0xff
+ ]);
+ }
+};
+
+Parser.prototype._parseTRNS = function(data) {
+
+ // palette
+ if (this._colorType == 3) {
+ if (this._palette.length == 0)
+ throw new Error('Transparency chunk must be after palette');
+
+ if (data.length > this._palette.length)
+ throw new Error('More transparent colors than palette size');
+
+ for (var i = 0; i < this._palette.length; i++)
+ this._palette[i][3] = i < data.length ? data.readUInt8(i) : 0xff;
+ }
+
+ // for colorType 0 (grayscale) and 2 (rgb)
+ // there might be one gray/color defined as transparent
+};
+
+Parser.prototype._parseGAMA = function(data) {
+ this.emit('gamma', data.readUInt32BE(0) / 100000);
+};
+
Parser.prototype._parseIDAT = function(data) {
+
+ if (this._colorType == 3 && this._palette.length == 0)
+ throw new Error('Expected palette not found');
+
this._compress.writeInflate(data);
};
diff --git a/lib/png.js b/lib/png.js
index 00cf3d1..693db79 100644
--- a/lib/png.js
+++ b/lib/png.js
@@ -26,7 +26,7 @@ var util = require('util'),
var PNG = exports.PNG = function(options) {
- Parser.call(this, options);
+ Parser.call(this, options = options || {});
this.width = options.width || 0;
this.height = options.height || 0;
@@ -34,12 +34,15 @@ var PNG = exports.PNG = function(options) {
this.data = this.width > 0 && this.height > 0
? new Buffer(4 * this.width * this.height) : null;
- this.on('metadata', this._metadata.bind(this));
+ this.gamma = 0;
this.readable = this.writable = true;
this._buffers = [];
this._buffLen = 0;
+ this.on('metadata', this._metadata.bind(this));
+ this.on('gamma', this._gamma.bind(this));
+
this.on('parsed', function(data) {
this.data = data;
}.bind(this));
@@ -93,6 +96,10 @@ PNG.prototype._metadata = function(width, height) {
this.data = null;
};
+PNG.prototype._gamma = function(gamma) {
+ this.gamma = gamma;
+};
+
PNG.prototype.bitblt = function(dst, sx, sy, w, h, dx, dy) {
var src = this;
@@ -100,7 +107,7 @@ PNG.prototype.bitblt = function(dst, sx, sy, w, h, dx, dy) {
if (sx > src.width || sy > src.height
|| sx + w > src.width || sy + h > src.height)
throw new Error('bitblt reading outside image');
- if (dy > dst.width || dy > dst.height
+ if (dx > dst.width || dy > dst.height
|| dx + w > dst.width || dy + h > dst.height)
throw new Error('bitblt writing outside image');