probe-image-size
Advanced tools
Comparing version 6.0.0 to 7.0.0
@@ -9,2 +9,8 @@ # Changelog | ||
## [7.0.0] - 2021-03-11 | ||
### Added | ||
- Add AVIF/HEIC/HEIF support. | ||
- Add orientation info. | ||
## [6.0.0] - 2020-11-04 | ||
@@ -178,2 +184,3 @@ ### Added | ||
[7.0.0]: https://github.com/nodeca/probe-image-size/compare/6.0.0...7.0.0 | ||
[6.0.0]: https://github.com/nodeca/probe-image-size/compare/5.0.0...6.0.0 | ||
@@ -180,0 +187,0 @@ [5.0.0]: https://github.com/nodeca/probe-image-size/compare/4.1.1...5.0.0 |
'use strict'; | ||
/* eslint-disable consistent-return */ | ||
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 | ||
@@ -52,3 +60,4 @@ function parseJpegMarker_afterFF(parser, callback) { | ||
function getJpegSize(parser) { | ||
// sandbox is a storage for intermediate data retrieved from jpeg while parsing it | ||
function getJpegSize(parser, sandbox) { | ||
parseJpegMarker(parser, function (code, length) { | ||
@@ -69,5 +78,17 @@ if (!code || length < 0) { | ||
// 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); | ||
getJpegSize(parser, sandbox); | ||
return; | ||
@@ -83,3 +104,3 @@ } | ||
parser.push({ | ||
var result = { | ||
width: data.readUInt16BE(3), | ||
@@ -91,4 +112,7 @@ height: data.readUInt16BE(1), | ||
hUnits: 'px' | ||
}); | ||
}; | ||
if (sandbox.orientation > 0) result.orientation = sandbox.orientation; | ||
parser.push(result); | ||
parser.push(null); | ||
@@ -100,3 +124,3 @@ }); | ||
parser._skipBytes(length, function () { | ||
getJpegSize(parser); | ||
getJpegSize(parser, sandbox); | ||
}); | ||
@@ -118,3 +142,3 @@ }); | ||
getJpegSize(parser); | ||
getJpegSize(parser, {}); | ||
}); | ||
@@ -121,0 +145,0 @@ |
'use strict'; | ||
/* eslint-disable no-bitwise */ | ||
/* eslint-disable no-use-before-define */ | ||
@@ -8,28 +9,37 @@ var ParserStream = require('../common').ParserStream; | ||
var sliceEq = require('../common').sliceEq; | ||
var exif = require('../exif_utils'); | ||
var SIG_RIFF = str2arr('RIFF'); | ||
var SIG_WEBPVP8 = str2arr('WEBPVP8'); | ||
var SIG_RIFF = str2arr('RIFF'); | ||
var SIG_WEBP = str2arr('WEBP'); | ||
function parseVP8(parser) { | ||
parser._bytes(14, function (data) { | ||
parser._skipBytes(Infinity); | ||
function safeSkip(parser, count, callback) { | ||
if (count === 0) { // parser._skipBytes throws error if count === 0 | ||
callback(); | ||
return; | ||
} | ||
if (data[7] !== 0x9D || data[8] !== 0x01 || data[9] !== 0x2A) { | ||
// bad code block signature | ||
parser.push(null); | ||
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' | ||
}; | ||
} | ||
parser.push({ | ||
width: data.readUInt16LE(10) & 0x3FFF, | ||
height: data.readUInt16LE(12) & 0x3FFF, | ||
type: 'webp', | ||
mime: 'image/webp', | ||
wUnits: 'px', | ||
hUnits: 'px' | ||
safeSkip(parser, length - 10, function () { | ||
sandbox.offset += length; | ||
getWebpSize(parser, sandbox); | ||
}); | ||
parser.push(null); | ||
}); | ||
@@ -39,17 +49,33 @@ } | ||
function parseVP8L(parser) { | ||
parser._bytes(9, function (data) { | ||
parser._skipBytes(Infinity); | ||
function parseVP8L(parser, length, sandbox) { | ||
parser._bytes(5, function (data) { | ||
// check code block signature | ||
if (data[0] === 0x2F) { | ||
var bits = data.readUInt32LE(1); | ||
if (data[4] !== 0x2F) { | ||
// bad code block signature | ||
parser.push(null); | ||
return; | ||
sandbox.result = sandbox.result || { | ||
width: (bits & 0x3FFF) + 1, | ||
height: ((bits >> 14) & 0x3FFF) + 1, | ||
type: 'webp', | ||
mime: 'image/webp', | ||
wUnits: 'px', | ||
hUnits: 'px' | ||
}; | ||
} | ||
var bits = data.readUInt32LE(5); | ||
safeSkip(parser, length - 5, function () { | ||
sandbox.offset += length; | ||
getWebpSize(parser, sandbox); | ||
}); | ||
}); | ||
} | ||
parser.push({ | ||
width: (bits & 0x3FFF) + 1, | ||
height: ((bits >> 14) & 0x3FFF) + 1, | ||
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', | ||
@@ -59,5 +85,19 @@ mime: 'image/webp', | ||
hUnits: 'px' | ||
}; | ||
safeSkip(parser, length - 10, function () { | ||
sandbox.offset += length; | ||
getWebpSize(parser, sandbox); | ||
}); | ||
}); | ||
} | ||
parser.push(null); | ||
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); | ||
}); | ||
@@ -67,18 +107,54 @@ } | ||
function parseVP8X(parser) { | ||
parser._bytes(14, function (data) { | ||
function getWebpSize(parser, sandbox) { | ||
if (sandbox.fileLength - 8 <= sandbox.offset) { | ||
parser._skipBytes(Infinity); | ||
parser.push({ | ||
// TODO: replace with `data.readUIntLE(8, 3) + 1` | ||
// when 0.10 support is dropped | ||
width: ((data[10] << 16) | (data[9] << 8) | data[8]) + 1, | ||
height: ((data[13] << 16) | (data[12] << 8) | data[11]) + 1, | ||
type: 'webp', | ||
mime: 'image/webp', | ||
wUnits: 'px', | ||
hUnits: 'px' | ||
}); | ||
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); | ||
}); | ||
} | ||
}); | ||
}); | ||
@@ -91,12 +167,12 @@ } | ||
parser._bytes(16, function (data) { | ||
parser._bytes(12, function (data) { | ||
// check /^RIFF....WEBPVP8([ LX])$/ signature | ||
if (sliceEq(data, 0, SIG_RIFF) && sliceEq(data, 8, SIG_WEBPVP8)) { | ||
switch (data[15]) { | ||
case 32/*' '*/: parseVP8(parser); return; | ||
case 76/* L */: parseVP8L(parser); return; | ||
case 88/* X */: parseVP8X(parser); return; | ||
} | ||
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 { | ||
@@ -103,0 +179,0 @@ parser._skipBytes(Infinity); |
@@ -6,4 +6,10 @@ '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) { | ||
@@ -48,2 +54,9 @@ if (data.length < 2) 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 && | ||
@@ -55,3 +68,3 @@ (0xC0 <= code && code <= 0xCF) && | ||
return { | ||
var result = { | ||
width: readUInt16BE(data, offset + 3), | ||
@@ -64,2 +77,8 @@ height: readUInt16BE(data, offset + 1), | ||
}; | ||
if (orientation > 0) { | ||
result.orientation = orientation; | ||
} | ||
return result; | ||
} | ||
@@ -66,0 +85,0 @@ |
@@ -10,12 +10,11 @@ 'use strict'; | ||
var readUInt32LE = require('../common').readUInt32LE; | ||
var exif = require('../exif_utils'); | ||
var SIG_RIFF = str2arr('RIFF'); | ||
var SIG_WEBPVP8 = str2arr('WEBPVP8'); | ||
var SIG_RIFF = str2arr('RIFF'); | ||
var SIG_WEBP = str2arr('WEBP'); | ||
function parseVP8(data) { | ||
if (data.length < 16 + 14) return; | ||
if (data[16 + 7] !== 0x9D || data[16 + 8] !== 0x01 || data[16 + 9] !== 0x2A) { | ||
function parseVP8(data, offset) { | ||
if (data[offset + 3] !== 0x9D || data[offset + 4] !== 0x01 || data[offset + 5] !== 0x2A) { | ||
// bad code block signature | ||
@@ -26,4 +25,4 @@ return; | ||
return { | ||
width: readUInt16LE(data, 16 + 10) & 0x3FFF, | ||
height: readUInt16LE(data, 16 + 12) & 0x3FFF, | ||
width: readUInt16LE(data, offset + 6) & 0x3FFF, | ||
height: readUInt16LE(data, offset + 8) & 0x3FFF, | ||
type: 'webp', | ||
@@ -37,9 +36,7 @@ mime: 'image/webp', | ||
function parseVP8L(data) { | ||
if (data.length < 16 + 9) return; | ||
function parseVP8L(data, offset) { | ||
if (data[offset] !== 0x2F) return; | ||
if (data[16 + 4] !== 0x2F) return; | ||
var bits = readUInt32LE(data, offset + 1); | ||
var bits = readUInt32LE(data, 16 + 5); | ||
return { | ||
@@ -56,10 +53,8 @@ width: (bits & 0x3FFF) + 1, | ||
function parseVP8X(data) { | ||
if (data.length < 16 + 14) return; | ||
function parseVP8X(data, offset) { | ||
return { | ||
// TODO: replace with `data.readUIntLE(8, 3) + 1` | ||
// when 0.10 support is dropped | ||
width: ((data[16 + 10] << 16) | (data[16 + 9] << 8) | data[16 + 8]) + 1, | ||
height: ((data[16 + 13] << 16) | (data[16 + 12] << 8) | data[16 + 11]) + 1, | ||
width: ((data[offset + 6] << 16) | (data[offset + 5] << 8) | data[offset + 4]) + 1, | ||
height: ((data[offset + 9] << offset) | (data[offset + 8] << 8) | data[offset + 7]) + 1, | ||
type: 'webp', | ||
@@ -77,9 +72,44 @@ mime: 'image/webp', | ||
// check /^RIFF....WEBPVP8([ LX])$/ signature | ||
if (sliceEq(data, 0, SIG_RIFF) && sliceEq(data, 8, SIG_WEBPVP8)) { | ||
switch (data[15]) { | ||
case 32/*' '*/: return parseVP8(data); | ||
case 76/* L */: return parseVP8L(data); | ||
case 88/* X */: return parseVP8X(data); | ||
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 >= 9) { | ||
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; | ||
}; |
'use strict'; | ||
module.exports = { | ||
avif: require('./parse_stream/avif'), | ||
bmp: require('./parse_stream/bmp'), | ||
@@ -5,0 +6,0 @@ gif: require('./parse_stream/gif'), |
@@ -5,6 +5,7 @@ 'use strict'; | ||
module.exports = { | ||
avif: require('./parse_sync/avif'), | ||
bmp: require('./parse_sync/bmp'), | ||
gif: require('./parse_sync/gif'), | ||
ico: require('./parse_sync/ico'), | ||
jpeg: require('./parse_sync/jpeg'), | ||
ico: require('./parse_sync/ico'), | ||
png: require('./parse_sync/png'), | ||
@@ -11,0 +12,0 @@ psd: require('./parse_sync/psd'), |
{ | ||
"name": "probe-image-size", | ||
"version": "6.0.0", | ||
"version": "7.0.0", | ||
"description": "Get image size without full download (JPG, GIF, PNG, WebP, BMP, TIFF, PSD)", | ||
@@ -32,3 +32,3 @@ "keywords": [ | ||
"coverage": "npm run test && nyc report --reporter html", | ||
"report-coveralls": "nyc report --reporter=text-lcov | coveralls" | ||
"report-coveralls": "nyc --reporter=lcov mocha" | ||
}, | ||
@@ -44,3 +44,2 @@ "mocha": { | ||
"devDependencies": { | ||
"coveralls": "^3.0.0", | ||
"eslint": "^7.12.1", | ||
@@ -47,0 +46,0 @@ "mocha": "^8.2.0", |
probe-image-size | ||
================ | ||
[![Build Status](https://img.shields.io/travis/nodeca/probe-image-size/master.svg?style=flat)](https://travis-ci.org/nodeca/probe-image-size) | ||
[![CI](https://github.com/nodeca/probe-image-size/workflows/CI/badge.svg?branch=master)](https://github.com/nodeca/probe-image-size/actions) | ||
[![NPM version](https://img.shields.io/npm/v/probe-image-size.svg?style=flat)](https://www.npmjs.org/package/probe-image-size) | ||
@@ -9,3 +9,3 @@ [![Coverage Status](https://coveralls.io/repos/github/nodeca/probe-image-size/badge.svg?branch=master)](https://coveralls.io/github/nodeca/probe-image-size?branch=master) | ||
> Get image size without full download. Supported image types: | ||
> JPG, GIF, PNG, WebP, BMP, TIFF, SVG, PSD, ICO. | ||
> JPG, GIF, PNG, WebP, BMP, TIFF, SVG, PSD, ICO, AVIF, HEIC, HEIF. | ||
@@ -96,6 +96,18 @@ Key features: | ||
// (if no redirects, same as src) | ||
variants: [ { width, height }, ... ] | undefined // full list of sizes for ICO | ||
// optional, image orientation (from Exif), number from 1 to 8; | ||
// you may wish to swap width and height if orientation is >= 5 | ||
orientation: X, | ||
// optional, full list of sizes for ICO (always) and AVIF (if multiple images) | ||
variants: [ { width, height }, ... ] | undefined | ||
} | ||
``` | ||
Width and height in the output object represent image size *before* any transformations | ||
(orientation, cropping) are applied. Orientation is returned separately, which you may | ||
wish to apply afterwards depending on browser support (browsers | ||
[only support JPEG](https://zpl.fi/exif-orientation-in-different-formats/) orientation for now). | ||
See [known issues](known_issues.md) for details. | ||
Returned errors can be extended with 2 fields: | ||
@@ -102,0 +114,0 @@ |
@@ -13,2 +13,5 @@ 'use strict'; | ||
// increase max number of listeners to stop memory leak warning | ||
proxy.setMaxListeners(Object.keys(parsers).length + 10); | ||
var result = new Promise(function (resolve, reject) { | ||
@@ -15,0 +18,0 @@ src.on('error', reject); |
73308
3
33
1923
138