306 lines
6.5 KiB
JavaScript
306 lines
6.5 KiB
JavaScript
|
|
|
||
|
|
var SIGS = {
|
||
|
|
GIF: new Buffer("GIF")
|
||
|
|
,PNG: new Buffer([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a])
|
||
|
|
,JPG: new Buffer([0xff, 0xd8, 0xff])
|
||
|
|
};
|
||
|
|
|
||
|
|
function checkSig(buffer, sig) {
|
||
|
|
return buffer.slice(0, sig.length) == sig.toString();
|
||
|
|
}
|
||
|
|
|
||
|
|
function Parser(options) {
|
||
|
|
|
||
|
|
options = options || {debug: false};
|
||
|
|
|
||
|
|
var data = new Buffer(16);
|
||
|
|
var dataLength = 0;
|
||
|
|
var state = sig;
|
||
|
|
var result = null;
|
||
|
|
|
||
|
|
var debug;
|
||
|
|
|
||
|
|
if (options.debug) {
|
||
|
|
debug = function() {
|
||
|
|
console.log.apply(console, arguments);
|
||
|
|
};
|
||
|
|
} else {
|
||
|
|
debug = function(){};
|
||
|
|
}
|
||
|
|
|
||
|
|
function switchState(newState) {
|
||
|
|
state = newState;
|
||
|
|
debug('switch state', state.name);
|
||
|
|
return state();
|
||
|
|
}
|
||
|
|
|
||
|
|
function sig() {
|
||
|
|
|
||
|
|
if (dataLength < 3) {
|
||
|
|
return Parser.EOF;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (checkSig(data, SIGS.GIF)) {
|
||
|
|
debug('got a gif');
|
||
|
|
return switchState(gif);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (checkSig(data, SIGS.JPG)) {
|
||
|
|
debug('got a jpeg');
|
||
|
|
return switchState(jpeg);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (dataLength < 8) {
|
||
|
|
return Parser.EOF;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (checkSig(data, SIGS.PNG)) {
|
||
|
|
debug('got a png');
|
||
|
|
return switchState(png);
|
||
|
|
}
|
||
|
|
|
||
|
|
return Parser.INVALID;
|
||
|
|
}
|
||
|
|
|
||
|
|
function gif() {
|
||
|
|
|
||
|
|
if (dataLength < 10) {
|
||
|
|
return Parser.EOF;
|
||
|
|
}
|
||
|
|
|
||
|
|
result = {
|
||
|
|
format: 'gif'
|
||
|
|
, width: data.readUInt16LE(6)
|
||
|
|
, height: data.readUInt16LE(8)
|
||
|
|
};
|
||
|
|
|
||
|
|
return Parser.DONE;
|
||
|
|
}
|
||
|
|
|
||
|
|
function png() {
|
||
|
|
|
||
|
|
if (dataLength < 24) {
|
||
|
|
return Parser.EOF;
|
||
|
|
}
|
||
|
|
|
||
|
|
result = {
|
||
|
|
format: 'png'
|
||
|
|
, width: data.readUInt32BE(16)
|
||
|
|
, height: data.readUInt32BE(20)
|
||
|
|
};
|
||
|
|
|
||
|
|
return Parser.DONE;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* The following code was largely inspired from php's core getimagesize()
|
||
|
|
* function */
|
||
|
|
function jpeg() {
|
||
|
|
|
||
|
|
const M_PSEUDO = 0xFFD8;
|
||
|
|
|
||
|
|
/* some defines for the different JPEG block types */
|
||
|
|
const M_SOF0 = 0xC0; /* Start Of Frame N */
|
||
|
|
const M_SOF1 = 0xC1; /* N indicates which compression process */
|
||
|
|
const M_SOF2 = 0xC2; /* Only SOF0-SOF2 are now in common use */
|
||
|
|
const M_SOF3 = 0xC3;
|
||
|
|
const M_SOF5 = 0xC5; /* NB: codes C4 and CC are NOT SOF markers */
|
||
|
|
const M_SOF6 = 0xC6;
|
||
|
|
const M_SOF7 = 0xC7;
|
||
|
|
const M_SOF9 = 0xC9;
|
||
|
|
const M_SOF10 = 0xCA;
|
||
|
|
const M_SOF11 = 0xCB;
|
||
|
|
const M_SOF13 = 0xCD;
|
||
|
|
const M_SOF14 = 0xCE;
|
||
|
|
const M_SOF15 = 0xCF;
|
||
|
|
const M_SOI = 0xD8;
|
||
|
|
const M_EOI = 0xD9; /* End Of Image (end of datastream) */
|
||
|
|
const M_SOS = 0xDA; /* Start Of Scan (begins compressed data) */
|
||
|
|
const M_APP0 = 0xE0;
|
||
|
|
const M_APP1 = 0xE1;
|
||
|
|
const M_APP2 = 0xE2;
|
||
|
|
const M_APP3 = 0xE3;
|
||
|
|
const M_APP4 = 0xE4;
|
||
|
|
const M_APP5 = 0xE5;
|
||
|
|
const M_APP6 = 0xE6;
|
||
|
|
const M_APP7 = 0xE7;
|
||
|
|
const M_APP8 = 0xE8;
|
||
|
|
const M_APP9 = 0xE9;
|
||
|
|
const M_APP10 = 0xEA;
|
||
|
|
const M_APP11 = 0xEB;
|
||
|
|
const M_APP12 = 0xEC;
|
||
|
|
const M_APP13 = 0xED;
|
||
|
|
const M_APP14 = 0xEE;
|
||
|
|
const M_APP15 = 0xEF;
|
||
|
|
const M_COM = 0xFE;/* COMment */
|
||
|
|
|
||
|
|
var i = 2;
|
||
|
|
var marker = M_PSEUDO;
|
||
|
|
|
||
|
|
return switchState(nextMarker);
|
||
|
|
|
||
|
|
function nextMarker() {
|
||
|
|
|
||
|
|
var newMarker;
|
||
|
|
var prevMarker = marker;
|
||
|
|
var commentCorrection;
|
||
|
|
var a = 0;
|
||
|
|
|
||
|
|
if (M_COM == prevMarker) {
|
||
|
|
commentCorrection = 2;
|
||
|
|
} else {
|
||
|
|
commentCorrection = 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (i >= dataLength) {
|
||
|
|
return Parser.EOF;
|
||
|
|
}
|
||
|
|
|
||
|
|
do {
|
||
|
|
if (i+a >= dataLength) {
|
||
|
|
return Parser.EOF;
|
||
|
|
}
|
||
|
|
|
||
|
|
newMarker = data.readUInt8(i+a);
|
||
|
|
|
||
|
|
debug('newMarker', newMarker.toString(16));
|
||
|
|
|
||
|
|
if (M_COM == prevMarker && 0 < commentCorrection) {
|
||
|
|
if (0xFF != newMarker) {
|
||
|
|
newMarker = 0xFF;
|
||
|
|
commentCorrection--;
|
||
|
|
} else {
|
||
|
|
prevMarker = M_PSEUDO; /* stop skipping non 0xff for M_COM */
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
a++;
|
||
|
|
|
||
|
|
} while (0xFF == newMarker);
|
||
|
|
|
||
|
|
if (2 > a) {
|
||
|
|
debug('less than 2 bytes were read');
|
||
|
|
return Parser.INVALID; /* at least one 0xff is needed before marker code */
|
||
|
|
}
|
||
|
|
if (M_COM == prevMarker && commentCorrection) {
|
||
|
|
debug('M_COM and less than 2 bytes were read');
|
||
|
|
return Parser.INVALID; /* ah illegal: char after COM section not 0xFF */
|
||
|
|
}
|
||
|
|
|
||
|
|
debug('marker', newMarker.toString(16));
|
||
|
|
|
||
|
|
i += a;
|
||
|
|
marker = newMarker;
|
||
|
|
|
||
|
|
return switchState(blockBody);
|
||
|
|
}
|
||
|
|
|
||
|
|
function blockBody() {
|
||
|
|
switch (marker) {
|
||
|
|
case M_SOF0:
|
||
|
|
case M_SOF1:
|
||
|
|
case M_SOF2:
|
||
|
|
case M_SOF3:
|
||
|
|
case M_SOF5:
|
||
|
|
case M_SOF6:
|
||
|
|
case M_SOF7:
|
||
|
|
case M_SOF9:
|
||
|
|
case M_SOF10:
|
||
|
|
case M_SOF11:
|
||
|
|
case M_SOF13:
|
||
|
|
case M_SOF14:
|
||
|
|
case M_SOF15:
|
||
|
|
/* handle SOFn block */
|
||
|
|
if (i + 2 + 1 + 2 + 2 >= dataLength) {
|
||
|
|
return Parser.EOF;
|
||
|
|
}
|
||
|
|
result = {
|
||
|
|
format: 'jpeg'
|
||
|
|
, width: data.readUInt16BE(i+5)
|
||
|
|
, height: data.readUInt16BE(i+3)
|
||
|
|
};
|
||
|
|
return Parser.DONE;
|
||
|
|
case M_SOS:
|
||
|
|
case M_EOI:
|
||
|
|
debug('M_SOS/M_EOI, the end');
|
||
|
|
return Parser.INVALID;
|
||
|
|
default:
|
||
|
|
if (i + 2 >= dataLength) {
|
||
|
|
return Parser.EOF;
|
||
|
|
}
|
||
|
|
var length = data.readUInt16BE(i);
|
||
|
|
debug('skipping block; length is', length);
|
||
|
|
i += length;
|
||
|
|
return switchState(nextMarker);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
function parse(chunk) {
|
||
|
|
|
||
|
|
if (chunk.length + dataLength > data.length) {
|
||
|
|
|
||
|
|
var newLength = data.length;
|
||
|
|
|
||
|
|
do {
|
||
|
|
newLength <<= 1;
|
||
|
|
} while (chunk.length + dataLength > newLength);
|
||
|
|
|
||
|
|
var newData = new Buffer(newLength);
|
||
|
|
data.copy(newData);
|
||
|
|
data = newData;
|
||
|
|
}
|
||
|
|
|
||
|
|
chunk.copy(data, dataLength)
|
||
|
|
dataLength += chunk.length
|
||
|
|
|
||
|
|
return state();
|
||
|
|
}
|
||
|
|
|
||
|
|
function getResult() {
|
||
|
|
return result;
|
||
|
|
}
|
||
|
|
|
||
|
|
return {
|
||
|
|
parse: parse
|
||
|
|
, getResult: getResult
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
Parser.EOF = 2; // feed moar
|
||
|
|
Parser.DONE = 3; // result is ready
|
||
|
|
Parser.INVALID = 4; // invalid image
|
||
|
|
|
||
|
|
/* Convenience function for getting the size of an image from a stream */
|
||
|
|
function imagesize(stream, cb) {
|
||
|
|
|
||
|
|
var parser = Parser();
|
||
|
|
|
||
|
|
var done = function (err, result) {
|
||
|
|
stream.removeListener('data', handleData);
|
||
|
|
stream.removeListener('end', handleEnd);
|
||
|
|
cb(err, result);
|
||
|
|
};
|
||
|
|
|
||
|
|
var handleEnd = function () {
|
||
|
|
done('invalid');
|
||
|
|
};
|
||
|
|
|
||
|
|
var handleData = function (data) {
|
||
|
|
switch (parser.parse(data)) {
|
||
|
|
case Parser.INVALID:
|
||
|
|
done('invalid');
|
||
|
|
break;
|
||
|
|
case Parser.DONE:
|
||
|
|
done(null, parser.getResult());
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
stream.on('data', handleData);
|
||
|
|
stream.on('end', handleEnd);
|
||
|
|
}
|
||
|
|
|
||
|
|
module.exports = imagesize;
|
||
|
|
module.exports.Parser = Parser;
|
||
|
|
|