tiff: TIFF images via libtiff
| (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
width : exact-positive-integer? height : exact-positive-integer? data : bytes?
4 Reading and writing
procedure
(tiff-info path) → info?
path : path-string?
(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?
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
(rgba->tiff "out.tiff" img #:compression 'deflate)
procedure
path : path-string?
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.