tiff:   TIFF images via libtiff
1 Why this package exists
2 Installation and platform notes
3 Data model
rgba
4 Reading and writing
tiff-info
tiff->rgba
rgba->tiff
tiff-page-count
5 Error handling and logging
6 Limitations and future work
7 Raw bindings
9.3

tiff: TIFF images via libtiff🔗ℹ

turinglambdaai

 (require tiff) package: tiff

The tiff package provides FFI bindings to libtiff, the standard C library for reading and writing TIFF files. It can decode any TIFF that libtiff can handle — grayscale, bilevel, palette, RGB, CMYK, YCbCr, tiled or stripped, classic TIFF or BigTIFF — into RGBA pixels, and it can encode RGBA pixels back into TIFF.

1 Why this package exists🔗ℹ

Racket’s built-in racket/draw reads PNG, JPEG, GIF and BMP bitmaps, but not TIFF. TIFF refuses to die: it is the lingua franca of scanners, scientific imaging, geospatial data and document management. Rather than reimplement 40 years of TIFF arcana in pure Racket, tiff binds directly to the system libtiff — no bundled C code, nothing to compile, and every TIFF feature libtiff supports comes along for the ride.

2 Installation and platform notes🔗ℹ

Install the package and make sure libtiff 4.x is present on the system:

  • macOS (Homebrew). brew install libtiff. Homebrew puts the library in "/opt/homebrew/lib" (Apple Silicon) or "/usr/local/lib" (Intel). Those locations are not in the default dlopen path, so the package also probes them directly and normally just works. If your libtiff lives somewhere else, run Racket with:

    DYLD_FALLBACK_LIBRARY_PATH=/dir/with/libtiff racket ...

  • Linux. Install the distro package (libtiff-dev on Debian/Ubuntu, libtiff-devel on Fedora). The loader finds the versioned soname through the ldconfig cache or LD_LIBRARY_PATH; no extra configuration is needed.

  • Windows. Not supported in v0.1.

The loader prefers versioned sonames ("libtiff.so.6", "libtiff.so.5", "libtiff.6.dylib") before the unversioned development symlink, so systems that only ship the runtime library work fine.

3 Data model🔗ℹ

struct

(struct rgba (width height data))

  width : exact-positive-integer?
  height : exact-positive-integer?
  data : bytes?
An in-memory image: data holds exactly (* 4 width height) bytes, four bytes per pixel in R, G, B, A order, with straight (non-premultiplied) alpha and a top-left origin (row 0 is the top row). The constructor checks the byte-string length.

4 Reading and writing🔗ℹ

procedure

(tiff-info path) → info?

  path : path-string?
Returns basic metadata for the first directory of path:

(tiff-info "photo.tiff")

The compression field is a symbol: 'none, 'lzw, 'deflate, 'packbits, 'jpeg, or 'code-N for schemes this package does not name.

procedure

(tiff->rgba path) → rgba?

  path : path-string?
Decodes the first page of path through libtiff’s high-level RGBA interface. Non-8-bit samples are scaled to 8 bits, other photometric interpretations are converted to RGB transparently, and the result always has a top-left origin regardless of the file’s Orientation tag.

Two alpha subtleties, handled for you:

  • Files with no alpha decode with alpha = 255 everywhere.

  • libtiff’s RGBA reader returns premultiplied pixels for files whose alpha is declared associated or unassociated. Because the rgba type is straight alpha, tiff->rgba converts the values back. Opaque pixels are exact; partially transparent pixels may differ from the file’s nominal values by at most 1 per color channel.

Signals exn:fail if the file is missing, is not a TIFF, or cannot be decoded; libtiff’s own diagnostics are logged under the tiff topic (see Error handling and logging).

procedure

(rgba->tiff path img [#:compression mode]) → void?

  path : path-string?
  img : rgba?
  mode : compression-mode/c = 'none
Writes img to path as a classic TIFF with 8-bit contiguous RGBA samples (the fourth sample declared as unassociated/straight alpha). mode is one of 'none, 'lzw, 'deflate, or 'packbits.

(rgba->tiff "out.tiff" img #:compression 'deflate)

procedure

(tiff-page-count path) → exact-nonnegative-integer?

  path : path-string?
Returns the number of directories (pages) in the file. Note that tiff->rgba always decodes the first page; walking to later pages requires the raw bindings in "tiff/private/ffi".

5 Error handling and logging🔗ℹ

libtiff reports problems through two C callback registries. This package routes them into Racket’s logger with topic 'tiff and levels 'error and 'warning, using current-logger at the time of the call:

(define receiver (make-log-receiver (current-logger) 'warning 'tiff))

Because libtiff passes its handlers a C va_list, which has no portable FFI representation, printf-style arguments are not interpolated; the format string itself is logged. Racket-level failures raise exn:fail with messages that include the file path. File handles are managed with dynamic-wind, so libtiff files are always closed — including when an exception unwinds through a decode or encode.

6 Limitations and future work🔗ℹ

  • Sources and destinations are paths only in v0.1; decoding from in-memory byte strings needs TIFFClientOpen callbacks and is planned.

  • Writing supports 8-bit contiguous RGBA only (one strip, classic TIFF). Reading has no such restriction.

  • tiff->rgba scales 16-bit and floating-point input down to 8 bits per channel, following libtiff’s RGBA interface.

  • Only the four corner orientations are normalized by libtiff; mirrored/rotated variants (Orientation 5-8) are reflected, not rotated.

7 Raw bindings🔗ℹ

The low-level bindings live in the private module tiff/private/ffi: TIFFOpen, TIFFClose, fixed-arity specializations of the varargs TIFFGetField/ TIFFSetField, the scanline API, the RGBA image API, and the safe per-tag wrappers used by the public functions. They are exported for applications that need more of libtiff than the high-level API exposes, but are undocumented here and may shift between minor releases.