UTIF.js
A dependency-light JavaScript TIFF decoder that also reads RAW camera formats like DNG, CR2, NEF, and ARW.
Repository Health
Technical Analysis
UTIF.js is a small, pure-JavaScript TIFF and EXIF decoder that also handles camera RAW formats such as DNG, CR2, NEF, and ARW. It began as the core TIFF-reading engine inside the Photopea online image editor, where it needed to parse whatever oddball TIFF variant a user dragged in without relying on native codecs or a server round-trip.
The library ships with a single dependency (pako, for DEFLATE-compressed strips) and supports an unusually wide range of TIFF compression schemes — Fax Group 3/4 (CCITT), old and new JPEG-in-TIFF, LZW, PackBits, Thunderscan, and several RAW-specific codecs (ARW, Panasonic, LogLuv32, VC5) — plus a convenience UTIF.replaceIMG() helper that swaps <img src="foo.tif"> tags for a rendered PNG data URI with no extra wiring.
What You Get
- A dependency-light TIFF/EXIF decoder (
UTIF.decode) that reads image file directory tags without decompressing pixel data. - Full pixel decompression via
UTIF.decodeImage()across a wide set of TIFF compression schemes. - An
UTIF.toRGBA8()conversion step that turns decoded TIFF data into browser-ready 8-bit RGBA pixels. - A one-line
UTIF.replaceIMG()helper for displaying TIFF/RAW images directly in<img>tags. - A basic uncompressed TIFF encoder (
UTIF.encodeImage/UTIF.encode) for writing IFD metadata back out.
Common Use Cases
- Rendering user-uploaded TIFF or camera RAW files (DNG, CR2, NEF) in a browser-based image editor without a server round trip.
- Extracting EXIF/IFD metadata (dimensions, camera tags) from TIFF files client-side.
- Displaying legacy TIFF scans or faxes (CCITT Group 3/4 compressed) directly in web pages via
replaceIMG(). - Converting decoded TIFF pixel data to RGBA for canvas-based image processing pipelines.
Under The Hood
Architecture
The entire library is a single IIFE (UTIF.js) that attaches everything to one flat UTIF namespace, with a typeof module check to support both CommonJS require() and a plain browser global. Internally it’s organized as dozens of self-contained decoder/encoder functions keyed off the TIFF compression tag (t259) — _decodeG3, _decodeG4, _decodeLZW, _decodeOldJPEG, _decodeNewJPEG, _decodeVC5, _decodeARW, _decodePanasonic, _decodeLogLuv32, _decodeNikon, and more — plus a separately vendored embedded JPEG decoder exposed as UTIF.JpegDecoder. There are no classes or DI; every function communicates through passed-in typed arrays and a shared IFD object keyed by TIFF tag number strings ("t256", "t273", etc.), so any change to that flat tag-dictionary shape would ripple through every decode/encode path at once.
Tech Stack
Pure vanilla JavaScript with no TypeScript, no bundler, and no build step — package.json’s main field points directly at the raw source file. The only runtime dependency is pako (^1.0.5), used solely for DEFLATE compression when encoding TIFF strips. It ships as an npm package (utif) but is equally consumable via a plain <script> tag, reflecting its original use as a vendored decoder inside the Photopea editor rather than a modern tooled library.
Code Quality
No test directory, test framework, or CI configuration exists in the repository. Error handling is largely implicit — most decoders assume well-formed TIFF input and will throw generic JavaScript errors (e.g. out-of-bounds array access) on malformed files, though the embedded JPEG decoder is a notable exception with dedicated JpegError, DNLMarkerError, and EOIMarkerError types. Naming throughout the core decoders favors terse, often single/double-letter identifiers even in the checked-in source, and there is no TypeScript or JSDoc type coverage. In short: no tests found, minimal error typing outside the JPEG path.
API Design
The public surface is intentionally tiny: UTIF.decode(), UTIF.decodeImage(), and UTIF.toRGBA8() take a caller from a raw TIFF ArrayBuffer to RGBA pixels in three calls, and UTIF.replaceIMG() collapses that into a single zero-config call for HTML <img> tags — genuinely low boilerplate for the common case. That said, documentation stops at those four entry points in the README; the large internal surface of codec-specific _decode* functions is entirely undocumented, and there are no TypeScript type definitions, JSDoc annotations, or a changelog to guide consumers past the happy path.