added support for all color types (but still only 8 bits/sample)

This commit is contained in:
Kuba Niegowski 2012-08-20 23:06:44 +02:00
parent 2fe6c8805a
commit a34e6fe1cd
29 changed files with 296 additions and 32 deletions

1
.gitignore vendored
View file

@ -1,2 +1 @@
node_modules
*.png

View file

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

View file

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 319 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 321 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 355 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 389 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 269 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 985 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 719 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

38
examples/test/list.html Normal file
View file

@ -0,0 +1,38 @@
<!DOCTYPE html>
<html>
<head>
<title>PNG Test</title>
<style>
body { background: #0f0; font: 12px Arial; margin: 10px; }
img { margin: 2px; }
h3 { margin: 10px 0; }
</style>
</head>
<body>
<h3>Basic</h3>
<img src="img/basn0g08.png"> <img src="out/basn0g08.png"> grayscale<br>
<img src="img/basn2c08.png"> <img src="out/basn2c08.png"> color<br>
<img src="img/basn3p08.png"> <img src="out/basn3p08.png"> paletted<br>
<img src="img/basn4a08.png"> <img src="out/basn4a08.png"> grayscale + alpha<br>
<img src="img/basn6a08.png"> <img src="out/basn6a08.png"> color + alpha<br>
<h3>Image filtering</h3>
<img src="img/f00n0g08.png"> <img src="out/f00n0g08.png"> grayscale, filter 0<br>
<img src="img/f00n2c08.png"> <img src="out/f00n2c08.png"> color, filter 0<br>
<img src="img/f01n0g08.png"> <img src="out/f01n0g08.png"> grayscale, filter 1<br>
<img src="img/f01n2c08.png"> <img src="out/f01n2c08.png"> color, filter 1<br>
<img src="img/f02n0g08.png"> <img src="out/f02n0g08.png"> grayscale, filter 2<br>
<img src="img/f02n2c08.png"> <img src="out/f02n2c08.png"> color, filter 2<br>
<img src="img/f03n0g08.png"> <img src="out/f03n0g08.png"> grayscale, filter 3<br>
<img src="img/f03n2c08.png"> <img src="out/f03n2c08.png"> color, filter 3<br>
<img src="img/f04n0g08.png"> <img src="out/f04n0g08.png"> grayscale, filter 4<br>
<img src="img/f04n2c08.png"> <img src="out/f04n2c08.png"> color, filter 4<br>
<h3>Transparency</h3>
<img src="img/tp0n0g08.png"> <img src="out/tp0n0g08.png"> grayscale, not transparent<br>
<img src="img/tp0n2c08.png"> <img src="out/tp0n2c08.png"> color, not transparent<br>
<img src="img/tp0n3p08.png"> <img src="out/tp0n3p08.png"> paletted, not transparent<br>
<img src="img/tbrn2c08.png"> <img src="out/tbrn2c08.png"> color, transparent<br>
<img src="img/tp1n3p08.png"> <img src="out/tp1n3p08.png"> paletted, transparent<br>
</body>
</html>

36
examples/test/test.js Normal file
View file

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

View file

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

View file

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

View file

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

View file

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