Entry points
| Import | Purpose |
|---|---|
purejsimage/codecs/tiff | Default display-image TIFF codec and createTiffCodec() composition. |
purejsimage/tiff | Validated IFD document graph, bounded tag access, raster decoders, and profile registry. |
purejsimage/scientific | OME-TIFF multidimensional datasets and native plane reads. |
purejsimage/pathology | Generic whole-slide API and the first-party Aperio SVS profile. |
purejsimage/compression/zstd | Reusable first-party Zstandard decoder also used by TIFF Compression 50000. |
All paths are first-party TypeScript with no runtime dependency, native library, external process, or automatically loaded WebAssembly module. WebP-in-TIFF is available only when the caller explicitly composes the WebP codec.
Decode support
Container and image layout
- Classic TIFF 6.0 and BigTIFF, little-endian and big-endian.
- Top-level frame selection and frame counting.
- Nested and chained reduced-resolution SubIFDs with cycle, alias, offset, and global directory-count validation.
- Strips and tiles, including padded tile edges and legacy tile tables stored in strip tags.
- Chunky and planar samples, orientation values 1–8, and requested-region decode.
- Only the selected frame, resolution level, strips, and tiles are decoded.
Samples and pixel models
- Grayscale at 1, 2, 4, 6, 8, 10, 12, 14, 16, 24, 32, and 64 bits, with WhiteIsZero or BlackIsZero.
- Indexed color at 1, 2, 4, 8, and 16 bits with exact TIFF ColorMap scaling.
- RGB at 2, 4, 8, 10, 12, 14, 16, 24, 32, and 64 bits.
- 8- and 16-bit grayscale-alpha and RGBA, with associated or unassociated alpha.
- Signed 8-/16-bit and IEEE float16/float32/float64 grayscale and RGB with native numeric preservation.
- Unsigned 24-/32-/64-bit grayscale and RGB with canonical wide numeric raster output.
- Unsigned CMYK, DotRange, CMYK plus alpha, deterministic signed/float CMYK display conversion, and bounded CMYK
lut16 A2B0ICC conversion. - Chunky subsampled YCbCr, planar 1×1 YCbCr, TIFF 6 CIELab, SGILog luminance, and SGILog24/32 LogLuv color.
- Explicit numeric display ranges from
SMinSampleValue/SMaxSampleValueor documented full-type defaults.
Compression and prediction
- Uncompressed, PackBits, LZW, Deflate, and Adobe Deflate.
- CCITT Modified Huffman, Group 3, and Group 4 bilevel fax.
- JPEG-in-TIFF Compression 7 complete or abbreviated streams with
JPEGTables. - Old-style JPEG Compression 6 complete interchange streams, multi-strip scans, and baseline table reconstruction.
- Aperio JPEG 2000 Compression 33003 YCbCr and 33005 MCT tiles.
- SGILog Compression 34676 and SGILog24 Compression 34677.
- Zstandard Compression 50000 through the bounded first-party decoder.
- LERC and LERC-plus-Deflate Compression 34887 through the bounded first-party LERC2 decoder.
- WebP Compression 50001 through explicit
createTiffCodec({ embeddedCodecs: [webpCodec] })composition. - Horizontal Predictor 2 for supported 2- through 64-bit samples and floating Predictor 3 for float16/32/64.
- FillOrder 2 normalization before predictor reversal.
Imazen conformance baseline
The 2026-08-10 154-file TIFF corpus run records 148 passes, 2 structured UNSUPPORTED_OPERATION results, and 4 safely rejected robustness inputs. It records zero decode failures, invalid outputs, raw exceptions, timeouts, crashes, or out-of-memory results.
Every file runs in an isolated worker with a 30-second timeout and 512 MiB heap limit. A pass means metadata inspection, TIFF-to-PNG decode, PNG reopen, and output-dimension validation all completed. This corpus result demonstrates safe end-to-end handling; it is not by itself an exact-pixel comparison.
Exact pixels are tested separately with pinned fixtures and independent ImageMagick/LibTIFF, tifffile/imagecodecs, Esri LERC, libwebp, GeoTIFF.js, and OpenSlide oracles. The corpus worker explicitly composes WebP-in-TIFF while the default TIFF codec remains independent. Unsupported totals record only the first boundary reached.
JavaScript TIFF library comparison
The compact matrix distinguishes documented capability from independently measured decode coverage and exact pixels. “Not verified” is not treated as unsupported.
| Library | Decoded / comparable | Exact | Pixel mismatch | Unsupported / error / oracle / timeout / crash | Malformed inputs |
|---|---|---|---|---|---|
| PureJsImagebenchmark snapshot · a1f20da | 104 / 106 | 57 | 47 | 0 / 0 / 2 / 0 / 0 | 4 rejected · 0 accepted · 0 timeout · 0 crash |
| GeoTIFF.js3.0.5 | 84 / 106 | 32 | 52 | 11 / 7 / 2 / 0 / 2 | 1 rejected · 3 accepted · 0 timeout · 0 crash |
| UTIF.js (utif2)4.1.0 | 74 / 106 | 49 | 25 | 0 / 28 / 2 / 2 / 3 | 0 rejected · 1 accepted · 0 timeout · 3 crash |
| image-js/tiff7.1.3 | 41 / 106 | 27 | 14 | 51 / 12 / 2 / 0 / 0 | 1 rejected · 3 accepted · 0 timeout · 0 crash |
| image-js1.7.0 | 39 / 106 | 33 | 6 | 51 / 14 / 2 / 0 / 0 | 1 rejected · 3 accepted · 0 timeout · 0 crash |
| Jimp1.6.0 | 74 / 106 | 49 | 25 | 0 / 28 / 2 / 2 / 3 | 0 rejected · 1 accepted · 0 timeout · 3 crash |
Open the full grouped capability matrix, methodology, versions, and sources →
Scientific TIFF and OME-TIFF
openTiffDocument() exposes stable top-level and SubIFD directories without forcing samples into a display format. Every directory exposes its absolute source offset; getDirectoryByOffset() resolves metadata pointers across the parsed graph. Each directory supports bounded, cached typed getTag() reads plus a normal ImageDecoder or native-precision RasterDecoder. RasterBlock supports planar or interleaved N-channel data; rasterToPixels() requires an explicit one- or three-channel selection and display range.
openOmeTiff() validates OME XML, dimensions, sample type, channel metadata, physical pixel size, all valid Z/C/T dimension orders, explicit or implicit TiffData IFD mappings, interleaved or separately stored channels, and reduced-resolution SubIFDs.
geoTiffProfile.open() exposes validated GeoKeys, GDAL metadata and nodata, pixel-to-model and model-to-pixel conversion, origin, resolution, and bounding-box helpers for tiepoint/scale or affine transformation models.
import { MemorySource } from 'purejsimage'
import { openOmeTiff } from 'purejsimage/scientific'
import { openTiffDocument } from 'purejsimage/tiff'
const document = await openTiffDocument(new MemorySource(tiffBytes))
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)
}Whole-slide images
The generic WholeSlideImage contract exposes level dimensions and downsample factors, requested-region reads, associated images, vendor properties, microns per pixel, and objective power where present. Each level also exposes tile(column, row, options?) using native tile coordinates; edge tiles return only the valid image region. Region, tile, associated-image, profile, and TIFF document operations accept an optional AbortSignal.
The first-party Aperio SVS profile classifies pyramid levels and label, macro, or thumbnail images; parses MPP and objective metadata; and decodes TIFF JPEG 2000 tags 33003/33005. The pinned irreversible Aperio tile differs from the OpenSlide 3.4.1 oracle by at most two 8-bit code values across 54 of 196,608 channels.
Third-party TIFF profiles
Vendor packages implement TiffProfile<T> through published APIs and register them with createTiffProfileRegistry(). Detection runs profiles independently, orders matches by descending priority then id, reports detector failures without suppressing unrelated matches, and rejects equal-priority ambiguity. registry.openWith(document, profile) is the typed explicit path 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)readBytes(offset, length, { maxBytes }) provides exact defensive-copy reads for private metadata without exposing the source object. It rejects invalid, out-of-source, or over-budget ranges. Parsed tag values are cached per directory; every call still enforces its own maxBytes, and byte-valued tags return defensive copies. The checked examples/tiff-profile-leica single-area Leica SCN driver compiles as an external package and imports only purejsimage, purejsimage/tiff, and purejsimage/pathology. Multi-area Leica composition remains explicitly unsupported instead of being flattened into a misleading image.
TIFF output
The writer emits chunky 8-bit RGB or RGBA with horizontal prediction and independently Deflate-compressed strips or tiles. It supports automatic roughly 128 KiB strip planning, explicit rows per strip, validated tile dimensions, Classic TIFF, automatic or explicit BigTIFF, multi-page top-level IFD chains, and reduced-resolution SubIFD pyramids. Compatible RGB ICC profiles can be retained explicitly.
await image.tiff({
compression: 'deflate',
predictor: 'horizontal',
layout: 'tiles',
tileWidth: 256,
tileHeight: 256,
format: 'auto',
compressionLevel: 6,
}).toFile('output.tiff')encodeTiffDocument() writes multiple top-level pages and nested reduced-resolution images. Alternate compression profiles, grayscale or palette output, 16-bit channels, associated alpha, and broader metadata writing remain unsupported.
Memory and safety
- Display, native raster, GeoTIFF, OME plane, and whole-slide reads are bounded to selected strips or tiles.
HttpRangeSource, imported frompurejsimage/sources/http-range, deduplicates bounded requests, enforces a byte-capped LRU cache, and rejects ETag or Last-Modified changes while reading remote COG-style TIFFs.- IFD traversal and profile detection do not read unselected pixel segments.
- Tag payloads, IFD counts, offsets, dimensions, segment geometry, decompression output, predictors, and nested graph traversal are validated before allocation or indexing.
- LERC, Zstandard, FillOrder normalization, palette lookup, color conversion, predictors, SGILog scratch, and output blocks stay bounded to the current metadata table or segment.
- The encoder bounds raw pixel and predictor scratch to the current strip or tile and retains compressed segments until final offsets are known; it does not stage a full uncompressed frame.
Explicit remaining boundaries
- No automatic display mapping for arbitrary scientific multiband data. Use native raster blocks or provide an explicit channel/range mapping.
- No raw numeric CMYK preservation or general CMYK ICC profile classes beyond the supported bounded
lut16 A2B0path. - No ThunderScan or unspecified extension compression.
- No automatic WebP composition in the default TIFF codec.
- No multi-area Leica scene composition in the external example.
- No alternate TIFF compression or pixel profiles beyond Deflate-predicted RGB/RGBA strips and tiles.
Recognized but unsupported combinations fail with a structured ImageError; they are not silently approximated.