TIFF reference

TIFF is a full document, raster, and image codec.

PureJsImage supports broad Classic TIFF and BigTIFF display decoding, native scientific and GeoTIFF rasters, OME-TIFF datasets, Aperio whole-slide pyramids, bounded remote region reads, and structured RGB/RGBA output.

Classic TIFF + BigTIFFStrip/tile boundedNode + browser

Entry points

ImportPurpose
purejsimage/codecs/tiffDefault display-image TIFF codec and createTiffCodec() composition.
purejsimage/tiffValidated IFD document graph, bounded tag access, raster decoders, and profile registry.
purejsimage/scientificOME-TIFF multidimensional datasets and native plane reads.
purejsimage/pathologyGeneric whole-slide API and the first-party Aperio SVS profile.
purejsimage/compression/zstdReusable 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 A2B0 ICC conversion.
  • Chunky subsampled YCbCr, planar 1×1 YCbCr, TIFF 6 CIELab, SGILog luminance, and SGILog24/32 LogLuv color.
  • Explicit numeric display ranges from SMinSampleValue/SMaxSampleValue or 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.
Full contract. The generated TIFF capability checklist is generated from the authoritative manifest and records every implemented and planned boundary.

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.

Read the complete Imazen TIFF report →

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.

LibraryDecoded / comparableExactPixel mismatchUnsupported / error / oracle / timeout / crashMalformed inputs
PureJsImagebenchmark snapshot · a1f20da104 / 10657470 / 0 / 2 / 0 / 04 rejected · 0 accepted · 0 timeout · 0 crash
GeoTIFF.js3.0.584 / 106325211 / 7 / 2 / 0 / 21 rejected · 3 accepted · 0 timeout · 0 crash
UTIF.js (utif2)4.1.074 / 10649250 / 28 / 2 / 2 / 30 rejected · 1 accepted · 0 timeout · 3 crash
image-js/tiff7.1.341 / 106271451 / 12 / 2 / 0 / 01 rejected · 3 accepted · 0 timeout · 0 crash
image-js1.7.039 / 10633651 / 14 / 2 / 0 / 01 rejected · 3 accepted · 0 timeout · 0 crash
Jimp1.6.074 / 10649250 / 28 / 2 / 2 / 30 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.

OME-TIFF plane
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.

JPEG 2000 memory class. TIFF region selection remains tile-bounded. The current JPEG 2000 decoder reconstructs complete component state for the current TIFF tile or strip, not for the source slide.

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>.

Vendor profile
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.

Read the profile API reference →

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.

TIFF output
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 from purejsimage/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 A2B0 path.
  • 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.