mirror of
https://github.com/danbulant/pngjs
synced 2026-07-06 11:40:43 +00:00
first commit
This commit is contained in:
commit
413c967aae
10 changed files with 824 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
node_modules
|
||||||
|
*.png
|
||||||
19
LICENSE
Normal file
19
LICENSE
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
Copyright (c) 2012 Kuba Niegowski
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
10
README.md
Normal file
10
README.md
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
PNG.js
|
||||||
|
========
|
||||||
|
|
||||||
|
|
||||||
|
Options
|
||||||
|
--------
|
||||||
|
- `checkCRC` - `boolean` default: `true`
|
||||||
|
- `deflateChunkSize` - `int` default: 32 kB
|
||||||
|
- `deflateLevel` - `int` default: 9
|
||||||
|
- `filterType` - `int` default: -1 (auto)
|
||||||
31
examples/test.js
Executable file
31
examples/test.js
Executable file
|
|
@ -0,0 +1,31 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
var fs = require('fs'),
|
||||||
|
PNG = require('../lib/png').PNG;
|
||||||
|
|
||||||
|
|
||||||
|
fs.readFile(process.argv[2], function(err, data) {
|
||||||
|
if (err) throw err;
|
||||||
|
|
||||||
|
var png = new PNG();
|
||||||
|
png.parse(data, function(err) {
|
||||||
|
if (err) console.log(err.stack);
|
||||||
|
|
||||||
|
for (var y = 0; y < png.height; y++) {
|
||||||
|
for (var x = 0; x < png.width; x++) {
|
||||||
|
var idx = (png.width * y + x) << 2;
|
||||||
|
|
||||||
|
// invert color
|
||||||
|
png.data[idx] = 255 - png.data[idx];
|
||||||
|
png.data[idx+1] = 255 - png.data[idx+1];
|
||||||
|
png.data[idx+2] = 255 - png.data[idx+2];
|
||||||
|
|
||||||
|
// and reduce opacity
|
||||||
|
png.data[idx+3] = png.data[idx+3] >> 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
png.pipe(fs.createWriteStream(process.argv[3] || 'out.png'));
|
||||||
|
png.pack();
|
||||||
|
});
|
||||||
|
});
|
||||||
33
examples/test2.js
Executable file
33
examples/test2.js
Executable file
|
|
@ -0,0 +1,33 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
var fs = require('fs'),
|
||||||
|
PNG = require('../lib/png').PNG;
|
||||||
|
|
||||||
|
|
||||||
|
var png = new PNG({
|
||||||
|
filterType: 4
|
||||||
|
}),
|
||||||
|
src = fs.createReadStream(process.argv[2]),
|
||||||
|
dst = fs.createWriteStream(process.argv[3] || 'out.png');
|
||||||
|
|
||||||
|
|
||||||
|
png.on('parsed', function() {
|
||||||
|
|
||||||
|
for (var y = 0; y < png.height; y++) {
|
||||||
|
for (var x = 0; x < png.width; x++) {
|
||||||
|
var idx = (png.width * y + x) << 2;
|
||||||
|
|
||||||
|
// invert color
|
||||||
|
png.data[idx] = 255 - png.data[idx];
|
||||||
|
png.data[idx+1] = 255 - png.data[idx+1];
|
||||||
|
png.data[idx+2] = 255 - png.data[idx+2];
|
||||||
|
|
||||||
|
// and reduce opacity
|
||||||
|
png.data[idx+3] = png.data[idx+3] >> 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
png.pack();
|
||||||
|
});
|
||||||
|
|
||||||
|
src.pipe(png).pipe(dst);
|
||||||
94
lib/compress.js
Normal file
94
lib/compress.js
Normal file
|
|
@ -0,0 +1,94 @@
|
||||||
|
// Copyright (c) 2012 Kuba Niegowski
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var util = require('util'),
|
||||||
|
zlib = require('zlib'),
|
||||||
|
events = require('events');
|
||||||
|
|
||||||
|
|
||||||
|
var Compress = module.exports = function(options) {
|
||||||
|
events.EventEmitter.call(this);
|
||||||
|
|
||||||
|
this._options = options;
|
||||||
|
options.deflateChunkSize = options.deflateChunkSize || 32 * 1024;
|
||||||
|
options.deflateLevel = options.deflateLevel || 9;
|
||||||
|
|
||||||
|
this._inflate = null;
|
||||||
|
};
|
||||||
|
util.inherits(Compress, events.EventEmitter);
|
||||||
|
|
||||||
|
|
||||||
|
Compress.prototype.prepareInflate = function(type) {
|
||||||
|
|
||||||
|
if (type != 0)
|
||||||
|
throw new Error('Unsupported compression method');
|
||||||
|
|
||||||
|
this._inflate = zlib.createInflate();
|
||||||
|
|
||||||
|
bufferStream(this._inflate, function(data) {
|
||||||
|
this._inflate = null;
|
||||||
|
this.emit('inflated', data);
|
||||||
|
}.bind(this));
|
||||||
|
|
||||||
|
this._inflate.on('error', this.emit.bind(this, 'error'));
|
||||||
|
};
|
||||||
|
|
||||||
|
Compress.prototype.writeInflate = function(data) {
|
||||||
|
this._inflate.write(data);
|
||||||
|
};
|
||||||
|
|
||||||
|
Compress.prototype.endInflate = function() {
|
||||||
|
this._inflate.end();
|
||||||
|
};
|
||||||
|
|
||||||
|
Compress.prototype.deflate = function(data) {
|
||||||
|
|
||||||
|
var deflate = zlib.createDeflate({
|
||||||
|
chunkSize: this._options.deflateChunkSize,
|
||||||
|
level: this._options.deflateLevel
|
||||||
|
});
|
||||||
|
|
||||||
|
bufferStream(deflate, function(data) {
|
||||||
|
this.emit('deflated', data);
|
||||||
|
}.bind(this));
|
||||||
|
|
||||||
|
deflate.on('error', this.emit.bind(this, 'error'));
|
||||||
|
|
||||||
|
deflate.end(data);
|
||||||
|
};
|
||||||
|
|
||||||
|
function bufferStream(stream, callback) {
|
||||||
|
|
||||||
|
var buffers = [],
|
||||||
|
length = 0;
|
||||||
|
|
||||||
|
stream.on('data', function(data) {
|
||||||
|
buffers.push(data);
|
||||||
|
length += data.length;
|
||||||
|
});
|
||||||
|
|
||||||
|
stream.on('end', function() {
|
||||||
|
callback(Buffer.concat(buffers, length));
|
||||||
|
});
|
||||||
|
|
||||||
|
return stream;
|
||||||
|
};
|
||||||
254
lib/filter.js
Normal file
254
lib/filter.js
Normal file
|
|
@ -0,0 +1,254 @@
|
||||||
|
// Copyright (c) 2012 Kuba Niegowski
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var util = require('util'),
|
||||||
|
zlib = require('zlib'),
|
||||||
|
events = require('events');
|
||||||
|
|
||||||
|
|
||||||
|
var Filter = module.exports = function(options) {
|
||||||
|
events.EventEmitter.call(this);
|
||||||
|
|
||||||
|
this._options = options;
|
||||||
|
options.filterType = 'filterType' in options ? options.filterType : -1;
|
||||||
|
|
||||||
|
this._width = 0;
|
||||||
|
this._height = 0;
|
||||||
|
|
||||||
|
this._filters = {
|
||||||
|
0: this._filterNone.bind(this),
|
||||||
|
1: this._filterSub.bind(this),
|
||||||
|
2: this._filterUp.bind(this),
|
||||||
|
3: this._filterAvg.bind(this),
|
||||||
|
4: this._filterPaeth.bind(this)
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
||||||
|
util.inherits(Filter, events.EventEmitter);
|
||||||
|
|
||||||
|
|
||||||
|
Filter.prototype.prepare = function(width, height, type) {
|
||||||
|
|
||||||
|
if (type != 0)
|
||||||
|
throw new Error('Unsupported filter method');
|
||||||
|
|
||||||
|
this.width = width;
|
||||||
|
this.height = height;
|
||||||
|
};
|
||||||
|
|
||||||
|
Filter.prototype.unfilter = function(rawData) {
|
||||||
|
|
||||||
|
var pxLineLength = this.width << 2,
|
||||||
|
rawLineLength = pxLineLength + 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);
|
||||||
|
|
||||||
|
} else if (filter == 1) {
|
||||||
|
for (var x = 0; x < pxLineLength; x++) {
|
||||||
|
|
||||||
|
var left = x >= 4 ? pxData[pxRowPos + x - 4] : 0;
|
||||||
|
pxData[pxRowPos + x] = rawData[rawRowPos + x] + left;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (filter == 2) {
|
||||||
|
for (var x = 0; x < pxLineLength; x++) {
|
||||||
|
|
||||||
|
var up = y > 0 ? pxData[pxUpRowPos + x] : 0;
|
||||||
|
pxData[pxRowPos + x] = rawData[rawRowPos + x] + up;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (filter == 3) {
|
||||||
|
for (var x = 0; x < pxLineLength; x++) {
|
||||||
|
|
||||||
|
var left = x >= 4 ? pxData[pxRowPos + x - 4] : 0,
|
||||||
|
up = y > 0 ? pxData[pxUpRowPos + x] : 0;
|
||||||
|
|
||||||
|
pxData[pxRowPos + x] = rawData[rawRowPos + x]
|
||||||
|
+ Math.floor((left + up) / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (filter == 4) {
|
||||||
|
for (var x = 0; x < pxLineLength; x++) {
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
pxData[pxRowPos + x] = rawData[rawRowPos + x]
|
||||||
|
+ PaethPredictor(left, up, upLeft)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return pxData;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
Filter.prototype.filter = function(pxData, width, height) {
|
||||||
|
|
||||||
|
var rawData = new Buffer(((width << 2) + 1) * height);
|
||||||
|
|
||||||
|
for (var y = 0; y < height; y++) {
|
||||||
|
|
||||||
|
// find best filter for this line (with lowest sum of values)
|
||||||
|
if (this._options.filterType == -1) {
|
||||||
|
var min = Infinity,
|
||||||
|
sel = 0;
|
||||||
|
|
||||||
|
for (var f in this._filters) {
|
||||||
|
var sum = this._filters[f](pxData, y, width, height, null);
|
||||||
|
if (sum < min) {
|
||||||
|
sel = f;
|
||||||
|
min = sum;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
sel = this._options.filterType;
|
||||||
|
}
|
||||||
|
this._filters[sel](pxData, y, width, height, rawData);
|
||||||
|
}
|
||||||
|
return rawData;
|
||||||
|
};
|
||||||
|
|
||||||
|
Filter.prototype._filterNone = function(pxData, y, width, height, rawData) {
|
||||||
|
|
||||||
|
var pxRowLength = width << 2,
|
||||||
|
rawRowLength = pxRowLength + 1,
|
||||||
|
sum = 0;
|
||||||
|
|
||||||
|
if (!rawData) {
|
||||||
|
for (var x = 0; x < pxRowLength; x++)
|
||||||
|
sum += pxData[y * pxRowLength + x];
|
||||||
|
|
||||||
|
} else {
|
||||||
|
rawData[y * rawRowLength] = 0;
|
||||||
|
pxData.copy(rawData, rawRowLength * y + 1, pxRowLength * y, pxRowLength * (y + 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
return sum;
|
||||||
|
};
|
||||||
|
|
||||||
|
Filter.prototype._filterSub = function(pxData, y, width, height, rawData) {
|
||||||
|
|
||||||
|
var pxRowLength = width << 2,
|
||||||
|
rawRowLength = pxRowLength + 1,
|
||||||
|
sum = 0;
|
||||||
|
|
||||||
|
if (rawData)
|
||||||
|
rawData[y * rawRowLength] = 1;
|
||||||
|
|
||||||
|
for (var x = 0; x < pxRowLength; x++) {
|
||||||
|
|
||||||
|
var left = x >= 4 ? pxData[y * pxRowLength + x - 4] : 0,
|
||||||
|
val = pxData[y * pxRowLength + x] - left;
|
||||||
|
|
||||||
|
if (!rawData) sum += val;
|
||||||
|
else rawData[y * rawRowLength + 1 + x] = val;
|
||||||
|
}
|
||||||
|
return sum;
|
||||||
|
};
|
||||||
|
|
||||||
|
Filter.prototype._filterUp = function(pxData, y, width, height, rawData) {
|
||||||
|
|
||||||
|
var pxRowLength = width << 2,
|
||||||
|
rawRowLength = pxRowLength + 1,
|
||||||
|
sum = 0;
|
||||||
|
|
||||||
|
if (rawData)
|
||||||
|
rawData[y * rawRowLength] = 2;
|
||||||
|
|
||||||
|
for (var x = 0; x < pxRowLength; x++) {
|
||||||
|
|
||||||
|
var up = y > 0 ? pxData[(y - 1) * pxRowLength + x] : 0,
|
||||||
|
val = pxData[y * pxRowLength + x] - up;
|
||||||
|
|
||||||
|
if (!rawData) sum += val;
|
||||||
|
else rawData[y * rawRowLength + 1 + x] = val;
|
||||||
|
}
|
||||||
|
return sum;
|
||||||
|
};
|
||||||
|
|
||||||
|
Filter.prototype._filterAvg = function(pxData, y, width, height, rawData) {
|
||||||
|
|
||||||
|
var pxRowLength = width << 2,
|
||||||
|
rawRowLength = pxRowLength + 1,
|
||||||
|
sum = 0;
|
||||||
|
|
||||||
|
if (rawData)
|
||||||
|
rawData[y * rawRowLength] = 3;
|
||||||
|
|
||||||
|
for (var x = 0; x < pxRowLength; x++) {
|
||||||
|
|
||||||
|
var left = x >= 4 ? pxData[y * pxRowLength + x - 4] : 0,
|
||||||
|
up = y > 0 ? pxData[(y - 1) * pxRowLength + x] : 0,
|
||||||
|
val = pxData[y * pxRowLength + x] - ((left + up) >> 1);
|
||||||
|
|
||||||
|
if (!rawData) sum += val;
|
||||||
|
else rawData[y * rawRowLength + 1 + x] = val;
|
||||||
|
}
|
||||||
|
return sum;
|
||||||
|
};
|
||||||
|
|
||||||
|
Filter.prototype._filterPaeth = function(pxData, y, width, height, rawData) {
|
||||||
|
|
||||||
|
var pxRowLength = width << 2,
|
||||||
|
rawRowLength = pxRowLength + 1,
|
||||||
|
sum = 0;
|
||||||
|
|
||||||
|
if (rawData)
|
||||||
|
rawData[y * rawRowLength] = 4;
|
||||||
|
|
||||||
|
for (var x = 0; x < pxRowLength; x++) {
|
||||||
|
|
||||||
|
var left = x >= 4 ? pxData[y * pxRowLength + x - 4] : 0,
|
||||||
|
up = y > 0 ? pxData[(y - 1) * pxRowLength + x] : 0,
|
||||||
|
upLeft = x >= 4 && y > 0 ? pxData[(y - 1) * pxRowLength + x - 4] : 0,
|
||||||
|
val = pxData[y * pxRowLength + x] - PaethPredictor(left, up, upLeft);
|
||||||
|
|
||||||
|
if (!rawData) sum += val;
|
||||||
|
else rawData[y * rawRowLength + 1 + x] = val;
|
||||||
|
}
|
||||||
|
return sum;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
var PaethPredictor = function(left, above, upLeft) {
|
||||||
|
|
||||||
|
var p = left + above - upLeft,
|
||||||
|
pLeft = Math.abs(p - left),
|
||||||
|
pAbove = Math.abs(p - above),
|
||||||
|
pUpLeft = Math.abs(p - upLeft);
|
||||||
|
|
||||||
|
if (pLeft <= pAbove && pLeft <= pUpLeft) return left;
|
||||||
|
else if (pAbove <= pUpLeft) return above;
|
||||||
|
else return upLeft;
|
||||||
|
};
|
||||||
272
lib/parser.js
Normal file
272
lib/parser.js
Normal file
|
|
@ -0,0 +1,272 @@
|
||||||
|
// Copyright (c) 2012 Kuba Niegowski
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
|
||||||
|
var util = require('util'),
|
||||||
|
Stream = require('stream'),
|
||||||
|
Compress = require('./compress'),
|
||||||
|
Filter = require('./filter');
|
||||||
|
|
||||||
|
|
||||||
|
var signature = [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a];
|
||||||
|
|
||||||
|
var TYPE_IHDR = 0x49484452;
|
||||||
|
var TYPE_IEND = 0x49454e44;
|
||||||
|
var TYPE_IDAT = 0x49444154;
|
||||||
|
|
||||||
|
|
||||||
|
var Parser = module.exports = function(options) {
|
||||||
|
Stream.call(this);
|
||||||
|
|
||||||
|
this._options = options;
|
||||||
|
options.checkCRC = options.checkCRC !== false;
|
||||||
|
|
||||||
|
this._hasIHDR = false;
|
||||||
|
this._hasIEND = false;
|
||||||
|
|
||||||
|
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._compress = new Compress(options);
|
||||||
|
this._filter = new Filter(options);
|
||||||
|
|
||||||
|
this._initCompress();
|
||||||
|
};
|
||||||
|
util.inherits(Parser, Stream);
|
||||||
|
|
||||||
|
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));
|
||||||
|
};
|
||||||
|
|
||||||
|
Parser.prototype._parse = function(data) {
|
||||||
|
|
||||||
|
var idx = 0;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// check PNG file signature
|
||||||
|
while (idx < signature.length) {
|
||||||
|
if (data[idx] != signature[idx]) {
|
||||||
|
throw new Error('Invalid file signature');
|
||||||
|
}
|
||||||
|
idx++;
|
||||||
|
}
|
||||||
|
//console.log('Signature is ok');
|
||||||
|
|
||||||
|
// iterate chunks
|
||||||
|
while (idx < data.length) {
|
||||||
|
idx = this._parseChunk(data, idx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(err) {
|
||||||
|
this.emit('error', err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Parser.prototype._pack = function(width, height, data) {
|
||||||
|
|
||||||
|
// Signature
|
||||||
|
this.emit('data', new Buffer(signature));
|
||||||
|
this.emit('data', this._packIHDR(width, height));
|
||||||
|
|
||||||
|
// filter pixel data
|
||||||
|
var data = this._filter.filter(data, width, height);
|
||||||
|
|
||||||
|
// compress it
|
||||||
|
this._compress.deflate(data);
|
||||||
|
};
|
||||||
|
|
||||||
|
Parser.prototype._packData = function(data) {
|
||||||
|
|
||||||
|
// console.log('deflate', data.length);
|
||||||
|
|
||||||
|
this.emit('data', this._packIDAT(data));
|
||||||
|
this.emit('data', this._packIEND());
|
||||||
|
this.emit('end');
|
||||||
|
};
|
||||||
|
|
||||||
|
Parser.prototype._parseChunk = function(data, idx) {
|
||||||
|
|
||||||
|
if (this._hasIEND)
|
||||||
|
throw new Error('Not expected chunk after IEND');
|
||||||
|
|
||||||
|
// chunk size (only content)
|
||||||
|
var length = data.readUInt32BE(idx);
|
||||||
|
idx += 4;
|
||||||
|
|
||||||
|
// chunk type
|
||||||
|
var type = data.readUInt32BE(idx),
|
||||||
|
ancillary = !!(data[idx] & 0x20), // or critical
|
||||||
|
priv = !!(data[idx+1] & 0x20), // or public
|
||||||
|
safeToCopy = !!(data[idx+3] & 0x20), // or unsafe
|
||||||
|
name = '';
|
||||||
|
for (var i = 0; i < 4; i++)
|
||||||
|
name += String.fromCharCode(data[idx+i]);
|
||||||
|
idx += 4;
|
||||||
|
|
||||||
|
// console.log('chunk ', name, length);
|
||||||
|
|
||||||
|
// calc CRC (of chunk type and content)
|
||||||
|
var calcCrc = crc32(data.slice(idx - 4, idx + length)),
|
||||||
|
content = data.slice(idx, idx + length);
|
||||||
|
idx += length;
|
||||||
|
|
||||||
|
// read CRC
|
||||||
|
var fileCrc = data.readInt32BE(idx);
|
||||||
|
idx += 4;
|
||||||
|
|
||||||
|
// and check CRC
|
||||||
|
if (this._options.checkCRC && calcCrc != fileCrc)
|
||||||
|
throw new Error('Crc error');
|
||||||
|
|
||||||
|
|
||||||
|
if (!this._hasIHDR && type != TYPE_IHDR)
|
||||||
|
throw new Error('Expected IHDR on beggining');
|
||||||
|
|
||||||
|
if (this._chunks[type]) {
|
||||||
|
this._chunks[type](content);
|
||||||
|
|
||||||
|
} else if (!ancillary)
|
||||||
|
throw new Error('Unsupported critical chunk type ' + name);
|
||||||
|
|
||||||
|
return idx;
|
||||||
|
};
|
||||||
|
|
||||||
|
Parser.prototype._packChunk = function(type, data) {
|
||||||
|
|
||||||
|
var len = (data ? data.length : 0),
|
||||||
|
buf = new Buffer(len + 12);
|
||||||
|
|
||||||
|
buf.writeUInt32BE(len, 0);
|
||||||
|
buf.writeUInt32BE(type, 4);
|
||||||
|
|
||||||
|
if (data) data.copy(buf, 8);
|
||||||
|
|
||||||
|
buf.writeInt32BE(crc32(buf.slice(4, buf.length - 4)), buf.length - 4);
|
||||||
|
return buf;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
Parser.prototype._parseIHDR = function(data) {
|
||||||
|
|
||||||
|
var width = data.readUInt32BE(0),
|
||||||
|
height = data.readUInt32BE(4),
|
||||||
|
depth = data[8],
|
||||||
|
colorType = data[9], // 1 palette, 2 color, 4 alpha
|
||||||
|
compr = data[10],
|
||||||
|
filter = data[11],
|
||||||
|
interlace = data[12];
|
||||||
|
|
||||||
|
// console.log(' width', width, 'height', height,
|
||||||
|
// 'depth', depth, 'colorType', colorType,
|
||||||
|
// 'compr', compr, 'filter', filter, 'interlace', interlace
|
||||||
|
// );
|
||||||
|
|
||||||
|
if (depth != 8)
|
||||||
|
throw new Error('Unsupported bit depth');
|
||||||
|
if (colorType != 6)
|
||||||
|
throw new Error('Unsupported color type');
|
||||||
|
if (interlace != 0)
|
||||||
|
throw new Error('Unsupported interlace method');
|
||||||
|
|
||||||
|
this._compress.prepareInflate(compr);
|
||||||
|
this._filter.prepare(width, height, filter);
|
||||||
|
|
||||||
|
this._hasIHDR = true;
|
||||||
|
|
||||||
|
this.emit('metadata', width, height);
|
||||||
|
};
|
||||||
|
|
||||||
|
Parser.prototype._packIHDR = function(width, height) {
|
||||||
|
|
||||||
|
var buf = new Buffer(13);
|
||||||
|
buf.writeUInt32BE(width, 0);
|
||||||
|
buf.writeUInt32BE(height, 4);
|
||||||
|
buf[8] = 8;
|
||||||
|
buf[9] = 6; // colorType
|
||||||
|
buf[10] = 0; // compression
|
||||||
|
buf[11] = 0; // filter
|
||||||
|
buf[12] = 0; // interlace
|
||||||
|
|
||||||
|
return this._packChunk(TYPE_IHDR, buf);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
Parser.prototype._parseIDAT = function(data) {
|
||||||
|
this._compress.writeInflate(data);
|
||||||
|
};
|
||||||
|
|
||||||
|
Parser.prototype._packIDAT = function(data) {
|
||||||
|
return this._packChunk(TYPE_IDAT, data);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
Parser.prototype._parseIEND = function(data) {
|
||||||
|
|
||||||
|
// no more data to inflate
|
||||||
|
this._compress.endInflate();
|
||||||
|
|
||||||
|
this._hasIEND = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
Parser.prototype._packIEND = function() {
|
||||||
|
return this._packChunk(TYPE_IEND, null);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// prepare crc table as in PNG Specification
|
||||||
|
var crcTable = [];
|
||||||
|
|
||||||
|
for (var i = 0; i < 256; i++) {
|
||||||
|
var c = i;
|
||||||
|
for (var j = 0; j < 8; j++) {
|
||||||
|
if (c & 1) {
|
||||||
|
c = 0xedb88320 ^ (c >>> 1);
|
||||||
|
} else {
|
||||||
|
c = c >>> 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
crcTable[i] = c;
|
||||||
|
}
|
||||||
|
|
||||||
|
function crc32(buf) {
|
||||||
|
|
||||||
|
var crc = -1;
|
||||||
|
for (var i = 0; i < buf.length; i++) {
|
||||||
|
crc = crcTable[(crc ^ buf[i]) & 0xff] ^ (crc >>> 8);
|
||||||
|
}
|
||||||
|
return crc ^ -1;
|
||||||
|
}
|
||||||
|
|
||||||
94
lib/png.js
Normal file
94
lib/png.js
Normal file
|
|
@ -0,0 +1,94 @@
|
||||||
|
// Copyright (c) 2012 Kuba Niegowski
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
|
||||||
|
var util = require('util'),
|
||||||
|
Parser = require('./parser');
|
||||||
|
|
||||||
|
|
||||||
|
var PNG = exports.PNG = function(options) {
|
||||||
|
Parser.call(this, options);
|
||||||
|
|
||||||
|
this.width = options.width || 0;
|
||||||
|
this.height = options.height || 0;
|
||||||
|
|
||||||
|
this.data = this.width > 0 && this.height > 0
|
||||||
|
? new Buffer(4 * this.width * this.height) : null;
|
||||||
|
|
||||||
|
this.on('metadata', this._metadata.bind(this));
|
||||||
|
|
||||||
|
this.readable = this.writable = true;
|
||||||
|
this._buffers = [];
|
||||||
|
this._buffLen = 0;
|
||||||
|
|
||||||
|
this.on('parsed', function(data) {
|
||||||
|
this.data = data;
|
||||||
|
}.bind(this));
|
||||||
|
};
|
||||||
|
util.inherits(PNG, Parser);
|
||||||
|
|
||||||
|
|
||||||
|
PNG.prototype.pack = function() {
|
||||||
|
this._pack(this.width, this.height, this.data);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
PNG.prototype.parse = function(data, callback) {
|
||||||
|
|
||||||
|
if (callback) {
|
||||||
|
var onParsed = null, onError = null;
|
||||||
|
|
||||||
|
this.once('parsed', onParsed = function(data) {
|
||||||
|
this.removeListener('error', onError);
|
||||||
|
|
||||||
|
this.data = data;
|
||||||
|
callback(null, this);
|
||||||
|
|
||||||
|
}.bind(this));
|
||||||
|
|
||||||
|
this.once('error', onError = function(err) {
|
||||||
|
this.removeListener('parsed', onParsed);
|
||||||
|
|
||||||
|
callback(err, null);
|
||||||
|
}.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
this._parse(data);
|
||||||
|
};
|
||||||
|
|
||||||
|
PNG.prototype.write = function(data) {
|
||||||
|
this._buffers.push(data);
|
||||||
|
this._buffLen += data.length;
|
||||||
|
};
|
||||||
|
|
||||||
|
PNG.prototype.end = function(data) {
|
||||||
|
if (data) this.write(data);
|
||||||
|
this._parse(Buffer.concat(this._buffers, this._buffLen));
|
||||||
|
this._buffers = [];
|
||||||
|
this._buffLen = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
PNG.prototype._metadata = function(width, height) {
|
||||||
|
this.width = width;
|
||||||
|
this.height = height;
|
||||||
|
this.data = null;
|
||||||
|
};
|
||||||
15
package.json
Normal file
15
package.json
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
"name": "pngjs",
|
||||||
|
"version": "0.1.0-alpha",
|
||||||
|
"description": "Simple PNG encoder/decoder",
|
||||||
|
"author": "Kuba Niegowski",
|
||||||
|
"contributors": [],
|
||||||
|
"homepage": "https://github.com/niegowski/node-pngjs/",
|
||||||
|
"keywords" : [
|
||||||
|
"png"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "0.8.x"
|
||||||
|
},
|
||||||
|
"main": "./lib/png.js"
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue