mirror of
https://github.com/danbulant/pngjs
synced 2026-07-06 03:30:44 +00:00
new experimental streams
This commit is contained in:
parent
11a02b6801
commit
43a7c3dc4e
6 changed files with 484 additions and 362 deletions
|
|
@ -1,94 +0,0 @@
|
||||||
// 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;
|
|
||||||
};
|
|
||||||
38
lib/constants.js
Normal file
38
lib/constants.js
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
// 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';
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
|
||||||
|
PNG_SIGNATURE: [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a],
|
||||||
|
|
||||||
|
TYPE_IHDR: 0x49484452,
|
||||||
|
TYPE_IEND: 0x49454e44,
|
||||||
|
TYPE_IDAT: 0x49444154,
|
||||||
|
TYPE_PLTE: 0x504c5445,
|
||||||
|
TYPE_tRNS: 0x74524e53,
|
||||||
|
TYPE_gAMA: 0x67414d41,
|
||||||
|
|
||||||
|
COLOR_PALETTE: 1,
|
||||||
|
COLOR_COLOR: 2,
|
||||||
|
COLOR_ALPHA: 4
|
||||||
|
};
|
||||||
45
lib/helpers.js
Normal file
45
lib/helpers.js
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
// 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 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.crc32 = function(buf) {
|
||||||
|
|
||||||
|
var crc = -1;
|
||||||
|
for (var i = 0; i < buf.length; i++) {
|
||||||
|
crc = crcTable[(crc ^ buf[i]) & 0xff] ^ (crc >>> 8);
|
||||||
|
}
|
||||||
|
return crc ^ -1;
|
||||||
|
};
|
||||||
98
lib/packer.js
Normal file
98
lib/packer.js
Normal file
|
|
@ -0,0 +1,98 @@
|
||||||
|
// 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 Parser = module.exports = function(options) {
|
||||||
|
Stream.call(this);
|
||||||
|
|
||||||
|
this._options = options;
|
||||||
|
|
||||||
|
// this._compress = new Compress(options);
|
||||||
|
// this._compress.on('error', this.emit.bind(this, 'error'));
|
||||||
|
// this._compress.on('deflated', this._packData.bind(this));
|
||||||
|
};
|
||||||
|
util.inherits(Parser, Stream);
|
||||||
|
|
||||||
|
|
||||||
|
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._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._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._packIDAT = function(data) {
|
||||||
|
return this._packChunk(TYPE_IDAT, data);
|
||||||
|
};
|
||||||
|
|
||||||
|
Parser.prototype._packIEND = function() {
|
||||||
|
return this._packChunk(TYPE_IEND, null);
|
||||||
|
};
|
||||||
516
lib/parser.js
516
lib/parser.js
|
|
@ -23,24 +23,12 @@
|
||||||
|
|
||||||
var util = require('util'),
|
var util = require('util'),
|
||||||
Stream = require('stream'),
|
Stream = require('stream'),
|
||||||
Compress = require('./compress'),
|
zlib = require('zlib'),
|
||||||
|
helpers = require('./helpers'),
|
||||||
|
constants = require('./constants'),
|
||||||
Filter = require('./filter');
|
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 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) {
|
var Parser = module.exports = function(options) {
|
||||||
Stream.call(this);
|
Stream.call(this);
|
||||||
|
|
||||||
|
|
@ -50,79 +38,277 @@ var Parser = module.exports = function(options) {
|
||||||
this._hasIHDR = false;
|
this._hasIHDR = false;
|
||||||
this._hasIEND = false;
|
this._hasIEND = false;
|
||||||
|
|
||||||
// input flags
|
this._stream = new ReadStream();
|
||||||
|
this._inflate = null;
|
||||||
|
|
||||||
|
// input flags/metadata
|
||||||
this._palette = [];
|
this._palette = [];
|
||||||
this._colorType = 0;
|
this._colorType = 0;
|
||||||
|
this._width = 0;
|
||||||
|
this._height = 0;
|
||||||
|
|
||||||
this._chunks = {};
|
this._chunks = {};
|
||||||
this._chunks[TYPE_IHDR] = this._parseIHDR.bind(this);
|
this._chunks[constants.TYPE_IHDR] = this._handleIHDR.bind(this);
|
||||||
this._chunks[TYPE_IEND] = this._parseIEND.bind(this);
|
this._chunks[constants.TYPE_IEND] = this._handleIEND.bind(this);
|
||||||
this._chunks[TYPE_IDAT] = this._parseIDAT.bind(this);
|
this._chunks[constants.TYPE_IDAT] = this._handleIDAT.bind(this);
|
||||||
this._chunks[TYPE_PLTE] = this._parsePLTE.bind(this);
|
this._chunks[constants.TYPE_PLTE] = this._handlePLTE.bind(this);
|
||||||
this._chunks[TYPE_tRNS] = this._parseTRNS.bind(this);
|
this._chunks[constants.TYPE_tRNS] = this._handleTRNS.bind(this);
|
||||||
this._chunks[TYPE_gAMA] = this._parseGAMA.bind(this);
|
this._chunks[constants.TYPE_gAMA] = this._handleGAMA.bind(this);
|
||||||
|
|
||||||
this._compress = new Compress(options);
|
this.writable = true;
|
||||||
this._filter = new Filter(options);
|
|
||||||
|
|
||||||
this._initCompress();
|
this.on('error', this._handleError.bind(this));
|
||||||
|
|
||||||
|
this._handleSignature();
|
||||||
};
|
};
|
||||||
util.inherits(Parser, Stream);
|
util.inherits(Parser, Stream);
|
||||||
|
|
||||||
Parser.prototype._initCompress = function() {
|
|
||||||
|
|
||||||
this._compress.on('error', this.emit.bind(this, 'error'));
|
Parser.prototype.write = function(data) {
|
||||||
|
this._stream.write(data);
|
||||||
this._compress.on('deflated', this._packData.bind(this));
|
|
||||||
this._compress.on('inflated', this._unfilter.bind(this));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Parser.prototype._parse = function(data) {
|
Parser.prototype.end = function(data) {
|
||||||
|
this._stream.end(data);
|
||||||
|
};
|
||||||
|
|
||||||
var idx = 0;
|
Parser.prototype._handleError = function() {
|
||||||
|
|
||||||
try {
|
this.writable = false;
|
||||||
// check PNG file signature
|
this._stream.destroy();
|
||||||
while (idx < signature.length) {
|
|
||||||
if (data[idx] != signature[idx]) {
|
|
||||||
throw new Error('Invalid file signature');
|
|
||||||
}
|
|
||||||
idx++;
|
|
||||||
}
|
|
||||||
//console.log('Signature is ok');
|
|
||||||
|
|
||||||
// iterate chunks
|
if (this._inflate)
|
||||||
while (idx < data.length) {
|
this._inflate.destroy();
|
||||||
idx = this._parseChunk(data, idx);
|
};
|
||||||
|
|
||||||
|
Parser.prototype._handleSignature = function() {
|
||||||
|
this._stream.read(constants.PNG_SIGNATURE.length,
|
||||||
|
this._parseSignature.bind(this)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
Parser.prototype._parseSignature = function(data) {
|
||||||
|
|
||||||
|
var signature = constants.PNG_SIGNATURE;
|
||||||
|
|
||||||
|
for (var i = 0; i < signature.length; i++)
|
||||||
|
if (data[i] != signature[i]) {
|
||||||
|
this.emit('error', new Error('Invalid file signature'));
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch(err) {
|
this._stream.read(8, this._parseChunkBegin.bind(this));
|
||||||
this.emit('error', err);
|
};
|
||||||
|
|
||||||
|
Parser.prototype._parseChunkBegin = function(data) {
|
||||||
|
|
||||||
|
// chunk content length
|
||||||
|
var length = data.readUInt32BE(0);
|
||||||
|
|
||||||
|
// chunk type
|
||||||
|
var type = data.readUInt32BE(4),
|
||||||
|
name = '';
|
||||||
|
for (var i = 4; i < 8; i++)
|
||||||
|
name += String.fromCharCode(data[i]);
|
||||||
|
|
||||||
|
// chunk flags
|
||||||
|
var ancillary = !!(data[4] & 0x20), // or critical
|
||||||
|
priv = !!(data[5] & 0x20), // or public
|
||||||
|
safeToCopy = !!(data[7] & 0x20); // or unsafe
|
||||||
|
|
||||||
|
if (!this._hasIHDR && type != constants.TYPE_IHDR) {
|
||||||
|
this.emit('error', new Error('Expected IHDR on beggining'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._chunks[type]) {
|
||||||
|
return this._chunks[type](length);
|
||||||
|
|
||||||
|
} else if (!ancillary) {
|
||||||
|
this.emit('error', new Error('Unsupported critical chunk type ' + name));
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Parser.prototype._pack = function(width, height, data) {
|
Parser.prototype._handleChunkEnd = function() {
|
||||||
|
this._stream.read(4, this._parseChunkEnd.bind(this));
|
||||||
// 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) {
|
Parser.prototype._parseChunkEnd = function(data) {
|
||||||
|
|
||||||
// console.log('deflate', data.length);
|
var fileCrc = data.readInt32BE(0);
|
||||||
|
|
||||||
this.emit('data', this._packIDAT(data));
|
// // calc CRC (of chunk type and content)
|
||||||
this.emit('data', this._packIEND());
|
// var calcCrc = helpers.crc32(data.slice(idx - 4, idx + length)),
|
||||||
this.emit('end');
|
// 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');
|
||||||
|
|
||||||
|
// TODO: calc crc on stream
|
||||||
|
|
||||||
|
if (this._hasIEND) {
|
||||||
|
this._stream.destroy();
|
||||||
|
|
||||||
|
} else {
|
||||||
|
this._stream.read(8, this._parseChunkBegin.bind(this));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
Parser.prototype._handleIHDR = function(length) {
|
||||||
|
this._stream.read(length, this._parseIHDR.bind(this));
|
||||||
|
};
|
||||||
|
Parser.prototype._parseIHDR = function(data) {
|
||||||
|
|
||||||
|
var width = data.readUInt32BE(0),
|
||||||
|
height = data.readUInt32BE(4),
|
||||||
|
depth = data[8],
|
||||||
|
colorType = data[9], // bits: 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) {
|
||||||
|
this.emit('error', new Error('Unsupported bit depth ' + depth));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (compr != 0) {
|
||||||
|
this.emit('error', new Error('Unsupported compression method'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (filter != 0) {
|
||||||
|
this.emit('error', new Error('Unsupported filter method'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (interlace != 0) {
|
||||||
|
this.emit('error', new Error('Unsupported interlace method'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._colorType = colorType;
|
||||||
|
this._width = width;
|
||||||
|
this._height = height;
|
||||||
|
this._hasIHDR = true;
|
||||||
|
|
||||||
|
this.emit('metadata', {
|
||||||
|
width: width,
|
||||||
|
height: height,
|
||||||
|
palette: !!(colorType & constants.COLOR_PALETTE),
|
||||||
|
color: !!(colorType & constants.COLOR_COLOR),
|
||||||
|
alpha: !!(colorType & constants.COLOR_ALPHA)
|
||||||
|
});
|
||||||
|
|
||||||
|
this._handleChunkEnd();
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
Parser.prototype._handlePLTE = function(length) {
|
||||||
|
this._stream.read(length, this._parsePLTE.bind(this));
|
||||||
|
};
|
||||||
|
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
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._handleChunkEnd();
|
||||||
|
};
|
||||||
|
|
||||||
|
Parser.prototype._handleTRNS = function(length) {
|
||||||
|
this._stream.read(length, this._parseTRNS.bind(this));
|
||||||
|
};
|
||||||
|
Parser.prototype._parseTRNS = function(data) {
|
||||||
|
|
||||||
|
// palette
|
||||||
|
if (this._colorType == 3) {
|
||||||
|
if (this._palette.length == 0) {
|
||||||
|
this.emit('error', new Error('Transparency chunk must be after palette'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (data.length > this._palette.length) {
|
||||||
|
this.emit('error', new Error('More transparent colors than palette size'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
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
|
||||||
|
|
||||||
|
this._handleChunkEnd();
|
||||||
|
};
|
||||||
|
|
||||||
|
Parser.prototype._handleGAMA = function(length) {
|
||||||
|
this._stream.read(length, this._parseGAMA.bind(this));
|
||||||
|
};
|
||||||
|
Parser.prototype._parseGAMA = function(data) {
|
||||||
|
|
||||||
|
this.emit('gamma', data.readUInt32BE(0) / 100000);
|
||||||
|
|
||||||
|
this._handleChunkEnd();
|
||||||
|
};
|
||||||
|
|
||||||
|
Parser.prototype._handleIDAT = function(length) {
|
||||||
|
this._stream.read(-length, this._parseIDAT.bind(this, length));
|
||||||
|
};
|
||||||
|
Parser.prototype._parseIDAT = function(lenght, data) {
|
||||||
|
|
||||||
|
if (this._colorType == 3 && this._palette.length == 0)
|
||||||
|
throw new Error('Expected palette not found');
|
||||||
|
|
||||||
|
if (!this._inflate) {
|
||||||
|
this._inflate = zlib.createInflate();
|
||||||
|
|
||||||
|
this._inflate.on('error', this.emit.bind(this, 'error'));
|
||||||
|
//this._inflate.pipe(filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._inflate.write(data);
|
||||||
|
length -= data.length;
|
||||||
|
|
||||||
|
if (length > 0)
|
||||||
|
this._handleIDAT(length);
|
||||||
|
else
|
||||||
|
this._handleChunkEnd();
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
Parser.prototype._handleIEND = function(length) {
|
||||||
|
this._stream.read(length, this._parseIEND.bind(this));
|
||||||
|
};
|
||||||
|
Parser.prototype._parseIEND = function(data) {
|
||||||
|
|
||||||
|
// no more data to inflate
|
||||||
|
this._inflate.end();
|
||||||
|
|
||||||
|
this._hasIEND = true;
|
||||||
|
this._handleChunkEnd();
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Parser.prototype._unfilter = function(data) {
|
Parser.prototype._unfilter = function(data) {
|
||||||
|
|
||||||
// expand data to 32 bit depending on colorType
|
// expand data to 32 bit depending on colorType
|
||||||
|
|
@ -160,209 +346,3 @@ Parser.prototype._unfilter = function(data) {
|
||||||
|
|
||||||
this.emit('parsed', data);
|
this.emit('parsed', data);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
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);
|
|
||||||
// else
|
|
||||||
// console.log('Ignoring chunk', name, type.toString(16));
|
|
||||||
|
|
||||||
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 ' + depth);
|
|
||||||
if (interlace != 0)
|
|
||||||
throw new Error('Unsupported interlace method');
|
|
||||||
|
|
||||||
this._colorType = colorType;
|
|
||||||
|
|
||||||
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._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);
|
|
||||||
};
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
|
||||||
55
lib/readstream.js
Normal file
55
lib/readstream.js
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
// 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');
|
||||||
|
|
||||||
|
|
||||||
|
var ReadStream = module.exports = function() {
|
||||||
|
Stream.call(this);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
this.writeable = true;
|
||||||
|
};
|
||||||
|
util.inherits(ReadStream, Stream);
|
||||||
|
|
||||||
|
|
||||||
|
// length < 0 -> at most this length
|
||||||
|
ReadStream.prototype.read = function(length, callback) {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
ReadStream.prototype.destroy = function() {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
ReadStream.prototype.write = function(data) {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
ReadStream.prototype.end = function(data) {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
Loading…
Reference in a new issue