Skip to content

nhebling/dcraw-wasm

Repository files navigation

dcraw-wasm

WebAssembly (WASM) RAW camera parser for browser apps

dcraw-wasm compiles dcraw.c to WebAssembly and exposes a JavaScript API that can:

  • extract RAW metadata
  • extract embedded JPEG thumbnails

The package now exposes a typed TypeScript-first public API with convenience layers for browser and Node batch workflows.

It currently exposes three API styles: an advanced RawDecoder facade for full control, browser convenience helpers for file/drop workflows, and Node.js batch helpers for mass processing. See API, Convenience Layers, and Node.js Batch Processing for details.

This package is intended for browser-based workflows where RAW files are provided as Uint8Array.

Installation

npm i dcraw-wasm

Quick Start (Browser)

import { analyzeBrowserFiles } from 'dcraw-wasm/browser';

const results = await analyzeBrowserFiles(fileInput.files, {
	includeMetadata: true,
	includeThumbnail: true,
});

for (const result of results) {
	if (result.error) {
		console.error(result.fileName, result.error);
		continue;
	}

	console.log(result.metadata?.propertyMap['camera.model']?.value);
	console.log(result.thumbnail?.length);
}

Quick Start (RawDecoder direct)

import { RawDecoder } from 'dcraw-wasm';

const decoder = new RawDecoder();

// Each method creates its own isolated runtime — safe to call repeatedly.
const metadata = await decoder.readMetadata(rawBuffer);
const thumbnail = await decoder.extractThumbnail(rawBuffer);

console.log(metadata.propertyMap['camera.model']?.value);

If your bundler or hosting setup serves static assets from a custom path, use locateFile to point to dcraw.wasm:

const decoder = new RawDecoder();
await decoder.init({ locateFile: (path) => `/static/wasm/${path}` });

const metadata = await decoder.readMetadata(rawBuffer);

API

new RawDecoder()

Creates the advanced decoder facade with full control. No initialization step is required before calling decode methods.

await init(moduleOptions?)

Optional warmup step. Stores module options (e.g. locateFile) that are forwarded to all isolated runtime instances created by subsequent decode calls. Call this before any decode method when you need to customize the WASM module (e.g. custom WASM asset path):

const decoder = new RawDecoder();
await decoder.init({
	locateFile: (path) => `/static/wasm/${path}`,
});

If the default WASM resolution works for your environment, init() can be omitted entirely.

await readMetadata(rawFileBuffer, options?)

Extracts metadata and returns both raw text and typed metadata properties:

type ParsedMetadata = {
	rawText: string;
	lines: string[];
	properties: MetadataProperty[];
	propertyMap: Record<string, MetadataProperty>;
};

Each call to readMetadata on the same instance is isolated and safe to repeat.

await extractThumbnail(rawFileBuffer, options?)

Extracts embedded thumbnail and returns Uint8Array. Each call is isolated.

await analyze(rawFileBuffer, options?)

Runs metadata and thumbnail extraction in one call and returns:

{
	metadata: ParsedMetadata | null;
	thumbnail: Uint8Array | null;
}

await readMetadataSafe(...) and await extractThumbnailSafe(...)

Async safe wrappers that return { ok, data, error }.

runInternal(rawFileBuffer, internalOptions)

Escape hatch for direct low-level option usage. Requires a prior init() call. The underlying runtime is one-shot — calling runInternal more than once on the same RawDecoder instance may produce incorrect results. Prefer the standard async methods for safe repeated use.

Typed Metadata Model

MetadataProperty shape:

type MetadataProperty = {
	id: string; // stable key, e.g. "camera.model"
	label: string; // English display label
	sourceLabel: string; // original dcraw label
	value: string | number | boolean | Date | FractionValue;
	valueType: 'string' | 'int' | 'float' | 'fraction' | 'datetime' | 'boolean' | 'unknown';
	unit: string | null;
	confidence: 'exact' | 'inferred' | 'parsed';
	rawValue: string;
};

Unknown labels are preserved as unknown.<normalized_label> ids.

Convenience Layers

Browser

Import from dcraw-wasm/browser:

  • analyzeBrowserFiles(fileListOrArray, options?)
  • analyzeDroppedFiles(dataTransfer, options?)

Each returns an array of per-file results with metadata, thumbnail, and error isolation.

Node.js Batch Processing

Import from dcraw-wasm/node:

import { analyzeNodeFiles } from 'dcraw-wasm/node';

const summary = await analyzeNodeFiles(['/in/a.ARW', '/in/b.ARW'], {
	concurrency: 2,
	includeMetadata: true,
	includeThumbnail: true,
	thumbnailOutputDir: './thumbs',
});

console.log(summary.succeeded, summary.failed);

Internal Runtime Access

The original low-level class is still available as an internal surface:

import { DcrawWasm } from 'dcraw-wasm/internal';

Most consumers should use RawDecoder and convenience APIs; this path is for low-level control. The underlying DcrawWasm instance should be treated as single-use per initialized instance — repeated decode calls on the same low-level instance may fail with RuntimeError: memory access out of bounds. RawDecoder and the convenience helpers avoid this entirely by isolating each operation in a fresh runtime.

Low-Level Option Examples

Common low-level flags mapped by the internal runtime include:

  • identify: true (-i)
  • verbose: true (-v)
  • extractThumbnail: true (-e)
  • setBrightnessLevel: 1.0 (-b)
  • setInterpolationQuality: 3 (-q)

The typed facade prefers descriptive options (brightness, interpolationQuality, etc.) and maps them internally.

Development

Instructions are explained for macOS; other platforms may vary. Some additional background on building WebAssembly can be found here.

Prepare (Required tools)

Install emscripten:

brew install emscripten

Build

npm run build         # dev profile (default)
npm run build:prod    # optimized profile

Build output is generated in ./bin.

Run Demo

npm run start

This copies runtime files to ./demo and starts emrun with demo/index.html.

Demo index includes:

  • browser convenience API demo (demo/browser/convenience.html)
  • browser RawDecoder demo (demo/browser/raw-decoder.html)
  • node batch API example (demo/node/batch-example.js)

Run the node demo with:

npm run demo:node

Test and verify

npm test

Test workflow:

  • uses test/fixtures-local/SAMPLE.ARW if present
  • otherwise tries downloading https://rawcameraimages.com/demo/SAMPLE.ARW
  • mirrors fixture into test/fixtures/SAMPLE.ARW for runtime compatibility
  • runs smoke tests via node:test

Tarball integration test (publish-shape validation):

npm run test:pack

This packs the module, installs the tarball in a temporary sandbox, and runs one decode.

Motivation

This project is inspired by dcraw.js which sadly seems not been maintained since 2017 at the point of starting this. Due to open PR's, missing ES6 support, missing WASM support I thought it might be a good idea to give it a chance and also learn something new. So I gave it a try.

License

License for dcraw.c.
License for remaining source code in this repository (excluding dependencies and libraries) can be found in LICENSE.