Skip to main content

Code examples

Install hdrify first. Node examples use ES modules and can be saved as .mjs files. Browser examples assume a bundler that resolves npm imports.

OpenEXR to Radiance HDR​

import { readFile, writeFile } from 'node:fs/promises';
import { readExr, writeHdr } from 'hdrify';

const image = readExr(await readFile('environment.exr'));
await writeFile('environment.hdr', writeHdr(image));

Radiance HDR stores RGBE rather than the original EXR channel representation. Keep the EXR source when its precision or metadata matters.

Create and read a JPEG gain map​

Encoding and assembling the JPEG are separate steps. encodeGainMap produces the SDR pixels, gain map pixels, and metadata; writeJpegGainMap packages them into a single file.

import { readFile, writeFile } from 'node:fs/promises';
import { encodeGainMap, readExr, readJpegGainMap, writeExr, writeJpegGainMap } from 'hdrify';

const image = readExr(await readFile('environment.exr'));
const encoding = encodeGainMap(image, {
toneMapping: 'aces',
exposure: 1,
});
const jpeg = writeJpegGainMap(encoding, {
quality: 90,
format: 'ultrahdr',
});
await writeFile('environment.jpg', jpeg);

const reconstructed = readJpegGainMap(jpeg);
await writeFile('reconstructed.exr', writeExr(reconstructed));

Use format: 'adobe-gainmap' for the alternative JPEG layout. Reconstruction is not a lossless round trip because the base image and gain map are JPEG compressed.

Resize and convert primaries​

import { readFile, writeFile } from 'node:fs/promises';
import { convertLinearColorSpace, readExr, resizeImage, writeExr } from 'hdrify';

const source = readExr(await readFile('environment.exr'));
const rec709 = convertLinearColorSpace(source, 'linear-rec709');
const resized = resizeImage(rec709, {
width: 1024,
height: 512,
filter: 'lanczos',
});
await writeFile('small.exr', writeExr(resized, { compression: 'piz' }));

Width and height are explicit; choose dimensions that preserve your desired aspect ratio. Resize filters include nearest, bilinear, and lanczos.

Load and preview an HDR file in a browser​

Add <canvas id="preview"></canvas> to your page and call this function from a module. It checks the HTTP response, decodes the image, and expands tone-mapped RGB bytes into canvas RGBA bytes.

import { applyToneMapping, readHdr } from 'hdrify';

async function drawPreview(url: string, canvas: HTMLCanvasElement): Promise<void> {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Could not load HDR image: ${response.status}`);
}
const image = readHdr(new Uint8Array(await response.arrayBuffer()));
const rgb = applyToneMapping(image.data, image.width, image.height, {
sourceColorSpace: image.linearColorSpace,
toneMapping: 'agx',
exposure: 1,
});

canvas.width = image.width;
canvas.height = image.height;
const context = canvas.getContext('2d', { colorSpace: 'srgb' });
if (!context) throw new Error('A 2D canvas context is required');
const pixels = context.createImageData(image.width, image.height);
for (let pixel = 0; pixel < image.width * image.height; pixel++) {
pixels.data[pixel * 4] = rgb[pixel * 3]!;
pixels.data[pixel * 4 + 1] = rgb[pixel * 3 + 1]!;
pixels.data[pixel * 4 + 2] = rgb[pixel * 3 + 2]!;
pixels.data[pixel * 4 + 3] = 255;
}
context.putImageData(pixels, 0, 0);
}

const canvas = document.querySelector<HTMLCanvasElement>('#preview');
if (!canvas) throw new Error('Missing preview canvas');
await drawPreview('/images/environment.hdr', canvas);

For a file picker, replace the fetch call with new Uint8Array(await file.arrayBuffer()). This canvas preview is SDR: it makes HDR content viewable through tone mapping. For a ready-made component, see the React guide, or try the live demo.

Decode ordinary display pixels into linear light​

When starting from sRGB canvas bytes, normalize every channel before decoding the transfer function. This does not recover HDR detail missing from an SDR image.

import { convertDisplayToLinear } from 'hdrify';

function fromSrgbCanvas(pixels: ImageData) {
const normalized = Float32Array.from(pixels.data, (value) => value / 255);
return convertDisplayToLinear(normalized, pixels.width, pixels.height, 'display-srgb', 'linear-rec709');
}

Use the matching display space if the source canvas is Display P3. See formats and color for the distinction between primaries, transfer functions, and tone mapping.