diff --git a/lib/packer-async.js b/lib/packer-async.js index 7f029e5..89324ca 100644 --- a/lib/packer-async.js +++ b/lib/packer-async.js @@ -3,16 +3,12 @@ var util = require('util'); var Stream = require('stream'); var zlib = require('zlib'); -var filter = require('./filter-pack'); -var CrcStream = require('./crc'); var constants = require('./constants'); -var bitPacker = require('./bitpacker'); +var Packer = require('./packer'); var PackerAsync = module.exports = function(options) { Stream.call(this); - this._options = options; - options.deflateChunkSize = options.deflateChunkSize || 32 * 1024; options.deflateLevel = options.deflateLevel != null ? options.deflateLevel : 9; options.deflateStrategy = options.deflateStrategy != null ? options.deflateStrategy : 3; @@ -28,6 +24,13 @@ var PackerAsync = module.exports = function(options) { throw new Error('option bit depth:' + options.bitDepth + ' is not supported at present'); } + this._packer = new Packer(options); + this._deflate = options.deflateFactory({ + chunkSize: options.deflateChunkSize, + level: options.deflateLevel, + strategy: options.deflateStrategy + }); + this.readable = true; }; util.inherits(PackerAsync, Stream); @@ -36,79 +39,25 @@ util.inherits(PackerAsync, Stream); PackerAsync.prototype.pack = function(data, width, height, gamma) { // Signature this.emit('data', new Buffer(constants.PNG_SIGNATURE)); - this.emit('data', this._packIHDR(width, height, this._options.bitDepth, this._options.colorType)); + this.emit('data', this._packer.packIHDR(width, height)); if (gamma) { - this.emit('data', this._packGAMA(gamma)); + this.emit('data', this._packer.packGAMA(gamma)); } - // convert to correct format for filtering (e.g. right bpp and bit depth) - var packedData = bitPacker(data, width, height, this._options); - - // filter pixel data - var bpp = constants.COLORTYPE_TO_BPP_MAP[this._options.colorType]; - var filteredData = filter(packedData, width, height, this._options, bpp); + var filteredData = this._packer.filterData(data, width, height); // compress it - var deflate = this._options.deflateFactory({ - chunkSize: this._options.deflateChunkSize, - level: this._options.deflateLevel, - strategy: this._options.deflateStrategy - }); - deflate.on('error', this.emit.bind(this, 'error')); + this._deflate.on('error', this.emit.bind(this, 'error')); - deflate.on('data', function(compressedData) { - this.emit('data', this._packIDAT(compressedData)); + this._deflate.on('data', function(compressedData) { + this.emit('data', this._packer.packIDAT(compressedData)); }.bind(this)); - deflate.on('end', function() { - this.emit('data', this._packIEND()); + this._deflate.on('end', function() { + this.emit('data', this._packer.packIEND()); this.emit('end'); }.bind(this)); - deflate.end(filteredData); -}; - -PackerAsync.prototype._packChunk = function(type, data) { - - var len = (data ? data.length : 0); - var buf = new Buffer(len + 12); - - buf.writeUInt32BE(len, 0); - buf.writeUInt32BE(type, 4); - - if (data) { - data.copy(buf, 8); - } - - buf.writeInt32BE(CrcStream.crc32(buf.slice(4, buf.length - 4)), buf.length - 4); - return buf; -}; - -PackerAsync.prototype._packGAMA = function(gamma) { - var buf = new Buffer(4); - buf.writeUInt32BE(Math.floor(gamma * constants.GAMMA_DIVISION), 0); - return this._packChunk(constants.TYPE_gAMA, buf); -}; - -PackerAsync.prototype._packIHDR = function(width, height, bitDepth, colorType) { - - var buf = new Buffer(13); - buf.writeUInt32BE(width, 0); - buf.writeUInt32BE(height, 4); - buf[8] = bitDepth; // Bit depth - buf[9] = colorType; // colorType - buf[10] = 0; // compression - buf[11] = 0; // filter - buf[12] = 0; // interlace - - return this._packChunk(constants.TYPE_IHDR, buf); -}; - -PackerAsync.prototype._packIDAT = function(data) { - return this._packChunk(constants.TYPE_IDAT, data); -}; - -PackerAsync.prototype._packIEND = function() { - return this._packChunk(constants.TYPE_IEND, null); + this._deflate.end(filteredData); }; diff --git a/lib/packer-sync.js b/lib/packer-sync.js index d627a6a..5673dbc 100644 --- a/lib/packer-sync.js +++ b/lib/packer-sync.js @@ -1,5 +1,53 @@ 'use strict'; -module.exports = function(buffer, options) { - return null; +var zlib = require('zlib'); +var constants = require('./constants'); +var Packer = require('./packer'); + +module.exports = function(metaData, options) { + options = options || {}; + options.deflateChunkSize = options.deflateChunkSize || 32 * 1024; + options.deflateLevel = options.deflateLevel != null ? options.deflateLevel : 9; + options.deflateStrategy = options.deflateStrategy != null ? options.deflateStrategy : 3; + options.inputHasAlpha = options.inputHasAlpha != null ? options.inputHasAlpha : true; + options.deflateFactory = options.deflateFactory || zlib.createDeflate; + options.bitDepth = options.bitDepth || 8; + options.colorType = (typeof options.colorType === 'number') ? options.colorType : constants.COLORTYPE_COLOR_ALPHA; + + if (options.colorType !== constants.COLORTYPE_COLOR && options.colorType !== constants.COLORTYPE_COLOR_ALPHA) { + throw new Error('option color type:' + options.colorType + ' is not supported at present'); + } + if (options.bitDepth !== 8) { + throw new Error('option bit depth:' + options.bitDepth + ' is not supported at present'); + } + + var packer = new Packer(options); + + var chunks = []; + + // Signature + chunks.push(new Buffer(constants.PNG_SIGNATURE)); + + // Header + chunks.push(packer.packIHDR(metaData.width, metaData.height)); + + if (metaData.gamma) { + chunks.push(packer.packGAMA(metaData.gamma)); + } + + var filteredData = packer.filterData(metaData.data, metaData.width, metaData.height); + + // compress it + var compressedData = zlib.deflateSync(filteredData); + filteredData = null; + + if (!compressedData || !compressedData.length) { + throw new Error('bad png - invalid compressed data response'); + } + chunks.push(packer.packIDAT(compressedData)); + + // End + chunks.push(packer.packIEND()); + + return Buffer.concat(chunks); }; diff --git a/lib/packer.js b/lib/packer.js new file mode 100644 index 0000000..95334e3 --- /dev/null +++ b/lib/packer.js @@ -0,0 +1,66 @@ +/** + * Created by gusc on 15.3.12. + */ + +var constants = require('./constants'); +var CrcStream = require('./crc'); +var bitPacker = require('./bitpacker'); +var filter = require('./filter-pack'); + +var Packer = module.exports = function(options) { + this._options = options; +}; + +Packer.prototype.filterData = function(data, width, height){ + // convert to correct format for filtering (e.g. right bpp and bit depth) + var packedData = bitPacker(data, width, height, this._options); + + // filter pixel data + var bpp = constants.COLORTYPE_TO_BPP_MAP[this._options.colorType]; + var filteredData = filter(packedData, width, height, this._options, bpp); + return filteredData; +}; + +Packer.prototype._packChunk = function(type, data) { + + var len = (data ? data.length : 0); + var buf = new Buffer(len + 12); + + buf.writeUInt32BE(len, 0); + buf.writeUInt32BE(type, 4); + + if (data) { + data.copy(buf, 8); + } + + buf.writeInt32BE(CrcStream.crc32(buf.slice(4, buf.length - 4)), buf.length - 4); + return buf; +}; + +Packer.prototype.packGAMA = function(gamma) { + var buf = new Buffer(4); + buf.writeUInt32BE(Math.floor(gamma * constants.GAMMA_DIVISION), 0); + return this._packChunk(constants.TYPE_gAMA, buf); +}; + +Packer.prototype.packIHDR = function(width, height) { + + var buf = new Buffer(13); + buf.writeUInt32BE(width, 0); + buf.writeUInt32BE(height, 4); + buf[8] = this._options.bitDepth; // Bit depth + buf[9] = this._options.colorType; // colorType + buf[10] = 0; // compression + buf[11] = 0; // filter + buf[12] = 0; // interlace + + return this._packChunk(constants.TYPE_IHDR, buf); +}; + +Packer.prototype.packIDAT = function(data) { + return this._packChunk(constants.TYPE_IDAT, data); +}; + +Packer.prototype.packIEND = function() { + return this._packChunk(constants.TYPE_IEND, null); +}; \ No newline at end of file