Canvas API and High-Bit-Depth AVIF Decoding

This article examines the technical pipeline connecting high-bit-depth AVIF images to the HTML5 Canvas API. It details how browsers decode 10-bit and 12-bit AVIF pixel data, how the default 8-bit canvas context handles this data via truncation or tone mapping, and how modern Canvas color management features—such as extended color spaces and 16-bit floating-point backing stores—enable the preservation of wide color gamuts and high dynamic range precision during rendering and pixel manipulation.

AVIF Decoding and Pixel Formats

AVIF supports 8-bit, 10-bit, and 12-bit color depths across various chroma subsampling configurations (4:2:0, 4:2:2, and 4:4:4). When a browser's decoding engine (typically utilizing an AV1 decoder such as dav1d wrapped in libavif) decodes a 10-bit or 12-bit AVIF, it produces an uncompressed buffer of high-precision pixel data.

In its raw state, this decoded buffer stores luminance and chrominance samples as 16-bit unsigned integers per channel within system memory. In addition to high bit depth, the decoder parses embedded color metadata, including Color Primaries, Transfer Characteristics, and Matrix Coefficients (CP/TC/MC), along with any embedded ICC profiles.

Default 2D Canvas Behavior (8-Bit Truncation)

By default, an HTML5 <canvas> 2D rendering context initializes with an 8-bit sRGB backing store (uint8 per color channel). When a decoded high-bit-depth AVIF image is drawn onto this default context using CanvasRenderingContext2D.drawImage(), the browser performs automatic format conversion:

  1. Chroma Upsampling and Color Matrix Transformation: The decoder's YUV data is converted to RGB.
  2. Color Space and Dynamic Range Conversion: If the AVIF utilizes a wide color gamut (e.g., Display P3 or Rec. 2020) or an HDR transfer function (such as Perceptual Quantizer or Hybrid Log-Gamma), the engine maps these values into standard sRGB. High Dynamic Range (HDR) values exceeding standard peak brightness are clipped or tone-mapped down to Standard Dynamic Range (SDR).
  3. Quantization: The 10-bit (0–1023) or 12-bit (0–4095) integer values are scaled and rounded down to an 8-bit (0–255) integer range.

Consequently, extracting pixels via ctx.getImageData() from a default canvas returns a Uint8ClampedArray, discarding the additional bit depth and resulting in potential color banding or clipping.

Preserving Precision with Modern Canvas Features

To retain the precision and dynamic range of 10-bit and 12-bit AVIF images, the Canvas 2D context must be explicitly configured to use a high-bit-depth storage format.

Floating-Point Backing Stores

Modern web engines support configuration options when creating a context:

const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d", {
  colorSpace: "display-p3",
  pixelFormat: "float16"
});

Configuring the canvas with pixelFormat: "float16" (or browser implementations supporting extended bit depths) changes the underlying pixel backing store from an 8-bit fixed-point buffer to a half-precision 16-bit floating-point buffer (RGBA16F).

When an AVIF decoded buffer is transferred into a float16 context:

Direct Pixel Access via Extended ImageData

When querying pixel data from a high-bit-depth canvas, the standard getImageData() call can be instructed to maintain floating-point precision:

const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height, {
  storageFormat: "float32"
});

This returns a Float32Array within the ImageData object, where normalized color components typically range from 0.0 to 1.0 for SDR, and can exceed 1.0 for HDR values present in the source AVIF.

ImageBitmap and Hardware Acceleration (WebGL / WebGPU)

To bypass the main thread and avoid unnecessary color conversions entirely, applications can load AVIF images via createImageBitmap():

const response = await fetch("image-10bit.avif");
const blob = await response.blob();
const bitmap = await createImageBitmap(blob, {
  colorSpaceConversion: "none",
  imageOrientation: "from-image"
});

Setting colorSpaceConversion: "none" instructs the decoder to preserve the original color values without forcing an intermediate conversion to sRGB.

In hardware-accelerated contexts like WebGL2 or WebGPU, the decoded ImageBitmap can be uploaded directly to GPU textures allocated with high-bit-depth formats, such as RGBA16F or RGB10_A2. This pipeline allows compute or fragment shaders to perform wide-gamut and HDR calculations using the original 10-bit or 12-bit AVIF color values without precision loss.