This commit is contained in:
Your Name
2026-08-11 17:41:36 +08:00
parent cfe4c82c90
commit 03fe4ddf9d
18771 changed files with 3617239 additions and 0 deletions
+87
View File
@@ -0,0 +1,87 @@
// 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 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')
module.exports = function (data) {
// ISO media file (avif format) starts with ftyp box:
// 0000 0020 6674 7970 6176 6966
// (length) f t y p a v i f
//
if (!sliceEq(data, 4, SIG_FTYP)) return
var firstBox = miaf.unbox(data, 0)
if (!firstBox) return
var fileType = miaf.getMimeType(firstBox.data)
if (!fileType) return
var meta
var offset = firstBox.end
for (;;) {
var box = miaf.unbox(data, offset)
if (!box) break
offset = box.end
// mdat block SHOULD be last (but not strictly required),
// so it's unlikely that metadata is after it
if (box.boxtype === 'mdat') return
if (box.boxtype === 'meta') {
meta = box.data
break
}
}
if (!meta) return
var imgSize = miaf.readSizeFromMeta(meta)
if (!imgSize) return
var result = {
width: imgSize.width,
height: imgSize.height,
type: fileType.type,
mime: fileType.mime,
wUnits: 'px',
hUnits: 'px'
}
if (imgSize.variants.length > 1) {
result.variants = imgSize.variants
}
if (imgSize.orientation) {
result.orientation = imgSize.orientation
}
if (imgSize.exif_location &&
imgSize.exif_location.offset + imgSize.exif_location.length <= data.length) {
var sig_offset = readUInt32BE(data, imgSize.exif_location.offset)
var exif_data = data.slice(
imgSize.exif_location.offset + sig_offset + 4,
imgSize.exif_location.offset + imgSize.exif_location.length)
var orientation = exif.get_orientation(exif_data)
if (orientation > 0) result.orientation = orientation
}
return result
}
+44
View File
@@ -0,0 +1,44 @@
'use strict'
var str2arr = require('../common').str2arr
var sliceEq = require('../common').sliceEq
var readInt16LE = require('../common').readInt16LE
var readInt32LE = require('../common').readInt32LE
var readUInt32LE = require('../common').readUInt32LE
var SIG_BM = str2arr('BM')
module.exports = function (data) {
if (data.length < 26) return
if (!sliceEq(data, 0, SIG_BM)) return
var h
var w
var headerSize = readUInt32LE(data, 14)
if (headerSize === 12) {
// BMP v2 header
w = readInt16LE(data, 18)
h = readInt16LE(data, 20)
} else if (headerSize > 12) {
// BMP v3+ header
w = readInt32LE(data, 18)
h = readInt32LE(data, 22)
} else {
// BPM v1 and other garbage (10 bytes usually)
return
}
return {
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'
}
}
+26
View File
@@ -0,0 +1,26 @@
'use strict'
var str2arr = require('../common').str2arr
var sliceEq = require('../common').sliceEq
var readUInt16LE = require('../common').readUInt16LE
var SIG_GIF87a = str2arr('GIF87a')
var SIG_GIF89a = str2arr('GIF89a')
module.exports = function (data) {
if (data.length < 10) return
if (!sliceEq(data, 0, SIG_GIF87a) && !sliceEq(data, 0, SIG_GIF89a)) return
return {
width: readUInt16LE(data, 6),
height: readUInt16LE(data, 8),
type: 'gif',
mime: 'image/gif',
wUnits: 'px',
hUnits: 'px'
}
}
+46
View File
@@ -0,0 +1,46 @@
'use strict'
var readUInt16LE = require('../common').readUInt16LE
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 (data) {
var header = readUInt16LE(data, 0)
var type = readUInt16LE(data, 2)
var numImages = readUInt16LE(data, 4)
if (header !== HEADER || type !== TYPE_ICO || !numImages) {
return
}
if (data.length < 6 + numImages * INDEX_SIZE) return
var variants = []
var maxSize = { width: 0, height: 0 }
for (var i = 0; i < numImages; i++) {
var width = data[6 + INDEX_SIZE * i] || 256
var height = data[6 + INDEX_SIZE * i + 1] || 256
var size = { width: width, height: height }
variants.push(size)
if (width > maxSize.width || height > maxSize.height) {
maxSize = size
}
}
return {
width: maxSize.width,
height: maxSize.height,
variants: variants,
type: 'ico',
mime: 'image/x-icon',
wUnits: 'px',
hUnits: 'px'
}
}
+85
View File
@@ -0,0 +1,85 @@
'use strict'
var readUInt16BE = require('../common').readUInt16BE
var str2arr = require('../common').str2arr
var sliceEq = require('../common').sliceEq
var exif = require('../exif_utils')
var SIG_EXIF = str2arr('Exif\0\0')
module.exports = function (data) {
if (data.length < 2) return
// first marker of the file MUST be 0xFFD8,
// following by either 0xFFE0, 0xFFE2 or 0xFFE3
if (data[0] !== 0xFF || data[1] !== 0xD8 || data[2] !== 0xFF) return
var offset = 2
for (;;) {
// skip until we see 0xFF, see https://github.com/nodeca/probe-image-size/issues/68
for (;;) {
if (data.length - offset < 2) return
if (data[offset++] === 0xFF) break
}
var code = data[offset++]
var length
// skip padding bytes
while (code === 0xFF) code = data[offset++]
// 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) {
length = 0
} else if (code >= 0xC0 && code <= 0xFE) {
// the rest of the unreserved markers
if (data.length - offset < 2) return
length = readUInt16BE(data, offset) - 2
offset += 2
} else {
// unknown markers
return
}
if (code === 0xD9 /* EOI */ || code === 0xDA /* SOS */) {
// end of the datastream
return
}
var orientation
// try to get orientation from Exif segment
if (code === 0xE1 && length >= 10 && sliceEq(data, offset, SIG_EXIF)) {
orientation = exif.get_orientation(data.slice(offset + 6, offset + length))
}
if (length >= 5 &&
(code >= 0xC0 && code <= 0xCF) &&
code !== 0xC4 && code !== 0xC8 && code !== 0xCC) {
if (data.length - offset < length) return
var result = {
width: readUInt16BE(data, offset + 3),
height: readUInt16BE(data, offset + 1),
type: 'jpg',
mime: 'image/jpeg',
wUnits: 'px',
hUnits: 'px'
}
if (orientation > 0) {
result.orientation = orientation
}
return result
}
offset += length
}
}
+30
View File
@@ -0,0 +1,30 @@
'use strict'
var str2arr = require('../common').str2arr
var sliceEq = require('../common').sliceEq
var readUInt32BE = require('../common').readUInt32BE
var SIG_PNG = str2arr('\x89PNG\r\n\x1a\n')
var SIG_IHDR = str2arr('IHDR')
module.exports = function (data) {
if (data.length < 24) return
// check PNG signature
if (!sliceEq(data, 0, SIG_PNG)) return
// check that first chunk is IHDR
if (!sliceEq(data, 12, SIG_IHDR)) return
return {
width: readUInt32BE(data, 16),
height: readUInt32BE(data, 20),
type: 'png',
mime: 'image/png',
wUnits: 'px',
hUnits: 'px'
}
}
+26
View File
@@ -0,0 +1,26 @@
'use strict'
var str2arr = require('../common').str2arr
var sliceEq = require('../common').sliceEq
var readUInt32BE = require('../common').readUInt32BE
var SIG_8BPS = str2arr('8BPS\x00\x01')
module.exports = function (data) {
if (data.length < 6 + 16) return
// signature + version
if (!sliceEq(data, 0, SIG_8BPS)) return
return {
width: readUInt32BE(data, 6 + 12),
height: readUInt32BE(data, 6 + 8),
type: 'psd',
mime: 'image/vnd.adobe.photoshop',
wUnits: 'px',
hUnits: 'px'
}
}
+145
View File
@@ -0,0 +1,145 @@
'use strict'
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 canBeSvg (buf) {
var i = 0
var max = buf.length
// byte order mark, https://github.com/nodeca/probe-image-size/issues/57
if (buf[0] === 0xEF && buf[1] === 0xBB && buf[2] === 0xBF) i = 3
while (i < max && isWhiteSpace(buf[i])) i++
if (i === max) return false
return buf[i] === 0x3c /* < */
}
// 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 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]
}
module.exports = function (data) {
if (!canBeSvg(data)) return
var str = ''
for (var i = 0; i < data.length; i++) {
// 1. We can't rely on buffer features
// 2. Don't care about UTF16 because ascii is enougth for our goals
str += String.fromCharCode(data[i])
}
// 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)
}
}
+85
View File
@@ -0,0 +1,85 @@
'use strict'
var str2arr = require('../common').str2arr
var sliceEq = require('../common').sliceEq
var readUInt16LE = require('../common').readUInt16LE
var readUInt16BE = require('../common').readUInt16BE
var readUInt32LE = require('../common').readUInt32LE
var readUInt32BE = require('../common').readUInt32BE
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 ? readUInt16BE(buffer, offset) : readUInt16LE(buffer, offset)
}
function readUInt32 (buffer, offset, is_big_endian) {
return is_big_endian ? readUInt32BE(buffer, offset) : readUInt32LE(buffer, 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 (data) {
if (data.length < 8) return
// check TIFF signature
if (!sliceEq(data, 0, SIG_1) && !sliceEq(data, 0, SIG_2)) return
var is_big_endian = (data[0] === 77 /* 'MM' */)
var count = readUInt32(data, 4, is_big_endian) - 8
if (count < 0) return
// skip until IFD
var offset = count + 8
if (data.length - offset < 2) return
// read number of IFD entries
var ifd_size = readUInt16(data, offset + 0, is_big_endian) * 12
if (ifd_size <= 0) return
offset += 2
// read all IFD entries
if (data.length - offset < ifd_size) return
var i, width, height, tag
for (i = 0; i < ifd_size; i += 12) {
tag = readUInt16(data, offset + i, is_big_endian)
if (tag === 256) {
width = readIFDValue(data, offset + i, is_big_endian)
} else if (tag === 257) {
height = readIFDValue(data, offset + i, is_big_endian)
}
}
if (width && height) {
return {
width: width,
height: height,
type: 'tiff',
mime: 'image/tiff',
wUnits: 'px',
hUnits: 'px'
}
}
}
+108
View File
@@ -0,0 +1,108 @@
'use strict'
var str2arr = require('../common').str2arr
var sliceEq = require('../common').sliceEq
var readUInt16LE = require('../common').readUInt16LE
var readUInt32LE = require('../common').readUInt32LE
var exif = require('../exif_utils')
var SIG_RIFF = str2arr('RIFF')
var SIG_WEBP = str2arr('WEBP')
function parseVP8 (data, offset) {
if (data[offset + 3] !== 0x9D || data[offset + 4] !== 0x01 || data[offset + 5] !== 0x2A) {
// bad code block signature
return
}
return {
width: readUInt16LE(data, offset + 6) & 0x3FFF,
height: readUInt16LE(data, offset + 8) & 0x3FFF,
type: 'webp',
mime: 'image/webp',
wUnits: 'px',
hUnits: 'px'
}
}
function parseVP8L (data, offset) {
if (data[offset] !== 0x2F) return
var bits = readUInt32LE(data, offset + 1)
return {
width: (bits & 0x3FFF) + 1,
height: ((bits >> 14) & 0x3FFF) + 1,
type: 'webp',
mime: 'image/webp',
wUnits: 'px',
hUnits: 'px'
}
}
function parseVP8X (data, offset) {
return {
// TODO: replace with `data.readUIntLE(8, 3) + 1`
// when 0.10 support is dropped
width: ((data[offset + 6] << 16) | (data[offset + 5] << 8) | data[offset + 4]) + 1,
height: ((data[offset + 9] << 16) | (data[offset + 8] << 8) | data[offset + 7]) + 1,
type: 'webp',
mime: 'image/webp',
wUnits: 'px',
hUnits: 'px'
}
}
module.exports = function (data) {
if (data.length < 16) return
// check /^RIFF....WEBPVP8([ LX])$/ signature
if (!sliceEq(data, 0, SIG_RIFF) || !sliceEq(data, 8, SIG_WEBP)) return
var offset = 12
var result = null
var exif_orientation = 0
var fileLength = readUInt32LE(data, 4) + 8
if (fileLength > data.length) return
while (offset + 8 < fileLength) {
if (data[offset] === 0) {
// after each chunk of odd size there should be 0 byte of padding, skip those
offset++
continue
}
var header = String.fromCharCode.apply(null, data.slice(offset, offset + 4))
var length = readUInt32LE(data, offset + 4)
if (header === 'VP8 ' && length >= 10) {
result = result || parseVP8(data, offset + 8)
} else if (header === 'VP8L' && length >= 5) {
result = result || parseVP8L(data, offset + 8)
} else if (header === 'VP8X' && length >= 10) {
result = result || parseVP8X(data, offset + 8)
} else if (header === 'EXIF') {
exif_orientation = exif.get_orientation(data.slice(offset + 8, offset + 8 + length))
// exif is the last chunk we care about, stop after it
offset = Infinity
}
offset += 8 + length
}
if (!result) return
if (exif_orientation > 0) {
result.orientation = exif_orientation
}
return result
}