| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297 | 'use strict';var constants = require('./constants');var CrcCalculator = require('./crc');var Parser = module.exports = function(options, dependencies) {  this._options = options;  options.checkCRC = options.checkCRC !== false;  this._hasIHDR = false;  this._hasIEND = false;  this._emittedHeadersFinished = false;  // input flags/metadata  this._palette = [];  this._colorType = 0;  this._chunks = {};  this._chunks[constants.TYPE_IHDR] = this._handleIHDR.bind(this);  this._chunks[constants.TYPE_IEND] = this._handleIEND.bind(this);  this._chunks[constants.TYPE_IDAT] = this._handleIDAT.bind(this);  this._chunks[constants.TYPE_PLTE] = this._handlePLTE.bind(this);  this._chunks[constants.TYPE_tRNS] = this._handleTRNS.bind(this);  this._chunks[constants.TYPE_gAMA] = this._handleGAMA.bind(this);  this.read = dependencies.read;  this.error = dependencies.error;  this.metadata = dependencies.metadata;  this.gamma = dependencies.gamma;  this.transColor = dependencies.transColor;  this.palette = dependencies.palette;  this.parsed = dependencies.parsed;  this.inflateData = dependencies.inflateData;  this.finished = dependencies.finished;  this.simpleTransparency = dependencies.simpleTransparency;  this.headersFinished = dependencies.headersFinished || function() {};};Parser.prototype.start = function() {  this.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.error(new Error('Invalid file signature'));      return;    }  }  this.read(8, this._parseChunkBegin.bind(this));};Parser.prototype._parseChunkBegin = function(data) {  // chunk content length  var length = data.readUInt32BE(0);  // chunk type  var type = data.readUInt32BE(4);  var name = '';  for (var i = 4; i < 8; i++) {    name += String.fromCharCode(data[i]);  }  //console.log('chunk ', name, length);  // chunk flags  var ancillary = Boolean(data[4] & 0x20); // or critical  //    priv = Boolean(data[5] & 0x20), // or public  //    safeToCopy = Boolean(data[7] & 0x20); // or unsafe  if (!this._hasIHDR && type !== constants.TYPE_IHDR) {    this.error(new Error('Expected IHDR on beggining'));    return;  }  this._crc = new CrcCalculator();  this._crc.write(new Buffer(name));  if (this._chunks[type]) {    return this._chunks[type](length);  }  if (!ancillary) {    this.error(new Error('Unsupported critical chunk type ' + name));    return;  }  this.read(length + 4, this._skipChunk.bind(this));};Parser.prototype._skipChunk = function(/*data*/) {  this.read(8, this._parseChunkBegin.bind(this));};Parser.prototype._handleChunkEnd = function() {  this.read(4, this._parseChunkEnd.bind(this));};Parser.prototype._parseChunkEnd = function(data) {  var fileCrc = data.readInt32BE(0);  var calcCrc = this._crc.crc32();  // check CRC  if (this._options.checkCRC && calcCrc !== fileCrc) {    this.error(new Error('Crc error - ' + fileCrc + ' - ' + calcCrc));    return;  }  if (!this._hasIEND) {    this.read(8, this._parseChunkBegin.bind(this));  }};Parser.prototype._handleIHDR = function(length) {  this.read(length, this._parseIHDR.bind(this));};Parser.prototype._parseIHDR = function(data) {  this._crc.write(data);  var width = data.readUInt32BE(0);  var height = data.readUInt32BE(4);  var depth = data[8];  var colorType = data[9]; // bits: 1 palette, 2 color, 4 alpha  var compr = data[10];  var filter = data[11];  var interlace = data[12];  // console.log('    width', width, 'height', height,  //     'depth', depth, 'colorType', colorType,  //     'compr', compr, 'filter', filter, 'interlace', interlace  // );  if (depth !== 8 && depth !== 4 && depth !== 2 && depth !== 1 && depth !== 16) {    this.error(new Error('Unsupported bit depth ' + depth));    return;  }  if (!(colorType in constants.COLORTYPE_TO_BPP_MAP)) {    this.error(new Error('Unsupported color type'));    return;  }  if (compr !== 0) {    this.error(new Error('Unsupported compression method'));    return;  }  if (filter !== 0) {    this.error(new Error('Unsupported filter method'));    return;  }  if (interlace !== 0 && interlace !== 1) {    this.error(new Error('Unsupported interlace method'));    return;  }  this._colorType = colorType;  var bpp = constants.COLORTYPE_TO_BPP_MAP[this._colorType];  this._hasIHDR = true;  this.metadata({    width: width,    height: height,    depth: depth,    interlace: Boolean(interlace),    palette: Boolean(colorType & constants.COLORTYPE_PALETTE),    color: Boolean(colorType & constants.COLORTYPE_COLOR),    alpha: Boolean(colorType & constants.COLORTYPE_ALPHA),    bpp: bpp,    colorType: colorType  });  this._handleChunkEnd();};Parser.prototype._handlePLTE = function(length) {  this.read(length, this._parsePLTE.bind(this));};Parser.prototype._parsePLTE = function(data) {  this._crc.write(data);  var entries = Math.floor(data.length / 3);  // console.log('Palette:', entries);  for (var i = 0; i < entries; i++) {    this._palette.push([      data[i * 3],      data[i * 3 + 1],      data[i * 3 + 2],      0xff    ]);  }  this.palette(this._palette);  this._handleChunkEnd();};Parser.prototype._handleTRNS = function(length) {  this.simpleTransparency();  this.read(length, this._parseTRNS.bind(this));};Parser.prototype._parseTRNS = function(data) {  this._crc.write(data);  // palette  if (this._colorType === constants.COLORTYPE_PALETTE_COLOR) {    if (this._palette.length === 0) {      this.error(new Error('Transparency chunk must be after palette'));      return;    }    if (data.length > this._palette.length) {      this.error(new Error('More transparent colors than palette size'));      return;    }    for (var i = 0; i < data.length; i++) {      this._palette[i][3] = data[i];    }    this.palette(this._palette);  }  // for colorType 0 (grayscale) and 2 (rgb)  // there might be one gray/color defined as transparent  if (this._colorType === constants.COLORTYPE_GRAYSCALE) {    // grey, 2 bytes    this.transColor([data.readUInt16BE(0)]);  }  if (this._colorType === constants.COLORTYPE_COLOR) {    this.transColor([data.readUInt16BE(0), data.readUInt16BE(2), data.readUInt16BE(4)]);  }  this._handleChunkEnd();};Parser.prototype._handleGAMA = function(length) {  this.read(length, this._parseGAMA.bind(this));};Parser.prototype._parseGAMA = function(data) {  this._crc.write(data);  this.gamma(data.readUInt32BE(0) / constants.GAMMA_DIVISION);  this._handleChunkEnd();};Parser.prototype._handleIDAT = function(length) {  if (!this._emittedHeadersFinished) {    this._emittedHeadersFinished = true;    this.headersFinished();  }  this.read(-length, this._parseIDAT.bind(this, length));};Parser.prototype._parseIDAT = function(length, data) {  this._crc.write(data);  if (this._colorType === constants.COLORTYPE_PALETTE_COLOR && this._palette.length === 0) {    throw new Error('Expected palette not found');  }  this.inflateData(data);  var leftOverLength = length - data.length;  if (leftOverLength > 0) {    this._handleIDAT(leftOverLength);  }  else {    this._handleChunkEnd();  }};Parser.prototype._handleIEND = function(length) {  this.read(length, this._parseIEND.bind(this));};Parser.prototype._parseIEND = function(data) {  this._crc.write(data);  this._hasIEND = true;  this._handleChunkEnd();  if (this.finished) {    this.finished();  }};
 |