Converting images with Node.js - Part 2: Exotic image types

Sharp has encoding functions for a few image types that are not supported out of the box. For example, without building Sharp from source with your own libvips build, a script like this:

import sharp from "sharp";

await sharp('fixtures/test.png').jxl().toFile('output/from_png.jxl').catch(console.error);
await sharp('fixtures/test.png').jp2().toFile('output/from_png.jp2').catch(console.error);

Will have an output roughly like this:

Error: VipsOperation: class "jxlsave" not found
Error: JP2 output requires libvips with support for OpenJPEG

If you want to support image types that are not included with a pre-built installation of sharp, you will have to build the vips library yourself and include in your own sharp build.

Below is an example of how to do this on Ubuntu 24.04, a popular linux distribution. It should be relatively simple to adapt these instructions to environments with different operating system distributions to suit your own needs.

Installing build tools

First, update the apt package manager:

apt update

In order to build libvips you will need:

  • build-essential: Meta-package providing GCC, G++, make, and standard libc headers required to compile C and C++ code.
  • pkg-config: Tool used by build systems to discover installed libraries, compiler flags, and linker flags at build time.
  • meson: High-level build system used by libvips and many newer C/C++ projects.
  • ninja-build: Low-level build executor typically used by Meson to perform the actual compilation steps.
  • cmake: Cross-platform build system required by several libvips delegates that do not use Meson.
  • libglib2.0-dev: Development headers for GLib, a core dependency of libvips providing data structures, threading, and I/O utilities.
  • libexpat1-dev: Development headers for Expat, an XML parser required by libvips for parsing XML-based image and metadata formats.

Install with:

apt install -y build-essential pkg-config meson ninja-build cmake libglib2.0-dev libexpat1-dev

Codecs for parity with Sharp

If you want parity with prebuilt sharp, you will also need the following codecs:

  • libjpeg-turbo8-dev: for JPEG
  • libpng-dev: for PNG
  • libwebp-dev: for WebP
  • libtiff-dev: for TIFF
  • libgif-dev, libcgif-dev, and libimagequant-dev: for GIF
  • libheif-dev and libdav1d-dev: for AVIF
  • liblcms2-dev: ICC profiles and correct color handling
  • libexif-dev: EXIF / metadata
  • zlib1g-dev and libbrotli-dev: compression / utility

Install with:

apt install -y libjpeg-turbo8-dev libpng-dev libwebp-dev libtiff-dev libgif-dev libcgif-dev libimagequant-dev libheif-dev libdav1d-dev liblcms2-dev libexif-dev zlib1g-dev libbrotli-dev

Codecs for exotic image types

You can install codecs for the image types you want to support. Each individual codec is optional; you can choose whether to install it depending on your usecase.

JPEG-XL

JPEG-XL is a relatively new image encoding standard intended to replace JPEG. It supports both a lossy compression method (varDTC) which significantly improves and expands upon the compression method of JPEG, and a modular mode of lossless compression similar to PNG. JPEG-XL was designed to become a universal replacement for all established raster formats for the web. According to the Sharp documentation, the feature is experimental and should not be used in production systems.

Install with:

apt install -y libjxl-dev

After completing the build steps for vips and Sharp below, you should be able to encode and decode .jxl files:

await sharp('test.png').jxl().toFile('output.jxl');
await sharp('test.jxl').png().toFile('output.png');

JPEG-2000

JPEG-2000 was developed from 1997 to 2000 with the intention of improving upon and eventually superseding the original JPEG standard. The main advantage of this format is the flexibility of its codestream, which is scalable by resolution and quality. A JPEG-2000 codestream can be truncated to yield a lower-resolution or lower-quality representation of the same image without re-encoding it.

JPEG-2000’s scalability and flexibility make it powerful, but the sophistication of the format also introduces unpredictability in the computational cost of decoding. There have been security vulnerabilities in decoders in the past. When handling untrusted files, decoding JPEG-2000 images should be done in isolated environments to manage security risks.

Install with:

apt install -y libopenjp2-7-dev

After completing the build steps for vips and Sharp below, you should be able to encode and decode .jp2 files:

await sharp('test.png').jp2().toFile('output.jp2');
await sharp('test.jp2').png().toFile('output.png');

OpenEXR

OpenEXR was released in 2003 and has been widely adopted for visual effects and animation. OpenEXR can store multiple channels (such as RGB and alpha) in a single file, which is more portable than a project folder with many files. This format is well suited for professional rendering pipelines, but decoding it is more complex than decoding typical raster image formats.

Install with:

apt install -y libopenexr-dev

After completing the build steps for vips and Sharp below, you should be able to decode .exr files:

await sharp('test.exr').png().toFile('output.png');

FITS

FITS was first standardized in 1981. It is the most commonly used digital file format in astronomy. The format supports arrays of any dimension which are defined in its human-readable file headers. The term “image” is somewhat loosely applied, as a FITS file may contain a two-dimensional image, a three-dimensional data cube, tabular data, or a time series.

For demonstration purposes and thumbnails, FITS images should be immediately normalized, downscaled, and converted to a lossy format such as JPEG to keep memory and output sizes bounded.

Install with:

apt install -y libcfitsio-dev

After completing the build steps for vips and Sharp below, you should be able to decode .fits files:

await sharp('test.fits').jpeg({quality: 50}).toFile('output.jpeg');

Building vips

After you have chosen the codecs you want to include you can download and build libvips with:

curl -L -O https://github.com/libvips/libvips/releases/download/v8.18.0/vips-8.18.0.tar.xz
tar xf vips-8.18.0.tar.xz
cd vips-8.18.0
meson setup build --prefix=/usr/local
ninja -C build
ninja -C build install
ldconfig
cd ..

Substitute vips-8.18.0 for the latest stable version of libvips you want to use.

Building Sharp

Then install node-addon-api in order to make C++ headers available:

npm i node-addon-api

and (re-)install Sharp with:

npm explore sharp -- npm run build

Portability

You should be able to take your node_modules folder and deploy it to an environment with the same operating system distribution and hardware architecture. However, when deploying to a runtime environment with different characteristics, there is a high chance your custom-built Sharp installation will stop working.

Conclusion

Other image formats supported by libvips follow the same pattern. Installing the appropriate codec and rebuilding libvips and Sharp exposes them automatically. Keep in mind that each additional decoder meaningfully expands the operational and security surface of your implementation. In the next article we will examine converting documents into images for preview and thumbnail purposes.