ImageLibrary
Create a frozen library with the codec implementations your application accepts. Registration order is preserved in formats(); duplicate format names are rejected.
function createImageLibrary(registration: Iterable<ImageCodec> | ImageLibraryConfiguration): ImageLibrary
interface ImageLibrary {
formats(): readonly string[]
open(input: ImageInput, options?: ImageOpenOptions): Promise<Image>
}
interface ImageOpenOptions {
frame?: number
resolutionLevel?: number
tolerantDecoding?: boolean
limits?: ImageLimitOptions
}formats()
Returns the format names registered in this library.
returns readonly string[]open(input, options?)
Creates a source, validates optional zero-based frame and resolution-level selections, enforces the input-byte limit, probes registered codecs, and returns a lazy image pipeline. TIFF supports explicit top-level frame selection and resolutionLevel selection, where level 0 is the full-resolution frame and later levels are reduced-resolution SubIFDs ordered from largest to smallest. Codecs reject nonzero selections they do not support. Static GIFs decode without a selection; animated GIF metadata remains available, but pixel output requires an explicit { frame: 0 } to prevent silent animation loss. Baseline JPEG restart recovery defaults to tolerantDecoding: true for compatibility with malformed real-world files. Set { tolerantDecoding: false } to require exact restart sequencing and permit an explicitly registered JPEG decode accelerator.
Codec imports
| Entry | Named export |
|---|---|
purejsimage/codecs/jpeg | jpegCodec |
purejsimage/codecs/jpeg2000 | jpeg2000Codec |
purejsimage/codecs/png | pngCodec |
purejsimage/codecs/gif | gifCodec |
purejsimage/codecs/ico | icoCodec |
purejsimage/codecs/webp | webpCodec |
purejsimage/codecs/bmp | bmpCodec |
purejsimage/codecs/tiff | tiffCodec |
purejsimage/codecs/avif | avifCodec |
purejsimage/codecs/experimental/heic | experimentalHeicCodec, experimentalHeifCodec |
purejsimage/codecs/all | allCodecs |
allCodecs contains the nine default codecs and intentionally excludes experimental HEIF/HEIC.
Experimental HEIC opt-in
import { createImageLibrary } from 'purejsimage'
import { allCodecs } from 'purejsimage/codecs/all'
import { experimentalHeicCodec } from 'purejsimage/codecs/experimental/heic'
const images = createImageLibrary({
codecs: [...allCodecs, experimentalHeicCodec],
})allCodecs, or the default browser demo. See the experimental HEIF/HEIC contract.TIFF documents, rasters, and profiles
purejsimage/tiff exposes the validated TIFF IFD graph independently of the display-image pipeline. openTiffDocument(source, options?) returns a TiffDocument with stable top-level and SubIFD directory objects. Each TiffDirectory exposes its absolute source offset; getDirectoryByOffset(offset) resolves parsed metadata pointers. A directory reads bounded, cached public tag values and creates either an ImageDecoder for display pixels or a RasterDecoder for native-precision, arbitrary-channel samples. readBytes(offset, length, { maxBytes }) performs an exact bounded private-metadata read and returns a defensive copy without exposing the underlying ImageSource.
import { MemorySource } from 'purejsimage'
import { openOmeTiff } from 'purejsimage/scientific'
import { openTiffDocument } from 'purejsimage/tiff'
const document = await openTiffDocument(new MemorySource(bytes))
const dataset = await openOmeTiff(document)
for await (const block of dataset.readPlane({
z: 0, c: [0, 1, 2], t: 0, resolutionLevel: 0,
x: 0, y: 0, width: 512, height: 512,
})) {
consume(block)
}openOmeTiff() validates OME dimensions, sample type, channel metadata, physical pixel size, dimension order, and explicit or implicit TiffData mappings before exposing a MultidimensionalRasterDataset. readPlane() preserves native sample types and can combine separately stored channel planes without materializing a source-sized display bitmap.
purejsimage/pathology exports openAperioSvs(), aperioSvsProfile, and the generic WholeSlideImage contract. Aperio levels and label, macro, or thumbnail images decode by requested region. TIFF compression tags 33003 and 33005 use the reusable createJpeg2000CodestreamDecoder(); JPEG 2000 reconstruction remains bounded to the current TIFF tile or strip, though the current codestream decoder retains that segment's complete component state.
Third-party TIFF profile authoring
A separately shipped integration implements TiffProfile<T> using only package exports. Register profiles with createTiffProfileRegistry(). Detection runs every profile in isolation, orders matches by descending integer priority then id, reports detector failures, and rejects equal-priority matches as ambiguous. For an explicitly selected profile, registry.openWith(document, profile) preserves T and returns Promise<T>.
import { createTiffProfileRegistry } from 'purejsimage/tiff'
import type { TiffProfile } from 'purejsimage/tiff'
export const vendorProfile: TiffProfile<VendorDataset> = {
id: 'vendor-format',
priority: 50,
async detect({ document }) {
const tag = await document.topLevelDirectories[0]?.getTag(270, {
maxBytes: 1024 * 1024,
})
return tag?.kind === 'ascii' && tag.value.startsWith('Vendor')
},
async open({ document }) {
const pointer = await document.topLevelDirectories[0]?.getTag(65000, {
maxBytes: 24,
})
if (pointer?.kind !== 'numbers') throw new Error('Vendor pointer is missing')
const directory = document.getDirectoryByOffset(pointer.values[0] ?? -1)
if (!directory) throw new Error('Vendor IFD is missing')
const length = pointer.values[2] ?? -1
const metadata = await document.readBytes(
pointer.values[1] ?? -1, length, { maxBytes: 1024 * 1024 },
)
return openVendorDataset(directory, metadata)
},
}
const registry = createTiffProfileRegistry([vendorProfile])
const dataset = await registry.openWith(document, vendorProfile)getTag(tag, { maxBytes }) or readBytes(offset, length, { maxBytes }), validate metadata-to-IFD mappings before decoding, and retain structured unsupported errors for ambiguous vendor semantics. Tag values are cached, but each caller's limit is checked before cache access; byte-valued tags and raw reads return defensive copies. The checked examples/tiff-profile-leica driver compiles as an external package and imports only purejsimage, purejsimage/tiff, and purejsimage/pathology.Zstandard decompression
The portable purejsimage/compression/zstd subpath exports the reusable first-party decoder used by TIFF Compression=50000. It has no format-specific parameters and does not load native code, WebAssembly, or a runtime dependency.
import { decodeZstd } from 'purejsimage/compression/zstd'
const output = decodeZstd(compressed, {
expectedOutputBytes: 8192,
maxOutputBytes: 8192,
maxWindowBytes: 64 * 1024 * 1024,
})maxOutputBytes and maxWindowBytes default to 64 MiB. Supplying expectedOutputBytes preallocates the exact bounded output and rejects a different decoded size. Dictionary-dependent frames fail with UNSUPPORTED_OPERATION.
Optional WASM acceleration
JPEG and PNG provide first-party WebAssembly accelerators as explicit package entries. The root, browser, and codec imports never load them automatically. Register either provider through ImageLibraryConfiguration; ineligible or unavailable work continues through the corresponding TypeScript codec.
import { createImageLibrary } from 'purejsimage'
import { wasmJpegAccelerator } from 'purejsimage/accelerators/wasm/jpeg'
import { wasmPngAccelerator } from 'purejsimage/accelerators/wasm/png'
import { allCodecs } from 'purejsimage/codecs/all'
const images = createImageLibrary({
codecs: allCodecs,
accelerators: [wasmJpegAccelerator, wasmPngAccelerator],
})Browser applications use createImageLibrary from purejsimage/browser; the two accelerator import paths are unchanged and resolve to browser-safe loaders.
Configuration
The ready-made providers use defaults chosen to avoid paying module and copy overhead on small images. Use the factory exports when an application needs different thresholds or input limits.
interface WasmJpegAcceleratorOptions {
minimumPixels?: number
minimumEncodePixels?: number
maximumInputBytes?: number
maximumEncoderRowBytes?: number
}
interface WasmPngAcceleratorOptions {
minimumPixels?: number
minimumEncodePixels?: number
maximumRowBytes?: number
}
function createWasmJpegAccelerator(options?: WasmJpegAcceleratorOptions): ImageCodecAccelerator
function createWasmPngAccelerator(options?: WasmPngAcceleratorOptions): ImageCodecAcceleratorBoth pixel thresholds default to 65,536. JPEG accepts at most 32 MiB of compressed input and a 16 MiB encoder row by default; PNG accepts at most a 16 MiB row. Every supplied value must be a positive integer.
Eligible workflows
| Provider | Accelerated work | TypeScript fallback examples |
|---|---|---|
| JPEG | Common full-image baseline YCbCr decode and baseline gray8, rgb8, or rgba8 encode | Progressive output, crop or scaled decode, ICC-transformed decode, metadata-preserving output, small images, and inputs beyond configured limits |
| PNG | Full-image, non-interlaced 8-bit grayscale, RGB, or RGBA decode without tRNS; adaptive-filter gray8, rgb8, or rgba8 encode | Palette, grayscale-alpha, sub-byte, 16-bit, tRNS, Adam7, APNG frames, crop, compression level 0, small images, and rows beyond configured limits |
zlib; browsers retain platform compression streams. Codec parsing, metadata, limits, CRC validation, sources, and sinks remain in JavaScript, and pixel processing remains bounded by rows or blocks.Image
Every transform returns a new immutable Image sharing the same input context. No pixel work occurs until an output method is called.
metadata()
Reads source metadata and applies planned transforms to width, height, format, alpha, and orientation fields without executing the pixel pipeline.
returns Promise<ImageMetadata>autoOrient()
Applies EXIF orientations 1–8 when present. Orientations that reorder rows use bounded-memory temporary tile storage.
returns ImagekeepExif()
Opts into retaining source EXIF in a compatible output. EXIF is stripped by default; pixel reorientation normalizes a retained orientation tag to 1.
returns ImagekeepIcc()
Opts into retaining a compatible source ICC profile instead of converting tagged samples and stripping the profile.
returns Imagecrop(options)
Crops to an integer rectangle. x and y may be zero; width and height must be positive. Out-of-bounds crops fail.
resize(options)
Resizes by one dimension or two. Supports contain, cover, fill, inside, and outside fits. Lanczos 3 is the default; nearest and bilinear sampling are available for explicit lower-cost resizing.
returns Imagewindow(options)
Maps numeric grayscale samples to gray8 through an explicit center and width, overriding the source's normal display range for this pipeline.
lut(options)
Applies a 256-entry lookup table. gray8 input may produce gray8, rgb8, or rgba8; rgba8 input uses independent interleaved channel tables and remains rgba8.
rotate(degrees, options?)
Rotates clockwise by any finite number of degrees. Quarter turns are exact; other angles use bilinear sampling and expand the canvas.
returns Imageflip()
Mirrors pixels vertically, from top to bottom.
returns Imageflop()
Mirrors pixels horizontally, from left to right.
returns Imagejpeg(options?)
Appends JPEG output. Quality is 1–100; background accepts transparent, #RRGGBB, or #RRGGBBAA; chroma subsampling is 420, 422, or 444.
returns Imagepng(options?)
Appends 8-bit PNG output. Node supports compression levels 0–9; browser CompressionStream output currently supports the default level 6.
returns Imagewebp(options?)
Appends static lossy or lossless WebP output. Lossy quality is 1–100. For lossless output prefer png(); WebP lossless is not yet size-competitive.
bmp(options?)
Appends BMP output. The alpha option selects 32-bit RGBA or 24-bit RGB output.
returns Imagetiff(options?)
Appends canonical Classic TIFF output: little-endian, chunky 8-bit RGB or RGBA, independently Deflate-compressed strips, and horizontal prediction. Node supports compression levels 0–9; browser CompressionStream output supports level 6.
returns Imageencode(format, options?)
Generic equivalent to the format-specific encoder methods for jpeg, png, webp, bmp, and tiff.
toBuffer(options?)
Executes the pipeline and collects encoded chunks into Node.js bytes. The runtime value remains a Buffer, exposed through the portable Uint8Array public type so consumers do not need Node ambient declarations.
toUint8Array(options?)
Executes and returns portable encoded bytes. Available in Node and browsers.
returns Promise<Uint8Array>toBlob(options?)
Executes and returns a Blob with the registered output codec MIME type. Intended for browser previews, uploads, and downloads.
returns Promise<Blob>toSink(sink, options?)
Executes into a custom ImageSink for streaming or application-owned output.
toFile(path, options?)
Node-only file output. If encoding fails or is cancelled, the partial output file is removed.
returns Promise<void>progressive: true to emit a refinement-based progressive JPEG. This retains compact quantized coefficient planes until all scans are written; baseline output remains the bounded-row default.Operation options
CropOptions
interface CropOptions {
x: number
y: number
width: number
height: number
}ResizeOptions
type ResizeFit = 'contain' | 'cover' | 'fill' | 'inside' | 'outside'
type ResizeKernel = 'nearest' | 'bilinear' | 'lanczos3'
type Background = 'transparent' | `#${string}`
type ResizeOptions = {
width?: number
height?: number
fit?: ResizeFit
position?: 'center'
background?: Background
withoutEnlargement?: boolean
kernel?: ResizeKernel
} // width or height is required| Option | Default | Notes |
|---|---|---|
fit | cover with two dimensions | Fit, position, and background require both width and height. |
position | center | Currently center only; valid with contain. |
background | transparent | Valid with contain. Accepts six- or eight-digit hex. |
kernel | lanczos3 | Lanczos 3 minimizes downscale aliasing. Bilinear is faster but lower quality; nearest preserves hard pixel edges. |
withoutEnlargement | false | Caps the scale at 1 where the fit permits it. |
WindowOptions and LutOptions
interface WindowOptions {
center: number
width: number
}
interface LutOptions {
table: Uint8Array // 256 × output channel count, interleaved
format: 'gray8' | 'rgb8' | 'rgba8'
}Windowing precedes geometric transforms and converts native numeric grayscale blocks directly to gray8. A grayscale LUT treats each table entry as an output color. An RGBA LUT maps each input channel through the corresponding interleaved channel table.
RotateOptions
interface RotateOptions {
background?: Background // transparent by default
}Positive degrees rotate clockwise. Arbitrary angles expand to the smallest integer canvas that contains the result and use the requested six- or eight-digit hex background, or transparency by default.
Encoder options
interface JpegEncodeOptions {
quality?: number // 1–100
progressive?: boolean // refinement-based output; false by default
background?: Background
chromaSubsampling?: '420' | '422' | '444'
restartInterval?: number // 0–65535; 0 disables restart markers
}
interface PngEncodeOptions { compressionLevel?: number } // 0–9
interface WebpEncodeOptions { lossless?: boolean; quality?: number }
interface BmpEncodeOptions { alpha?: boolean }
interface TiffEncodeOptions {
compression?: 'deflate'
predictor?: 'horizontal'
layout?: 'strips'
compressionLevel?: number // 0–9; browser supports 6
}EXIF and ICC preservation
Encoders strip EXIF and ICC metadata unless the pipeline calls keepExif() or keepIcc(). The two choices are independent and apply when the pipeline executes.
| Format | Read for preservation | Write on output |
|---|---|---|
| JPEG | EXIF and RGB ICC | EXIF and RGB ICC |
| PNG | EXIF and compatible ICC | EXIF and compatible ICC |
| WebP | EXIF and RGB ICC | EXIF and RGB ICC |
| HEIF / HEIC (experimental) | EXIF and RGB ICC | Not implemented |
| TIFF | Compatible ICC only | Compatible ICC only |
| Other formats | Not implemented | Not implemented |
With keepIcc(), tagged samples remain in their source color space and the original profile is embedded in the output, avoiding a double conversion. The profile must match the output pixel model. With keepExif(), autoOrient(), rotate(), flip(), and flop() normalize a retained EXIF orientation to 1 because the pixels have already moved.
ImageError instead of silently dropping requested metadata.ImageMetadata
interface ImageMetadata {
width: number
height: number
format: string
mimeType: string
hasAlpha: boolean
orientation?: number
colorSpace?: string
colorProfile?:
| { kind: 'icc'; description?: string }
| {
kind: 'nclx'
primaries: number
transferCharacteristics: number
matrixCoefficients: number
fullRange: boolean
}
bitDepth?: number
chromaSubsampling?: '400' | '411' | '420' | '422' | '440' | '444'
codecProfile?: number
frames?: number
}Optional fields depend on what the container and registered codec can determine safely. HEIF and AVIF expose validated ICC descriptions when present or the numeric nclx primaries, transfer characteristics, matrix coefficients, and range. Animated formats may report frame counts even when the current pixel decoder handles only a still subset.
Inputs & sources
type BrowserImageInput = ArrayBuffer | Blob | ImageSource | Uint8Array
type NodeImageInput = BrowserImageInput | string
interface ImageSource {
readonly size: number
read(offset: number, length: number, options?: { signal?: AbortSignal }): Promise<Uint8Array>
}MemorySource
Zero-copy reads over an ArrayBuffer or Uint8Array.
BlobSource
Range reads through Blob.slice().
FileSource
Node-only. Use await FileSource.open(path) to build a file-backed source explicitly.
HttpRangeSource
Optional bounded HTTP range reads with request and byte statistics. Import from purejsimage/sources/http-range. A per-read signal is combined with the source-lifetime signal and cancels an in-flight fetch.
For an in-range read, custom read() implementations must return exactly min(length, size - offset) bytes as a Uint8Array, or reject when the backing read fails. Returned bytes may be reused or invalidated when the next read begins. Short, oversized, detached, and rejected reads surface as ImageError results. Public open, metadata, decode, raster, whole-slide, source-read, and terminal output options accept an optional AbortSignal; cancellation aborts the active sink and is rethrown as AbortError.
Safety limits
Pass a partial limits object to open(). Every provided value must be a positive safe integer.
| Limit | Default | Enforcement |
|---|---|---|
maxWidth | 100,000 | Declared and transformed width |
maxHeight | 100,000 | Declared and transformed height |
maxPixels | 268,435,456 | Width × height using overflow-safe arithmetic |
maxInputBytes | 134,217,728 (128 MiB) | Before source contents are read |
maxFrames | 1,000 | Declared frame count |
maxDecodedBytes | 1,073,741,824 (1 GiB) | Worst-case dimensions and streaming expansion |
ImageError
type ImageErrorCode =
| 'INVALID_INPUT'
| 'LIMIT_EXCEEDED'
| 'TRUNCATED_INPUT'
| 'UNSUPPORTED_FORMAT'
| 'UNSUPPORTED_OPERATION'| Code | Meaning |
|---|---|
INVALID_INPUT | Malformed image structure, invalid options, dimensions, or source contract. |
LIMIT_EXCEEDED | A configured input, dimension, frame, decoded-byte, or temporary-storage budget was exceeded. |
TRUNCATED_INPUT | The source ended before the codec could read required bytes or pixels. |
UNSUPPORTED_FORMAT | The format is unknown or its codec was not registered. |
UNSUPPORTED_OPERATION | The format is recognized, but the requested coding feature, decode subset, or encoder is not implemented. |
Advanced contracts
The Node root and browser entry export the primitives used to author custom codecs, accelerators, and sinks: ImageCodec, ImageCodecAccelerator, ImageLibraryConfiguration, ImageDecoder, ImageEncoder, CodecRegistry, ImageSink, Uint8ArraySink, PixelBlock, PixelFormat, and BufferPool. Node additionally exports BufferSink, FileSink, and FileSource.
These interfaces support custom codecs and sinks. A decoder yields ordered pixel blocks; an encoder consumes them and writes encoded chunks to a sink. Implementations must preserve the release callback and source-buffer lifetime contracts.