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, as well as the ability to decode more file types which it cannot encode. This article will demonstrate how to create a libvips build with additional decoders and encoders, then install Sharp with this custom libvips build.
For example, running this with a standard distribution of Sharp:
import sharp from "sharp";
await sharp('fixtures/test.png').jxl().toFile('output/from_png.jxl');
await sharp('fixtures/test.png').jp2().toFile('output/from_png.jp2');Will have an output roughly like this:
Error: VipsOperation: class "jxlsave" not found
Error: JP2 output requires libvips with support for OpenJPEGBelow 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 updateIn 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-devSections below will explain optional codecs, any of which can be skipped. Once you have installed all codecs relevant to your use-case feel free to continue to the Building Vips section of this article.
Codecs for parity with Sharp
If you want parity with prebuilt sharp, you will also need the following codecs:
libjpeg-turbo8-dev: for JPEGlibpng-dev: for PNGlibwebp-dev: for WebPlibtiff-dev: for TIFFlibgif-dev,libcgif-dev, andlibimagequant-dev: for GIFlibheif-devandlibdav1d-dev: for AVIFliblcms2-dev: ICC profiles and correct color handlinglibexif-dev: EXIF / metadatazlib1g-devandlibbrotli-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-devCodecs 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 them depending on your use-case.
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-devAfter 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-devAfter 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-devAfter 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-devAfter 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 libvips
Libvips is compiled with optional features based on which codec libraries are present at build time. Therefore, codecs installed after libvips was already built will not be used unless libvips is rebuilt.
Download a source release of libvips in order to control which specific version you build from. Your build process will reliably create the same artifact, and you can balance access to new features with the risk of compatibility issues with other libraries.
Repositories like apt can include libvips-dev but its version often lags behind. The repository maintainers change package versions over time, meaning you end up with a different artifact after re-running the same build commands.
Retrieving a -dev version of a library as demonstrated with the codecs above can be acceptable, but libvips is at the center of the entire integration. You should build libvips from source to keep the build process idempotent.
The example below demonstrates how to download and decompress a libvips release, build it with meson and ninja, and update ldconfig:
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
Unlike most npm packages, Sharp is a native Node.js addon, not a pure JavaScript library. It uses C++ bindings to link with libvips and compiles into a platform-specific binary .node addon, which Node.js can load at runtime via Node-API.
The node-addon-api package is a C++ wrapper around Node’s Node-API, which provides stable headers so native addons don’t break across Node.js versions. Technically the package is only used at build-time. Sharp uses it to compile a binary that generally works across versions without recompilation. Install with:
npm i node-addon-apiA regular installation of Sharp ships with a prebuilt libvips and core set of codec libraries. If you require a different set of features, you can rebuild Sharp with your own libvips installation. The following command creates a new .node addon, linked against the libvips installation available on the system:
npm explore sharp -- npm run buildPortability
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 probability 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.