JavaScript/TypeScript bindings for the UltraHDR (ISO 21496-1) gain map specification, backed by upstream google/libultrahdr compiled to WebAssembly via Emscripten/embind.
Dual-licensed under Apache-2.0 OR MIT, matching upstream libultrahdr.
- Detection: Check if a JPEG contains UltraHDR/gain map data
- Decoding: Extract SDR base, gain map, and metadata from UltraHDR JPEGs
- Encoding: Create UltraHDR JPEGs from SDR + HDR image pairs
- SDR Extraction: Extract backwards-compatible SDR image
- ISO 21496-1:2025 (Gain map metadata)
- Google UltraHDR v1 (Android compatibility)
- Adobe Gain Map specification
npm install open-ultrahdrimport { isUltraHdr, setLocation } from 'open-ultrahdr';
// Set the location of WASM files (required if not co-located with JS)
setLocation('/path/to/wasm/');
const buffer = await file.arrayBuffer();
if (await isUltraHdr(buffer)) {
console.log('This is an UltraHDR image!');
}import { decodeUltraHdr } from 'open-ultrahdr';
const buffer = await file.arrayBuffer();
const result = await decodeUltraHdr('image-1', buffer);
// Access components
const sdrBlob = new Blob([result.sdrImage], { type: 'image/jpeg' });
console.log('Image size:', result.width, 'x', result.height);
console.log('HDR capacity:', result.metadata.hdrCapacityMax, 'stops');import { extractSdrBase } from 'open-ultrahdr';
const ultraHdrBuffer = await file.arrayBuffer();
const sdrBuffer = await extractSdrBase(ultraHdrBuffer);
// Use the SDR image for non-HDR displays
const blob = new Blob([sdrBuffer], { type: 'image/jpeg' });import { encodeUltraHdr, defaultEncodeOptions } from 'open-ultrahdr';
const sdrBuffer = await sdrFile.arrayBuffer();
const hdrLinearData = await getHdrLinearData(); // Float32Array, 3 values per pixel
const ultraHdr = await encodeUltraHdr('encode-1', sdrBuffer, hdrLinearData, {
...defaultEncodeOptions,
targetHdrCapacity: 4.0,
});
// Create downloadable file
const blob = new Blob([ultraHdr], { type: 'image/jpeg' });isUltraHdr(buffer: ArrayBuffer): Promise<boolean>- Check if image contains UltraHDR data
decodeUltraHdr(id: string, buffer: ArrayBuffer): Promise<UltraHdrDecodeResult>- Decode UltraHDR imageextractSdrBase(buffer: ArrayBuffer): Promise<ArrayBuffer>- Extract SDR base imagegetMetadata(buffer: ArrayBuffer): Promise<GainMapMetadata>- Get gain map metadata only
encodeUltraHdr(id: string, sdrBuffer: ArrayBuffer, hdrBuffer: ArrayBuffer, options?: UltraHdrEncodeOptions): Promise<ArrayBuffer>- Encode UltraHDR image
validateMetadata(metadata: GainMapMetadata): Promise<boolean>- Validate metadataestimateHdrHeadroom(metadata: GainMapMetadata): Promise<number>- Get HDR headroom in stopsisMeaningfulHdr(metadata: GainMapMetadata): Promise<boolean>- Check if HDR is significant
setLocation(path: string): void- Set WASM file location
interface GainMapMetadata {
version: string;
baseRenditionIsHdr: boolean;
gainMapMin: number[]; // RGB, log2 scale
gainMapMax: number[]; // RGB, log2 scale
gamma: number[]; // Per-channel gamma
offsetSdr: number[]; // Black point adjustment
offsetHdr: number[]; // Black point adjustment
hdrCapacityMin: number;
hdrCapacityMax: number;
}interface UltraHdrEncodeOptions {
baseQuality: number; // 1-100
gainMapQuality: number; // 1-100
targetHdrCapacity: number; // Typically 2.0-4.0
includeIsoMetadata: boolean;
includeUltrahdrV1: boolean; // Android compatibility
gainMapScale: number; // 1, 2, or 4
}- Emscripten SDK (
emcc,emcmake) - CMake (3.20+)
- Node.js (18+)
# Clone the repository (with submodules — libultrahdr is vendored as one)
git clone --recurse-submodules https://github.com/adamsilverstein/lib-open-ultrahdr.git
cd lib-open-ultrahdr
# Activate emsdk in the current shell
source /path/to/emsdk/emsdk_env.sh
# Install dependencies
npm install
# Build WASM module (emcmake + cmake + emcc)
npm run build:wasm
# Build TypeScript wrapper
npm run build:jsnpm testDual-licensed under Apache-2.0 OR MIT. See LICENSE for the
preamble plus LICENSE-APACHE and LICENSE-MIT
for the full texts.
This matches the licensing of upstream
libultrahdr, which is bundled as a
git submodule under wasm/third_party/libultrahdr/ and statically linked
into the WASM build.
This project uses GitHub Actions to publish packages to npm.
Create a GitHub release with a version tag (e.g., v0.1.2). The workflow will automatically:
- Build both packages
- Run tests
- Publish
open-ultrahdr-wasmto npm - Publish
open-ultrahdrto npm
- Go to Actions > Publish to npm in the GitHub repository
- Click Run workflow
- Optionally enable "Perform a dry run" to test without publishing
- Click Run workflow
- Update version numbers in both
wasm/package.jsonandjs/package.json - Ensure
js/package.jsonhas the correctopen-ultrahdr-wasmdependency version - Commit version changes to main branch
This project uses npm trusted publishing with OIDC, which eliminates the need for long-lived npm tokens:
-
Configure trusted publishing for both packages on npm:
- Visit the package settings on npm for
open-ultrahdr-wasmandopen-ultrahdr - Set up GitHub Actions as a trusted publisher
- Configure the workflow:
npm-publish.yml
- Visit the package settings on npm for
-
Ensure the GitHub Actions workflow has the required OIDC permissions:
permissions: id-token: write- Required to request the GitHub OIDC JWTpermissions: contents: read- Required to access repository contents
-
npm CLI v11.5.1 or higher is required for trusted publishing to work.
No secrets need to be added to the repository. Authentication is handled automatically via GitHub's OIDC token.
Contributions are welcome! Please open an issue or pull request on GitHub.