更新
This commit is contained in:
+166
@@ -0,0 +1,166 @@
|
||||
// Utils used to parse miaf-based files (avif/heic/heif)
|
||||
//
|
||||
// - image collections are not supported (only last size is reported)
|
||||
// - images with metadata encoded after image data are not supported
|
||||
// - images without any `ispe` box are not supported
|
||||
//
|
||||
|
||||
'use strict'
|
||||
|
||||
|
||||
|
||||
var ParserStream = require('../common').ParserStream
|
||||
var str2arr = require('../common').str2arr
|
||||
var sliceEq = require('../common').sliceEq
|
||||
var readUInt32BE = require('../common').readUInt32BE
|
||||
var miaf = require('../miaf_utils')
|
||||
var exif = require('../exif_utils')
|
||||
|
||||
var SIG_FTYP = str2arr('ftyp')
|
||||
|
||||
|
||||
function safeSkip (parser, count, callback) {
|
||||
if (count === 0) { // parser._skipBytes throws error if count === 0
|
||||
callback()
|
||||
return
|
||||
}
|
||||
|
||||
parser._skipBytes(count, callback)
|
||||
}
|
||||
|
||||
|
||||
function readExifOrientation (parser, sandbox, on_orientation) {
|
||||
if (!sandbox.exif_location || sandbox.exif_location.offset <= sandbox.offset) {
|
||||
on_orientation(0)
|
||||
return
|
||||
}
|
||||
|
||||
parser._skipBytes(sandbox.exif_location.offset - sandbox.offset, function () {
|
||||
sandbox.offset = sandbox.exif_location.offset
|
||||
|
||||
parser._bytes(4, function (data) {
|
||||
sandbox.offset += 4
|
||||
var sig_offset = readUInt32BE(data, 0)
|
||||
|
||||
safeSkip(parser, sig_offset, function () {
|
||||
sandbox.offset += sig_offset
|
||||
var byteCount = sandbox.exif_location.length - sig_offset - 4
|
||||
|
||||
if (byteCount <= 0) {
|
||||
on_orientation(0)
|
||||
return
|
||||
}
|
||||
|
||||
parser._bytes(byteCount, function (exif_data) {
|
||||
sandbox.offset += byteCount
|
||||
on_orientation(exif.get_orientation(exif_data))
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
// sandbox is a storage for intermediate data retrieved from jpeg while parsing it
|
||||
function readAvifSize (parser, sandbox) {
|
||||
parser._bytes(8, function (data) {
|
||||
sandbox.offset += 8
|
||||
var size = readUInt32BE(data, 0) - 8
|
||||
var type = String.fromCharCode.apply(null, data.slice(4, 8))
|
||||
|
||||
if (type === 'mdat') {
|
||||
parser._skipBytes(Infinity)
|
||||
parser.push(null)
|
||||
return
|
||||
}
|
||||
|
||||
if (size < 0) {
|
||||
parser._skipBytes(Infinity)
|
||||
parser.push(null)
|
||||
return
|
||||
}
|
||||
|
||||
if (type === 'meta' && size > 0) {
|
||||
parser._bytes(size, function (data) {
|
||||
sandbox.offset += size
|
||||
var imgSize = miaf.readSizeFromMeta(data)
|
||||
|
||||
if (!imgSize) {
|
||||
parser._skipBytes(Infinity)
|
||||
parser.push(null)
|
||||
return
|
||||
}
|
||||
|
||||
var result = {
|
||||
width: imgSize.width,
|
||||
height: imgSize.height,
|
||||
type: sandbox.fileType.type,
|
||||
mime: sandbox.fileType.mime,
|
||||
wUnits: 'px',
|
||||
hUnits: 'px'
|
||||
}
|
||||
|
||||
if (imgSize.variants.length > 1) {
|
||||
result.variants = imgSize.variants
|
||||
}
|
||||
|
||||
if (imgSize.orientation) {
|
||||
result.orientation = imgSize.orientation
|
||||
}
|
||||
|
||||
sandbox.exif_location = imgSize.exif_location
|
||||
|
||||
readExifOrientation(parser, sandbox, function (orientation) {
|
||||
if (orientation > 0) result.orientation = orientation
|
||||
|
||||
parser._skipBytes(Infinity)
|
||||
parser.push(result)
|
||||
parser.push(null)
|
||||
})
|
||||
})
|
||||
} else {
|
||||
safeSkip(parser, size, function () {
|
||||
sandbox.offset += size
|
||||
readAvifSize(parser, sandbox)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
module.exports = function () {
|
||||
var parser = new ParserStream()
|
||||
var sandbox = { offset: 0, fileType: null }
|
||||
|
||||
parser._bytes(8, function (data) {
|
||||
sandbox.offset += 8
|
||||
if (!sliceEq(data, 4, SIG_FTYP)) {
|
||||
parser._skipBytes(Infinity)
|
||||
parser.push(null)
|
||||
return
|
||||
}
|
||||
|
||||
var size = readUInt32BE(data, 0) - 8
|
||||
|
||||
if (size <= 0) {
|
||||
parser._skipBytes(Infinity)
|
||||
parser.push(null)
|
||||
return
|
||||
}
|
||||
|
||||
parser._bytes(size, function (data) {
|
||||
sandbox.offset += size
|
||||
sandbox.fileType = miaf.getMimeType(data)
|
||||
|
||||
if (!sandbox.fileType) {
|
||||
parser._skipBytes(Infinity)
|
||||
parser.push(null)
|
||||
return
|
||||
}
|
||||
|
||||
readAvifSize(parser, sandbox)
|
||||
})
|
||||
})
|
||||
|
||||
return parser
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
'use strict'
|
||||
|
||||
|
||||
var ParserStream = require('../common').ParserStream
|
||||
var str2arr = require('../common').str2arr
|
||||
var sliceEq = require('../common').sliceEq
|
||||
|
||||
|
||||
var SIG_BM = str2arr('BM')
|
||||
|
||||
|
||||
module.exports = function () {
|
||||
var parser = new ParserStream()
|
||||
|
||||
parser._bytes(26, function (data) {
|
||||
parser._skipBytes(Infinity)
|
||||
|
||||
if (!sliceEq(data, 0, SIG_BM)) {
|
||||
parser.push(null)
|
||||
return
|
||||
}
|
||||
|
||||
var w, h
|
||||
var headerSize = data.readUInt32LE(14)
|
||||
|
||||
if (headerSize === 12) {
|
||||
// BMP v2 header
|
||||
w = data.readInt16LE(18)
|
||||
h = data.readInt16LE(20)
|
||||
} else if (headerSize > 12) {
|
||||
// BMP v3+ header
|
||||
w = data.readInt32LE(18)
|
||||
h = data.readInt32LE(22)
|
||||
} else {
|
||||
parser.push(null)
|
||||
return
|
||||
}
|
||||
|
||||
parser.push({
|
||||
width: w,
|
||||
// Height can be negative to indicate a top-down bitmap
|
||||
height: Math.abs(h),
|
||||
type: 'bmp',
|
||||
mime: 'image/bmp',
|
||||
wUnits: 'px',
|
||||
hUnits: 'px'
|
||||
})
|
||||
|
||||
parser.push(null)
|
||||
})
|
||||
|
||||
return parser
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
'use strict'
|
||||
|
||||
|
||||
var ParserStream = require('../common').ParserStream
|
||||
var str2arr = require('../common').str2arr
|
||||
var sliceEq = require('../common').sliceEq
|
||||
|
||||
|
||||
var SIG_GIF87a = str2arr('GIF87a')
|
||||
var SIG_GIF89a = str2arr('GIF89a')
|
||||
|
||||
|
||||
module.exports = function () {
|
||||
var parser = new ParserStream()
|
||||
|
||||
parser._bytes(10, function (data) {
|
||||
parser._skipBytes(Infinity)
|
||||
|
||||
if (!sliceEq(data, 0, SIG_GIF87a) && !sliceEq(data, 0, SIG_GIF89a)) {
|
||||
parser.push(null)
|
||||
return
|
||||
}
|
||||
|
||||
parser.push({
|
||||
width: data.readUInt16LE(6),
|
||||
height: data.readUInt16LE(8),
|
||||
type: 'gif',
|
||||
mime: 'image/gif',
|
||||
wUnits: 'px',
|
||||
hUnits: 'px'
|
||||
})
|
||||
|
||||
parser.push(null)
|
||||
})
|
||||
|
||||
return parser
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
'use strict'
|
||||
|
||||
var ParserStream = require('../common').ParserStream
|
||||
|
||||
var HEADER = 0
|
||||
var TYPE_ICO = 1
|
||||
var INDEX_SIZE = 16
|
||||
|
||||
// Format specification:
|
||||
// https://en.wikipedia.org/wiki/ICO_(file_format)#Icon_resource_structure
|
||||
module.exports = function () {
|
||||
var parser = new ParserStream()
|
||||
|
||||
parser._bytes(6, function (data) {
|
||||
var header = data.readUInt16LE(0)
|
||||
var type = data.readUInt16LE(2)
|
||||
var numImages = data.readUInt16LE(4)
|
||||
|
||||
if (header !== HEADER || type !== TYPE_ICO || !numImages) {
|
||||
parser._skipBytes(Infinity)
|
||||
parser.push(null)
|
||||
return
|
||||
}
|
||||
|
||||
parser._bytes(numImages * INDEX_SIZE, function (indexData) {
|
||||
parser._skipBytes(Infinity)
|
||||
|
||||
var variants = []
|
||||
var maxSize = { width: 0, height: 0 }
|
||||
|
||||
for (var i = 0; i < numImages; i++) {
|
||||
var width = indexData.readUInt8(INDEX_SIZE * i + 0) || 256
|
||||
var height = indexData.readUInt8(INDEX_SIZE * i + 1) || 256
|
||||
var size = { width: width, height: height }
|
||||
variants.push(size)
|
||||
|
||||
if (width > maxSize.width || height > maxSize.height) {
|
||||
maxSize = size
|
||||
}
|
||||
}
|
||||
|
||||
parser.push({
|
||||
width: maxSize.width,
|
||||
height: maxSize.height,
|
||||
variants: variants,
|
||||
type: 'ico',
|
||||
mime: 'image/x-icon',
|
||||
wUnits: 'px',
|
||||
hUnits: 'px'
|
||||
})
|
||||
parser.push(null)
|
||||
})
|
||||
})
|
||||
|
||||
return parser
|
||||
}
|
||||
+148
@@ -0,0 +1,148 @@
|
||||
'use strict'
|
||||
|
||||
|
||||
var ParserStream = require('../common').ParserStream
|
||||
var str2arr = require('../common').str2arr
|
||||
var sliceEq = require('../common').sliceEq
|
||||
var exif = require('../exif_utils')
|
||||
|
||||
|
||||
var SIG_EXIF = str2arr('Exif\0\0')
|
||||
|
||||
|
||||
// part of parseJpegMarker called after skipping initial FF
|
||||
function parseJpegMarker_afterFF (parser, callback) {
|
||||
parser._bytes(1, function (data) {
|
||||
var code = data[0]
|
||||
|
||||
if (code === 0xFF) {
|
||||
// padding byte, skip it
|
||||
parseJpegMarker_afterFF(parser, callback)
|
||||
return
|
||||
}
|
||||
|
||||
// standalone markers, according to JPEG 1992,
|
||||
// http://www.w3.org/Graphics/JPEG/itu-t81.pdf, see Table B.1
|
||||
if ((code >= 0xD0 && code <= 0xD9) || code === 0x01) {
|
||||
callback(code, 0)
|
||||
return
|
||||
}
|
||||
|
||||
// the rest of the unreserved markers
|
||||
if (code >= 0xC0 && code <= 0xFE) {
|
||||
parser._bytes(2, function (length) {
|
||||
callback(code, length.readUInt16BE(0) - 2)
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// unknown markers
|
||||
callback()
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
function parseJpegMarker (parser, sandbox, callback) {
|
||||
var start = sandbox.start
|
||||
sandbox.start = false
|
||||
|
||||
parser._bytes(1, function (data) {
|
||||
if (data[0] !== 0xFF) {
|
||||
// not a JPEG marker
|
||||
if (start) {
|
||||
// expect JPEG file to start with `FFD8 FFE0`, `FFD8 FFE2` or `FFD8 FFE1`,
|
||||
// don't allow garbage before second marker
|
||||
callback()
|
||||
} else {
|
||||
// skip until we see 0xFF, see https://github.com/nodeca/probe-image-size/issues/68
|
||||
parseJpegMarker(parser, sandbox, callback)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
parseJpegMarker_afterFF(parser, callback)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
// sandbox is a storage for intermediate data retrieved from jpeg while parsing it
|
||||
function getJpegSize (parser, sandbox) {
|
||||
parseJpegMarker(parser, sandbox, function (code, length) {
|
||||
if (!code || length < 0) {
|
||||
// invalid jpeg
|
||||
parser._skipBytes(Infinity)
|
||||
parser.push(null)
|
||||
return
|
||||
}
|
||||
|
||||
if (code === 0xD9 /* EOI */ || code === 0xDA /* SOS */) {
|
||||
// end of the datastream
|
||||
parser._skipBytes(Infinity)
|
||||
parser.push(null)
|
||||
return
|
||||
}
|
||||
|
||||
// try to get orientation from Exif segment
|
||||
if (code === 0xE1 && length >= 10) {
|
||||
parser._bytes(length, function (data) {
|
||||
if (sliceEq(data, 0, SIG_EXIF)) {
|
||||
sandbox.orientation = exif.get_orientation(data.slice(6, 6 + length))
|
||||
}
|
||||
|
||||
getJpegSize(parser, sandbox)
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if (length <= 0) {
|
||||
// e.g. empty comment
|
||||
getJpegSize(parser, sandbox)
|
||||
return
|
||||
}
|
||||
|
||||
if (length >= 5 &&
|
||||
(code >= 0xC0 && code <= 0xCF) &&
|
||||
code !== 0xC4 && code !== 0xC8 && code !== 0xCC) {
|
||||
parser._bytes(length, function (data) {
|
||||
parser._skipBytes(Infinity)
|
||||
|
||||
var result = {
|
||||
width: data.readUInt16BE(3),
|
||||
height: data.readUInt16BE(1),
|
||||
type: 'jpg',
|
||||
mime: 'image/jpeg',
|
||||
wUnits: 'px',
|
||||
hUnits: 'px'
|
||||
}
|
||||
|
||||
if (sandbox.orientation > 0) result.orientation = sandbox.orientation
|
||||
|
||||
parser.push(result)
|
||||
parser.push(null)
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
parser._skipBytes(length, function () {
|
||||
getJpegSize(parser, sandbox)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
module.exports = function () {
|
||||
var parser = new ParserStream()
|
||||
|
||||
parser._bytes(2, function (data) {
|
||||
if (data[0] !== 0xFF || data[1] !== 0xD8) {
|
||||
// first marker of the file MUST be 0xFFD8
|
||||
parser._skipBytes(Infinity)
|
||||
parser.push(null)
|
||||
return
|
||||
}
|
||||
|
||||
getJpegSize(parser, { start: true })
|
||||
})
|
||||
|
||||
return parser
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
'use strict'
|
||||
|
||||
|
||||
var ParserStream = require('../common').ParserStream
|
||||
var str2arr = require('../common').str2arr
|
||||
var sliceEq = require('../common').sliceEq
|
||||
|
||||
|
||||
var SIG_PNG = str2arr('\x89PNG\r\n\x1a\n')
|
||||
var SIG_IHDR = str2arr('IHDR')
|
||||
|
||||
|
||||
module.exports = function () {
|
||||
var parser = new ParserStream()
|
||||
|
||||
parser._bytes(24, function (data) {
|
||||
parser._skipBytes(Infinity)
|
||||
|
||||
// check PNG signature
|
||||
if (!sliceEq(data, 0, SIG_PNG)) {
|
||||
parser.push(null)
|
||||
return
|
||||
}
|
||||
|
||||
// check that first chunk is IHDR
|
||||
if (!sliceEq(data, 12, SIG_IHDR)) {
|
||||
parser.push(null)
|
||||
return
|
||||
}
|
||||
|
||||
parser.push({
|
||||
width: data.readUInt32BE(16),
|
||||
height: data.readUInt32BE(20),
|
||||
type: 'png',
|
||||
mime: 'image/png',
|
||||
wUnits: 'px',
|
||||
hUnits: 'px'
|
||||
})
|
||||
|
||||
parser.push(null)
|
||||
})
|
||||
|
||||
return parser
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
'use strict'
|
||||
|
||||
|
||||
var ParserStream = require('../common').ParserStream
|
||||
var str2arr = require('../common').str2arr
|
||||
var sliceEq = require('../common').sliceEq
|
||||
|
||||
|
||||
var SIG_8BPS = str2arr('8BPS\x00\x01')
|
||||
|
||||
|
||||
module.exports = function () {
|
||||
var parser = new ParserStream()
|
||||
|
||||
parser._bytes(6, function (data) {
|
||||
// signature + version
|
||||
if (!sliceEq(data, 0, SIG_8BPS)) {
|
||||
parser._skipBytes(Infinity)
|
||||
parser.push(null)
|
||||
return
|
||||
}
|
||||
|
||||
parser._bytes(16, function (data) {
|
||||
parser._skipBytes(Infinity)
|
||||
|
||||
parser.push({
|
||||
width: data.readUInt32BE(12),
|
||||
height: data.readUInt32BE(8),
|
||||
type: 'psd',
|
||||
mime: 'image/vnd.adobe.photoshop',
|
||||
wUnits: 'px',
|
||||
hUnits: 'px'
|
||||
})
|
||||
|
||||
parser.push(null)
|
||||
})
|
||||
})
|
||||
|
||||
return parser
|
||||
}
|
||||
+217
@@ -0,0 +1,217 @@
|
||||
'use strict'
|
||||
|
||||
|
||||
var Transform = require('stream').Transform
|
||||
|
||||
var STATE_IDENTIFY = 0 // look for '<'
|
||||
var STATE_PARSE = 1 // extract width and height from svg tag
|
||||
var STATE_IGNORE = 2 // we got all the data we want, skip the rest
|
||||
|
||||
// max size for pre-svg-tag comments plus svg tag itself
|
||||
var MAX_DATA_LENGTH = 65536
|
||||
|
||||
// skip `<?` (comments), `<!` (directives, cdata, doctype),
|
||||
// looking for `<svg>` or `<NAMESPACE:svg>`
|
||||
var SVG_HEADER_RE = /<[-_.:a-zA-Z0-9][^>]*>/
|
||||
|
||||
// test if the top level element is svg + optional namespace,
|
||||
// used to skip svg embedded in html
|
||||
var SVG_TAG_RE = /^<([-_.:a-zA-Z0-9]+:)?svg\s/
|
||||
|
||||
var SVG_WIDTH_RE = /[^-]\bwidth="([^%]+?)"|[^-]\bwidth='([^%]+?)'/
|
||||
var SVG_HEIGHT_RE = /\bheight="([^%]+?)"|\bheight='([^%]+?)'/
|
||||
var SVG_VIEWBOX_RE = /\bview[bB]ox="(.+?)"|\bview[bB]ox='(.+?)'/
|
||||
var SVG_UNITS_RE = /in$|mm$|cm$|pt$|pc$|px$|em$|ex$/
|
||||
|
||||
|
||||
function isWhiteSpace (chr) {
|
||||
return chr === 0x20 || chr === 0x09 || chr === 0x0D || chr === 0x0A
|
||||
}
|
||||
|
||||
// Filter NaN, Infinity, < 0
|
||||
function isFinitePositive (val) {
|
||||
return typeof val === 'number' && isFinite(val) && val > 0
|
||||
}
|
||||
|
||||
function svgAttrs (str) {
|
||||
var width = str.match(SVG_WIDTH_RE)
|
||||
var height = str.match(SVG_HEIGHT_RE)
|
||||
var viewbox = str.match(SVG_VIEWBOX_RE)
|
||||
|
||||
return {
|
||||
width: width && (width[1] || width[2]),
|
||||
height: height && (height[1] || height[2]),
|
||||
viewbox: viewbox && (viewbox[1] || viewbox[2])
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function units (str) {
|
||||
if (!SVG_UNITS_RE.test(str)) return 'px'
|
||||
|
||||
return str.match(SVG_UNITS_RE)[0]
|
||||
}
|
||||
|
||||
|
||||
function parseSvg (str) {
|
||||
// get top level element
|
||||
var svgTag = (str.match(SVG_HEADER_RE) || [''])[0]
|
||||
|
||||
// test if top level element is <svg>
|
||||
if (!SVG_TAG_RE.test(svgTag)) return
|
||||
|
||||
var attrs = svgAttrs(svgTag)
|
||||
var width = parseFloat(attrs.width)
|
||||
var height = parseFloat(attrs.height)
|
||||
|
||||
// Extract from direct values
|
||||
|
||||
if (attrs.width && attrs.height) {
|
||||
if (!isFinitePositive(width) || !isFinitePositive(height)) return
|
||||
|
||||
return {
|
||||
width: width,
|
||||
height: height,
|
||||
type: 'svg',
|
||||
mime: 'image/svg+xml',
|
||||
wUnits: units(attrs.width),
|
||||
hUnits: units(attrs.height)
|
||||
}
|
||||
}
|
||||
|
||||
// Extract from viewbox
|
||||
|
||||
var parts = (attrs.viewbox || '').split(' ')
|
||||
var viewbox = {
|
||||
width: parts[2],
|
||||
height: parts[3]
|
||||
}
|
||||
var vbWidth = parseFloat(viewbox.width)
|
||||
var vbHeight = parseFloat(viewbox.height)
|
||||
|
||||
if (!isFinitePositive(vbWidth) || !isFinitePositive(vbHeight)) return
|
||||
if (units(viewbox.width) !== units(viewbox.height)) return
|
||||
|
||||
var ratio = vbWidth / vbHeight
|
||||
|
||||
if (attrs.width) {
|
||||
if (!isFinitePositive(width)) return
|
||||
|
||||
return {
|
||||
width: width,
|
||||
height: width / ratio,
|
||||
type: 'svg',
|
||||
mime: 'image/svg+xml',
|
||||
wUnits: units(attrs.width),
|
||||
hUnits: units(attrs.width)
|
||||
}
|
||||
}
|
||||
|
||||
if (attrs.height) {
|
||||
if (!isFinitePositive(height)) return
|
||||
|
||||
return {
|
||||
width: height * ratio,
|
||||
height: height,
|
||||
type: 'svg',
|
||||
mime: 'image/svg+xml',
|
||||
wUnits: units(attrs.height),
|
||||
hUnits: units(attrs.height)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
width: vbWidth,
|
||||
height: vbHeight,
|
||||
type: 'svg',
|
||||
mime: 'image/svg+xml',
|
||||
wUnits: units(viewbox.width),
|
||||
hUnits: units(viewbox.height)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
module.exports = function () {
|
||||
var state = STATE_IDENTIFY
|
||||
var data_len = 0
|
||||
var str = ''
|
||||
var buf = null // used to manage first chunk in IDENTIFY
|
||||
|
||||
var parser = new Transform({
|
||||
readableObjectMode: true,
|
||||
transform: function transform (chunk, encoding, next) {
|
||||
switch (state) {
|
||||
// identify step is needed to fail fast if the file isn't SVG
|
||||
case STATE_IDENTIFY:
|
||||
if (buf) {
|
||||
// make sure that first chunk is at least 4 bytes (to do BOM skip later),
|
||||
// last chunk was small
|
||||
chunk = Buffer.concat([buf, chunk])
|
||||
buf = null
|
||||
}
|
||||
|
||||
if (data_len === 0 && chunk.length < 4) {
|
||||
// make sure that first chunk is at least 4 bytes (to do BOM skip later),
|
||||
// current chunk is small
|
||||
buf = chunk
|
||||
break
|
||||
}
|
||||
|
||||
var i = 0
|
||||
var max = chunk.length
|
||||
|
||||
// byte order mark, https://github.com/nodeca/probe-image-size/issues/57
|
||||
if (data_len === 0 && chunk[0] === 0xEF && chunk[1] === 0xBB && chunk[2] === 0xBF) i = 3
|
||||
|
||||
while (i < max && isWhiteSpace(chunk[i])) i++
|
||||
|
||||
if (i >= max) {
|
||||
data_len += chunk.length
|
||||
|
||||
if (data_len > MAX_DATA_LENGTH) {
|
||||
state = STATE_IGNORE
|
||||
parser.push(null)
|
||||
}
|
||||
} else if (chunk[i] === 0x3c /* < */) {
|
||||
state = STATE_PARSE
|
||||
return transform(chunk, encoding, next)
|
||||
} else {
|
||||
state = STATE_IGNORE
|
||||
parser.push(null)
|
||||
}
|
||||
|
||||
break
|
||||
|
||||
case STATE_PARSE:
|
||||
str += chunk.toString()
|
||||
|
||||
var result = parseSvg(str)
|
||||
|
||||
if (result) {
|
||||
state = STATE_IGNORE
|
||||
parser.push(result)
|
||||
parser.push(null)
|
||||
break
|
||||
}
|
||||
|
||||
data_len += chunk.length
|
||||
|
||||
if (data_len > MAX_DATA_LENGTH) {
|
||||
state = STATE_IGNORE
|
||||
parser.push(null)
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
next()
|
||||
},
|
||||
|
||||
flush: function () {
|
||||
state = STATE_IGNORE
|
||||
parser.push(null)
|
||||
}
|
||||
})
|
||||
|
||||
return parser
|
||||
}
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
'use strict'
|
||||
|
||||
|
||||
var ParserStream = require('../common').ParserStream
|
||||
var str2arr = require('../common').str2arr
|
||||
var sliceEq = require('../common').sliceEq
|
||||
|
||||
|
||||
var SIG_1 = str2arr('II\x2A\0')
|
||||
var SIG_2 = str2arr('MM\0\x2A')
|
||||
|
||||
|
||||
function readUInt16 (buffer, offset, is_big_endian) {
|
||||
return is_big_endian ? buffer.readUInt16BE(offset) : buffer.readUInt16LE(offset)
|
||||
}
|
||||
|
||||
function readUInt32 (buffer, offset, is_big_endian) {
|
||||
return is_big_endian ? buffer.readUInt32BE(offset) : buffer.readUInt32LE(offset)
|
||||
}
|
||||
|
||||
function readIFDValue (data, data_offset, is_big_endian) {
|
||||
var type = readUInt16(data, data_offset + 2, is_big_endian)
|
||||
var values = readUInt32(data, data_offset + 4, is_big_endian)
|
||||
|
||||
if (values !== 1 || (type !== 3 && type !== 4)) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (type === 3) {
|
||||
return readUInt16(data, data_offset + 8, is_big_endian)
|
||||
}
|
||||
|
||||
return readUInt32(data, data_offset + 8, is_big_endian)
|
||||
}
|
||||
|
||||
module.exports = function () {
|
||||
var parser = new ParserStream()
|
||||
|
||||
// read header
|
||||
parser._bytes(8, function (data) {
|
||||
// check TIFF signature
|
||||
if (!sliceEq(data, 0, SIG_1) && !sliceEq(data, 0, SIG_2)) {
|
||||
parser._skipBytes(Infinity)
|
||||
parser.push(null)
|
||||
return
|
||||
}
|
||||
|
||||
var is_big_endian = (data[0] === 77 /* 'MM' */)
|
||||
var count = readUInt32(data, 4, is_big_endian) - 8
|
||||
|
||||
if (count < 0) {
|
||||
parser._skipBytes(Infinity)
|
||||
parser.push(null)
|
||||
return
|
||||
}
|
||||
|
||||
function safeSkip (parser, count, callback) {
|
||||
if (count === 0) { // parser._skipBytes throws error if count === 0
|
||||
callback()
|
||||
return
|
||||
}
|
||||
|
||||
parser._skipBytes(count, callback)
|
||||
}
|
||||
|
||||
// skip until IFD
|
||||
safeSkip(parser, count, function () {
|
||||
// read number of IFD entries
|
||||
parser._bytes(2, function (data) {
|
||||
var ifd_size = readUInt16(data, 0, is_big_endian) * 12
|
||||
|
||||
if (ifd_size <= 0) {
|
||||
parser._skipBytes(Infinity)
|
||||
parser.push(null)
|
||||
return
|
||||
}
|
||||
|
||||
// read all IFD entries
|
||||
parser._bytes(ifd_size, function (data) {
|
||||
parser._skipBytes(Infinity)
|
||||
|
||||
var i, width, height, tag
|
||||
|
||||
for (i = 0; i < ifd_size; i += 12) {
|
||||
tag = readUInt16(data, i, is_big_endian)
|
||||
|
||||
if (tag === 256) {
|
||||
width = readIFDValue(data, i, is_big_endian)
|
||||
} else if (tag === 257) {
|
||||
height = readIFDValue(data, i, is_big_endian)
|
||||
}
|
||||
}
|
||||
|
||||
if (width && height) {
|
||||
parser.push({
|
||||
width: width,
|
||||
height: height,
|
||||
type: 'tiff',
|
||||
mime: 'image/tiff',
|
||||
wUnits: 'px',
|
||||
hUnits: 'px'
|
||||
})
|
||||
}
|
||||
|
||||
parser.push(null)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
return parser
|
||||
}
|
||||
+177
@@ -0,0 +1,177 @@
|
||||
'use strict'
|
||||
|
||||
|
||||
var ParserStream = require('../common').ParserStream
|
||||
var str2arr = require('../common').str2arr
|
||||
var sliceEq = require('../common').sliceEq
|
||||
var exif = require('../exif_utils')
|
||||
|
||||
|
||||
var SIG_RIFF = str2arr('RIFF')
|
||||
var SIG_WEBP = str2arr('WEBP')
|
||||
|
||||
|
||||
function safeSkip (parser, count, callback) {
|
||||
if (count === 0) { // parser._skipBytes throws error if count === 0
|
||||
callback()
|
||||
return
|
||||
}
|
||||
|
||||
parser._skipBytes(count, callback)
|
||||
}
|
||||
|
||||
|
||||
function parseVP8 (parser, length, sandbox) {
|
||||
parser._bytes(10, function (data) {
|
||||
// check code block signature
|
||||
if (data[3] === 0x9D && data[4] === 0x01 && data[5] === 0x2A) {
|
||||
sandbox.result = sandbox.result || {
|
||||
width: data.readUInt16LE(6) & 0x3FFF,
|
||||
height: data.readUInt16LE(8) & 0x3FFF,
|
||||
type: 'webp',
|
||||
mime: 'image/webp',
|
||||
wUnits: 'px',
|
||||
hUnits: 'px'
|
||||
}
|
||||
}
|
||||
|
||||
safeSkip(parser, length - 10, function () {
|
||||
sandbox.offset += length
|
||||
getWebpSize(parser, sandbox)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
function parseVP8L (parser, length, sandbox) {
|
||||
parser._bytes(5, function (data) {
|
||||
// check code block signature
|
||||
if (data[0] === 0x2F) {
|
||||
var bits = data.readUInt32LE(1)
|
||||
|
||||
sandbox.result = sandbox.result || {
|
||||
width: (bits & 0x3FFF) + 1,
|
||||
height: ((bits >> 14) & 0x3FFF) + 1,
|
||||
type: 'webp',
|
||||
mime: 'image/webp',
|
||||
wUnits: 'px',
|
||||
hUnits: 'px'
|
||||
}
|
||||
}
|
||||
|
||||
safeSkip(parser, length - 5, function () {
|
||||
sandbox.offset += length
|
||||
getWebpSize(parser, sandbox)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
function parseVP8X (parser, length, sandbox) {
|
||||
parser._bytes(10, function (data) {
|
||||
sandbox.result = sandbox.result || {
|
||||
// TODO: replace with `data.readUIntLE(8, 3) + 1`
|
||||
// when 0.10 support is dropped
|
||||
width: ((data[6] << 16) | (data[5] << 8) | data[4]) + 1,
|
||||
height: ((data[9] << 16) | (data[8] << 8) | data[7]) + 1,
|
||||
type: 'webp',
|
||||
mime: 'image/webp',
|
||||
wUnits: 'px',
|
||||
hUnits: 'px'
|
||||
}
|
||||
|
||||
safeSkip(parser, length - 10, function () {
|
||||
sandbox.offset += length
|
||||
getWebpSize(parser, sandbox)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
function parseExif (parser, length, sandbox) {
|
||||
parser._bytes(length, function (data) {
|
||||
// exif is the last chunk we care about, stop after it
|
||||
sandbox.offset = Infinity
|
||||
sandbox.exif_orientation = exif.get_orientation(data)
|
||||
|
||||
getWebpSize(parser, sandbox)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
function getWebpSize (parser, sandbox) {
|
||||
if (sandbox.fileLength - 8 <= sandbox.offset) {
|
||||
parser._skipBytes(Infinity)
|
||||
|
||||
if (sandbox.result) {
|
||||
var result = sandbox.result
|
||||
|
||||
if (sandbox.exif_orientation > 0) {
|
||||
result.orientation = sandbox.exif_orientation
|
||||
}
|
||||
|
||||
parser.push(result)
|
||||
}
|
||||
|
||||
parser.push(null)
|
||||
return
|
||||
}
|
||||
|
||||
parser._bytes(4 - sandbox.bufferedChunkHeader.length, function (data) {
|
||||
sandbox.offset += 4 - sandbox.bufferedChunkHeader.length
|
||||
var header = sandbox.bufferedChunkHeader + String.fromCharCode.apply(null, data)
|
||||
|
||||
// after each chunk of odd size there should be 0 byte of padding, skip those
|
||||
header = header.replace(/^\0+/, '')
|
||||
|
||||
if (header.length < 4) {
|
||||
sandbox.bufferedChunkHeader = header
|
||||
getWebpSize(parser, sandbox)
|
||||
return
|
||||
}
|
||||
|
||||
sandbox.bufferedChunkHeader = ''
|
||||
|
||||
parser._bytes(4, function (data) {
|
||||
sandbox.offset += 4
|
||||
var length = data.readUInt32LE(0)
|
||||
|
||||
if (header === 'VP8 ' && length >= 10) {
|
||||
parseVP8(parser, length, sandbox)
|
||||
} else if (header === 'VP8L' && length >= 5) {
|
||||
parseVP8L(parser, length, sandbox)
|
||||
} else if (header === 'VP8X' && length >= 10) {
|
||||
parseVP8X(parser, length, sandbox)
|
||||
} else if (header === 'EXIF' && length >= 4) {
|
||||
parseExif(parser, length, sandbox)
|
||||
} else {
|
||||
safeSkip(parser, length, function () {
|
||||
sandbox.offset += length
|
||||
getWebpSize(parser, sandbox)
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
module.exports = function () {
|
||||
var parser = new ParserStream()
|
||||
|
||||
parser._bytes(12, function (data) {
|
||||
// check /^RIFF....WEBPVP8([ LX])$/ signature
|
||||
if (sliceEq(data, 0, SIG_RIFF) && sliceEq(data, 8, SIG_WEBP)) {
|
||||
getWebpSize(parser, {
|
||||
fileLength: data.readUInt32LE(4) + 8,
|
||||
offset: 12,
|
||||
exif_orientation: 0,
|
||||
bufferedChunkHeader: '' // for dealing with padding
|
||||
})
|
||||
} else {
|
||||
parser._skipBytes(Infinity)
|
||||
parser.push(null)
|
||||
}
|
||||
})
|
||||
|
||||
return parser
|
||||
}
|
||||
Reference in New Issue
Block a user