commit 413c967aae6548031fdfb466b8ed75d3b08e70da Author: Kuba Niegowski Date: Sun Aug 19 00:44:48 2012 +0200 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..24cd046 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +node_modules +*.png diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..04d21fe --- /dev/null +++ b/LICENSE @@ -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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..5449471 --- /dev/null +++ b/README.md @@ -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) diff --git a/examples/test.js b/examples/test.js new file mode 100755 index 0000000..785f66c --- /dev/null +++ b/examples/test.js @@ -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(); + }); +}); diff --git a/examples/test2.js b/examples/test2.js new file mode 100755 index 0000000..fe46892 --- /dev/null +++ b/examples/test2.js @@ -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); diff --git a/lib/compress.js b/lib/compress.js new file mode 100644 index 0000000..f110de6 --- /dev/null +++ b/lib/compress.js @@ -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; +}; diff --git a/lib/filter.js b/lib/filter.js new file mode 100644 index 0000000..379a65f --- /dev/null +++ b/lib/filter.js @@ -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; +}; diff --git a/lib/parser.js b/lib/parser.js new file mode 100644 index 0000000..ed7b3bf --- /dev/null +++ b/lib/parser.js @@ -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; +} + diff --git a/lib/png.js b/lib/png.js new file mode 100644 index 0000000..0d1e624 --- /dev/null +++ b/lib/png.js @@ -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; +}; diff --git a/package.json b/package.json new file mode 100644 index 0000000..0e33ee4 --- /dev/null +++ b/package.json @@ -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" +}