libTIFF for WebAssembly
v4.7.2WebAssemblylibTIFF 4.7.2 for browsers, Node.js and edge runtimes, precompiled for wasm32, single-threaded and multi-threaded as @crossbind/port-tiff-wasm.
Install
crossbind itself arrives with your bundler plugin, or with a new project from npm create crossbind@beta; Bundlers has Vite, Webpack, Rspack and Rollup.
Usage
Each example runs here, in this tab, and prints what the site build checked.
Each example also has a JavaScript only tab: the same task with no C++ file, calling libTIFF's own headers from @crossbind/port-tiff directly. None of them work that way; each tab says what stops it.
Encode pixels as a TIFF and decode them back
The core of libtiff: TIFFSetField and TIFFWriteScanline write a page, TIFFGetField and TIFFReadRGBAImageOriented read it back as display pixels. TIFFStreamOpen keeps the file in memory.
TIFFSetField(TIFF *, uint32_t, ...): width, height, samples, bits, photometric, compression and rows per strip. crossbind does not bind variadic functions, nor their va_list twin TIFFVSetField (SWIG warning 505, "Variable length arguments are not supported by embind, TIFFSetField skipped."), so importing either fails the build with MISSING_EXPORT. TIFFOpen on a /memfs path does open a file for writing, but with no tags TIFFWriteScanline returns -1 ("Must set "ImageWidth" before writing data."), and TIFFStreamOpen from tiffio.hxx throws for its std::ostream * ("unbound types"). Decoding a TIFF that already exists works: on the C++ version's file, TIFFRGBAImageBegin fills a new _TIFFRGBAImage() whose width, height, bitspersample and samplesperpixel read 160, 120, 8 and 3, and TIFFReadRGBAImageOriented fills an allocBuffer with the same 76800 RGBA bytes as the C++ decode. The codec is out of reach: only TIFFGetField returns it, and TIFFFindCODEC gives a TIFFCodec without fields.Store several pages in one file
Scanners and fax software keep a document as one TIFF: every page is a directory closed with TIFFWriteDirectory, listed with TIFFReadDirectory and opened again with TIFFSetDirectory. These pages are 1-bit CCITT Group 4, the fax codec.
TIFFSetField (size, 1-bit samples, CCITT Group 4, page number and name) before its rows are written, and the listing reads the name and the codec back with TIFFGetField. Both are variadic, and crossbind skips them and their va_list versions (SWIG warning 505, "Variable length arguments are not supported by embind"; the imports fail with MISSING_EXPORT), so no page can be written: TIFFWriteScanline returns -1 with "Must set "ImageWidth" before writing data." On the C++ version's file the rest works from JavaScript: TIFFNumberOfDirectories counts 3 pages, TIFFReadDirectory steps through them, TIFFRGBAImageBegin reads each page's size (240x320, 1 bit) from a _TIFFRGBAImage, and TIFFSetDirectory with TIFFReadScanline returns page 3's pixels exactly as the C++ does. The page names and the codec have no getter besides TIFFGetField.Keep 32-bit float samples exact
Elevation, temperature and microscopy data are not 8-bit pictures. SAMPLEFORMAT_IEEEFP stores the real values, the floating-point predictor helps them compress and TIFFReadScanline reads them back; TIFFRGBAImageOK says why the RGBA reader cannot.
TIFFSetField for TIFFTAG_SAMPLEFORMAT and TIFFTAG_PREDICTOR as well as the size, and crossbind skips that variadic function and its va_list version TIFFVSetField (SWIG warning 505, MISSING_EXPORT on import), so JavaScript cannot write either file: TIFFWriteScanline returns -1 with "Must set "ImageWidth" before writing data." Reading the C++ version's predictor-3 file works: TIFFReadScanline into an allocBuffer returns its 256 rows of float32 bytes unchanged, and TIFFRGBAImageOK with a 1024-byte allocBuffer gives "Sorry, can not handle images with 32-bit samples", the third line of the C++ version. No call gives the width and height back: TIFFGetField is skipped, and TIFFRGBAImageBegin refuses float samples (it returns 0 and leaves width at 0).Read one tile of a big image
Large TIFFs are cut into tiles so a viewer decodes only what it shows. TIFFWriteTile stores them, TIFFComputeTile finds the one under a pixel and TIFFReadTile decodes it alone.
TIFFSetField(tif, TIFFTAG_TILEWIDTH, 128) and friends, and crossbind skips that variadic function and its va_list version TIFFVSetField (SWIG warning 505, MISSING_EXPORT on import). Without those tags a new file stays in strips: TIFFIsTiled returns 0, TIFFTileSize 0 and TIFFWriteTile -1 ("Col out of range"). Reading one tile, the point of the example, works on the C++ version's file: TIFFComputeTile puts pixel (300, 200) in tile 6, and TIFFReadTile decodes that tile alone into a 49152-byte allocBuffer, the same bytes as the C++, rgb(150, 100, 125) at the pixel; TIFFNumberOfTiles gives 16 and TIFFRGBAImageBegin 512x512. The tile width and height have no getter besides TIFFGetField.Open a TIFF file and print its tags
Files reach C++ by path: m.autoMountFiles mounts what an <input type="file"> or a drop gives you, TIFFOpen reads it in place and TIFFPrintDirectory prints every tag, as the tiffinfo tool does.
TIFFPrintDirectory(TIFF *, FILE *, long), which is bound, but no bound function returns a FILE * to print into, and an allocBuffer handle in its place is accepted and traps with "null function". The values it prints come from TIFFGetField, which crossbind skips as a variadic function (SWIG warning 505), as it skips the TIFFSetField the first example needs to make the file. The rest works: on the C++ version's photo.tif mounted with m.autoMountFiles, TIFFOpen reads it by path, TIFFNumberOfDirectories counts 1 page, TIFFCurrentDirOffset gives the directory offset 6112 as a BigInt, and TIFFRGBAImageBegin gives 64x48, 8 bits, 3 samples and photometric 2 (RGB) from a _TIFFRGBAImage. The compression and the rows per strip have no getter.What is different on WebAssembly
- In a browser the module runs in a Worker by default (
useWorker), so every call returns a promise:awaitcalls and constructors alike. - The module has its own filesystem:
m.FSwrites files,m.getFileBytesreads them back andm.autoMountFilesmountsFileobjects from an<input type=file>./memfslives in memory;/opfspersists across reloads and needs the Worker. See Filesystem. - In Node.js,
m.FSis the real disk, so use real paths there. - Multi-threaded builds (
runtime: 'mt') need COOP and COEP headers in production. See Threading.
Other platforms
- libTIFF overview: the apps, every platform's setup and the packages.
- libTIFF for Android: React Native apps on Android.
- libTIFF for iOS: React Native apps on iOS.
- libTIFF for WASI: command-line programs under wasmtime.
Facts on this page come from the port manifests in the repository and from what npm served on beta when the site was built. See the Libraries guide for the full consumer flow.