Skip to content
LogoLogo

@portable/image-size

Reads an image's type and dimensions from its header bytes or from a stream, reading only as far as the header. Runs in browsers, Cloudflare Workers, Deno, Bun and Node 24+.

Replaces image-size.

npm install @portable/image-size
import * as ImageSize from '@portable/image-size';
 
const response = await fetch('https://example.com/photo.jpg');
const { width, height, type } = await ImageSize.parseStream(
  response.body!,
);
// { width: 1600, height: 1200, type: 'jpg', orientation: 1 }

parseStream stops reading and cancels the stream once it has the bytes that hold the dimensions. For a JPEG that means the segments up to its frame header (5 KB of the 730 KB large.jpg fixture), and for a PNG the first 24 bytes.

API

parse(bytes: Uint8Array): Size

Returns the size of the image in bytes. The bytes only have to cover the header.

parseStream(stream: ReadableStream<Uint8Array>, options?): Promise<Size>

Reads stream until the size is known, then cancels it.

OptionDefaultDescription
maxBytes524288The most bytes that parseStream buffers. If the header isn't complete by then, parseStream rejects with MaxBytesError, which stores the limit as maxBytes. ICNS files list their icons throughout the file, so a large one needs a higher limit. A negative or NaN value throws a RangeError.
signalnoneAborting rejects with signal.reason and cancels the stream.

Size

FieldDescription
width, heightPixels. For a file with several images, the largest by area.
typebmp, cur, dds, gif, icns, ico, j2c, jp2, jpg, jxl (container), jxl-stream (bare codestream), ktx, ktx2, png, pnm, psd, svg, tga, tiff, bigtiff or webp. HEIF files report their brand: avif, avis, heic, heix, hevc, hevx, mif1 or msf1.
orientationThe EXIF orientation of a JPEG (1 to 8), when it has one. width and height are the stored dimensions, before rotation.
imagesEvery image in an ICO, CUR or ICNS file with more than one. ICNS entries also have their icon type, such as ic08.

The size of an animated PNG is the canvas size, with type png. The size of a HEIF file is the size of its primary image, cropped by its clean aperture (clap) and rotated by its irot property.

Errors

ErrorThrown when
UnsupportedErrorThe bytes don't match a supported format.
TruncatedErrorThe input ends before the dimensions, including an empty input.
InvalidInputErrorThe header is malformed, such as a PNG without an IHDR chunk or an SVG with neither size nor viewBox.
MaxBytesErrorparseStream read maxBytes without finding the dimensions. Its maxBytes holds the limit.

Their names are ImageSize.UnsupportedError, ImageSize.TruncatedError, ImageSize.InvalidInputError and ImageSize.MaxBytesError.

Types

TypeDescription
ImageSize.SizeWhat parse and parseStream return, described above.
ImageSize.TypeThe type values listed under Size.
ImageSize.Image{ width, height, type? }, one entry of images for a file that holds several images.

Why use this over image-size

parseStream reads a fetch body or a File only up to the header and then cancels it, in any JavaScript runtime. The package has no dependencies outside @portable/*.

  • parseStream reads 5 KB of the 730 KB large.jpg. Collecting the same stream and passing it to image-size was about 70× slower, and image-size's imageSizeFromFile reads a fixed 512 KiB whatever the format needs.
  • parse read large.jpg about 3,800× faster than image-size, and was 3.6 to 360× faster on the PNG, HEIC, WebP, SVG and TIFF fixtures.
  • Input that ends early gives TruncatedError. image-size throws a RangeError from DataView.
  • HEIF sizes come from the primary item, with the clean aperture and rotation applied. image-size reports a 16×64 image as 63×64 and gives landscape dimensions for a portrait photo stored rotated.
  • Lossless and arithmetic-coded JPEGs are read, which image-size rejects, and there is no global disableTypes state shared by every caller in the process.
  • image-size throws a TypeError for an unsupported file type. parse throws UnsupportedError instead, and TruncatedError or InvalidInputError for a broken file, so a catch that checks for TypeError has to check for these errors instead.
  • image-size skips the JPEG marker right after the start-of-image marker without reading it, so it misses a frame header there, and it assumes the Exif IFD0 starts 8 bytes into the TIFF header. parse reads that marker and follows the IFD0 offset the header stores.
  • image-size lists every HEIF ispe property in images, which for a tiled photo means every tile. parse returns no images list for HEIF files, and it detects animated AVIF (avis), which image-size doesn't.
  • image-size reads TIFF tags past the IFD's entry count until it finds a zero tag, so data after the IFD can overwrite the width and height, and it returns an undocumented compression field. parse reads only the entries the first IFD declares and returns no compression field.
  • image-size lists ICNS entries that aren't icons, such as the table of contents, and returns the icons found so far when the file is cut short. parse skips those entries and throws TruncatedError for a short file.
  • image-size splits an SVG viewBox on single spaces only, so it gives no size for 0,0,100,100. parse accepts commas and repeated spaces.
  • image-size needs a PNM file's width and height on one line, separated by one space, and returns NaN for dimensions that aren't numbers. parse reads them across lines with comments between them, and throws InvalidInputError for dimensions that aren't numbers.
@portable/image-sizeimage-size
Read the size of a 730 KB JPEG from a stream88,7001,250
Read the size of a 730 KB JPEG6,840,0001,800
Read the size of a PNG2,590,000439,000
Read the size of a HEIC image201,00056,600
Read the size of a WebP image2,470,000347,000
Read the size of an SVG550,00086,500
Read the size of a TIFF8,530,00024,000

Operations per second from pnpm bench, higher is better, in Node 24.22.0, on Intel(R) Xeon(R) Processor @ 2.10GHz (4 cores). A dash means the library has no equivalent.

@portable/image-sizeimage-size
Minified and gzipped5.3 kB4.6 kB
Runtime dependencies20
Node built-ins usednonenone
TypeScript typesincludedincluded
Version compared–2.0.4

Sizes cover the imports in the usage example, or the equivalent imports of each library, bundled for the browser by scripts/bundle-size.ts. Runtime dependencies count every package installed with the published package. A library that uses Node built-ins needs polyfills in a browser and the nodejs_compat flag in Cloudflare Workers.