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 historical 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. Current operation-level TIFF and scientific-reader timings are kept on the generated performance page.

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 unavailable / timeout / crashMalformed inputs
PureJsImagebenchmark snapshot · 3be4530104 / 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.

Structural limits. TiffDocumentOptions separately limits physical segment count, peak raw-plus-converted segment-table bytes, and each encoded strip or tile. Region admission also counts the largest intersecting encoded payload with decoded segments and output before reading pixels. Raise these defaults deliberately for unusually dense whole-slide layouts.

omeTiffReader validates OME XML, dimensions, sample type, labeled axis and channel metadata, physical pixel size, all valid OME 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. The COG Range Lab opens the public geo reader in a worker and reports its actual byte ranges.

OME-TIFF plane
import { MemorySource } from 'purejsimage'
import { createScientificLibrary } from 'purejsimage/scientific'
import { omeTiffReader } from 'purejsimage/scientific/readers/ome-tiff'

const science = createScientificLibrary({ readers: [omeTiffReader] })
const document = await science.open({ primary: { id: 'image', source: new MemorySource(tiffBytes) } })
const dataset = await document.openDataset(document.datasets[0].id)

for await (const block of dataset.readPlane({
  displayAxes: ['x', 'y'], fixedIndices: [{ axisId: 'channel', index: 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.