diff --git a/lib/parser-async.js b/lib/parser-async.js index ef36667..e3e1a6b 100644 --- a/lib/parser-async.js +++ b/lib/parser-async.js @@ -46,12 +46,48 @@ ParserAsync.prototype._handleError = function(err) { ParserAsync.prototype._inflateData = function(data) { if (!this._inflate) { - this._inflate = zlib.createInflate(); + if (this._bitmapInfo.interlace) { + this._inflate = zlib.createInflate(); - this._inflate.on('error', this.emit.bind(this, 'error')); - this._filter.on('complete', this._complete.bind(this)); + this._inflate.on('error', this.emit.bind(this, 'error')); + this._filter.on('complete', this._complete.bind(this)); - this._inflate.pipe(this._filter); + this._inflate.pipe(this._filter); + } else { + var rowSize = ((this._bitmapInfo.width * this._bitmapInfo.bpp * this._bitmapInfo.depth + 7) >> 3) + 1; + var imageSize = rowSize * this._bitmapInfo.height; + var chunkSize = Math.max(imageSize, zlib.Z_MIN_CHUNK); + + this._inflate = zlib.createInflate({ chunkSize: chunkSize }); + var leftToInflate = imageSize; + + var emitError = this.emit.bind(this, 'error'); + this._inflate.on('error', function(err) { + if (!leftToInflate) { + return; + } + + emitError(err); + }); + this._filter.on('complete', this._complete.bind(this)); + + var filterWrite = this._filter.write.bind(this._filter); + this._inflate.on('data', function(chunk) { + if (!leftToInflate) { + return; + } + + if (chunk.length > leftToInflate) { + chunk = chunk.slice(0, leftToInflate); + } + + leftToInflate -= chunk.length; + + filterWrite(chunk); + }); + + this._inflate.on('end', this._filter.end.bind(this._filter)); + } } this._inflate.write(data); }; diff --git a/lib/parser-sync.js b/lib/parser-sync.js index 5cba0ae..a49e386 100644 --- a/lib/parser-sync.js +++ b/lib/parser-sync.js @@ -2,6 +2,7 @@ var hasSyncZlib = true; var zlib = require('zlib'); +var inflateSync = require('./sync-inflate'); var SyncReader = require('./sync-reader'); var FilterSync = require('./filter-parse-sync'); var Parser = require('./parser'); @@ -66,7 +67,14 @@ module.exports = function(buffer, options) { var inflateData = Buffer.concat(inflateDataList); inflateDataList.length = 0; - var inflatedData = zlib.inflateSync(inflateData); + var inflatedData; + if (metaData.interlace) { + inflatedData = zlib.inflateSync(inflateData); + } else { + var rowSize = ((metaData.width * metaData.bpp * metaData.depth + 7) >> 3) + 1; + var imageSize = rowSize * metaData.height; + inflatedData = inflateSync(inflateData, { chunkSize: imageSize, maxLength: imageSize }); + } inflateData = null; if (!inflatedData || !inflatedData.length) { diff --git a/lib/parser.js b/lib/parser.js index ebe5f57..b730c8f 100644 --- a/lib/parser.js +++ b/lib/parser.js @@ -32,7 +32,6 @@ var Parser = module.exports = function(options, dependencies) { this.palette = dependencies.palette; this.parsed = dependencies.parsed; this.inflateData = dependencies.inflateData; - this.inflateData = dependencies.inflateData; this.finished = dependencies.finished; }; diff --git a/lib/sync-inflate.js b/lib/sync-inflate.js new file mode 100644 index 0000000..26c5b2f --- /dev/null +++ b/lib/sync-inflate.js @@ -0,0 +1,155 @@ +'use strict'; + +var assert = require('assert').ok; +var zlib = require('zlib'); +var util = require('util'); + +var kMaxLength = require('buffer').kMaxLength; + +function Inflate(opts) { + if (!(this instanceof Inflate)) { + return new Inflate(opts); + } + + if (opts && opts.chunkSize < zlib.Z_MIN_CHUNK) { + opts.chunkSize = zlib.Z_MIN_CHUNK; + } + + zlib.Inflate.call(this, opts); + + if (opts && opts.maxLength != null) { + this._maxLength = opts.maxLength; + } +} + +function createInflate(opts) { + return new Inflate(opts); +} + +function _close(engine, callback) { + if (callback) { + process.nextTick(callback); + } + + // Caller may invoke .close after a zlib error (which will null _handle). + if (!engine._handle) { + return; + } + + engine._handle.close(); + engine._handle = null; +} + +Inflate.prototype._processChunk = function(chunk, flushFlag, asyncCb) { + if (typeof asyncCb === 'function') { + return zlib.Inflate._processChunk.call(this, chunk, flushFlag, asyncCb); + } + + var self = this; + + var availInBefore = chunk && chunk.length; + var availOutBefore = this._chunkSize - this._offset; + var leftToInflate = this._maxLength; + var inOff = 0; + + var buffers = []; + var nread = 0; + + var error; + this.on('error', function(err) { + error = err; + }); + + function handleChunk(availInAfter, availOutAfter) { + if (self._hadError) { + return; + } + + var have = availOutBefore - availOutAfter; + assert(have >= 0, 'have should not go down'); + + if (have > 0) { + var out = self._buffer.slice(self._offset, self._offset + have); + self._offset += have; + + if (out.length > leftToInflate) { + out = out.slice(0, leftToInflate); + } + + buffers.push(out); + nread += out.length; + leftToInflate -= out.length; + + if (leftToInflate === 0) { + return false; + } + } + + if (availOutAfter === 0 || self._offset >= self._chunkSize) { + availOutBefore = self._chunkSize; + self._offset = 0; + self._buffer = Buffer.allocUnsafe(self._chunkSize); + } + + if (availOutAfter === 0) { + inOff += (availInBefore - availInAfter); + availInBefore = availInAfter; + + return true; + } + + return false; + } + + assert(this._handle, 'zlib binding closed'); + do { + var res = this._handle.writeSync(flushFlag, + chunk, // in + inOff, // in_off + availInBefore, // in_len + this._buffer, // out + this._offset, //out_off + availOutBefore); // out_len + } while (!this._hadError && handleChunk(res[0], res[1])); + + if (this._hadError) { + throw error; + } + + if (nread >= kMaxLength) { + _close(this); + throw new RangeError('Cannot create final Buffer. It would be larger than 0x' + kMaxLength.toString(16) + ' bytes'); + } + + var buf = Buffer.concat(buffers, nread); + _close(this); + + return buf; +}; + +util.inherits(Inflate, zlib.Inflate); + +function zlibBufferSync(engine, buffer) { + if (typeof buffer === 'string') { + buffer = Buffer.from(buffer); + } + if (!(buffer instanceof Buffer)) { + throw new TypeError('Not a string or buffer'); + } + + var flushFlag = engine._finishFlushFlag; + if (flushFlag == null) { + flushFlag = zlib.Z_FINISH; + } + + return engine._processChunk(buffer, flushFlag); +} + +function inflateSync(buffer, opts) { + return zlibBufferSync(new Inflate(opts), buffer); +} + +module.exports = exports = inflateSync; +exports.Inflate = Inflate; +exports.createInflate = createInflate; +exports.inflateSync = inflateSync;