How Node.js Sharp Uses Libavif Under the Hood

This article explores the technical pipeline that enables Node.js image processing libraries, specifically sharp, to compress and manipulate AVIF images. It details the multi-layered architecture starting from JavaScript, moving through C++ bindings (N-API) and the high-performance libvips library, and finally executing the core encoding and decoding tasks via the C-based libavif library.

The Architectural Pipeline

To process AVIF images, sharp does not execute image compression directly in JavaScript. Because JavaScript is single-threaded and not optimized for heavy mathematical computations, sharp acts as a wrapper around a native C++ engine. The data flows through four distinct layers:

  1. JavaScript Layer (sharp): The developer calls methods like sharp('input.jpg').avif().toFile('output.avif').
  2. C++ Binding Layer (N-API / Node-API): Bridges the Node.js runtime and the native C++ library, passing Javascript buffers and configuration options to native memory.
  3. Image Processing Engine (libvips): A fast, multi-threaded image processing library that manages memory, handles resizing, and orchestrates image operations.
  4. Codec Layer (libavif): The specialized library responsible for parsing the AVIF container and encoding/decoding the underlying AV1 video frames.

The Role of libvips as the Mediator

At the core of sharp is libvips. When sharp is installed, it downloads pre-compiled native binaries of libvips tailored to the host operating system.

libvips contains a writer and reader plugin system. When sharp requests an AVIF output, libvips identifies the .avif format and routes the raw image pixel buffer to its foreign save plugin, vips_foreign_save_avif. This plugin acts as the direct interface to libavif.


How libavif Handles the Heavy Lifting

libavif is a C library written by the Alliance for Open Media (AOMedia) designed to encode and decode AVIF files. AVIF is technically an image format that wraps AV1 video frames inside a HEIF (High Efficiency Image File Format) ISO base media file format container.

When libavif receives pixel data from libvips, it performs two primary tasks:

1. Interfacing with AV1 Codecs

libavif itself does not compress the raw pixels into AV1 format; it delegates this to an underlying AV1 codec. * Encoding (Compression): libavif typically uses libaom (the reference encoder) or SVT-AV1 to compress the spatial image data into an AV1 bitstream. * Decoding (Decompression): When reading an AVIF, libavif often utilizes dav1d, a highly optimized, fast AV1 decoder, to translate the compressed bitstream back into raw RGB/RGBA pixels.

2. Packaging the HEIF Container

Once the AV1 encoder produces the compressed payload, libavif packages this bitstream into the standard HEIF container. This step includes writing metadata such as color profiles (ICC profiles), Exif data, and alpha channel configuration (transparency) into the file structure.


Memory Management and Threading

To prevent image processing from blocking the main Node.js event loop, sharp and libvips utilize the libuv thread pool.

When an AVIF operation begins, the JavaScript thread hands the task off to a background worker thread. libvips allocates memory outside of the V8 JavaScript engine’s garbage-collected heap. libavif then utilizes multi-threading (leveraging CPU cores via the AV1 codec) to compress the image in parallel. Once the compression is complete, the native memory is freed, and the final AVIF buffer is passed back to Node.js as a standard JavaScript Buffer object.