Skip to content
ybouanePublic

About

Fast, memory-safe camera RAW decoder in Rust: ~40 formats, SIMD, GPU, WebAssembly, ICC colour management. CLI, C, C++, Python and JavaScript bindings.

Topics

Resources

Contributing

Security policy

Stars

114 stars

Watchers

0 watching

Forks

Latest commit

Β 

History

360 Commits

Folders and files

Repository files navigation

Built by @ybouane Β· see also LightFlow Β· ImageFlow

FlashRaw

FlashRaw: the fast, safe camera RAW decoder

The fast, safe camera RAW decoder. β–Ά Live demo: open a RAW file right in your browser.

Rust core Β· C, C++, Python and JavaScript bindings Β· SIMD, all cores and the GPU Β· runs in the browser

Version 0.1.0 is published on crates.io, PyPI and npm. See docs/packages.md for every package and its install command.

crates.io docs.rs npm PyPI License: MIT OR Apache-2.0 MSRV 1.85

FlashRaw opens a camera RAW file and gives you the sensor data plus everything needed to interpret it (CFA layout, black and white levels, white balance, colour matrices, crops, orientation), the embedded previews, or a display-ready sRGB image. It is written in memory-safe Rust. Of the 2,016 files in the raw.pixls.us corpus, 1,980 decode (98.2%), and 1,965 of them have pinned output hashes; the other 36 are formats it does not support yet or files that are not RAW (28 Nikon High Efficiency NEF, Light L16, Casio CAM, JPEG-in-DNG, a few more: docs/formats.md). On the same machine as the other open-source decoders it is 7.9×–11.4Γ— faster than LibRaw at decoding, 8.8×–15.4Γ— faster at producing an sRGB image, and needs 1.5×–1.9Γ— less memory for it (medians over 62 popular cameras).

⚑ FlashRaw vs the others

Median over the 62 bodies, phones and drones of benches/popular-cameras.tsv, each library on the files it can decode, same files, same machine, all cores. NΓ— faster = the other library's time Γ· FlashRaw's; NΓ— less memory = the other library's peak memory Γ· FlashRaw's.

FlashRaw compared with 🐧 Linux · Ryzen 7 9700X 🍎 macOS · Apple M1 Ultra
Decode (RAW β†’ 16-bit sensor data)
vs LibRaw unpack() 7.9Γ— faster 11.4Γ— faster
vs RawSpeed (darktable) 2.1Γ— faster 1.5Γ— faster
vs rawler (dnglab, Rust) 3.0Γ— faster 2.7Γ— faster
vs rawpy (Python) 8.4Γ— faster 14.3Γ— faster
vs dcraw (single-threaded) 14.5Γ— faster 29.1Γ— faster
RAW β†’ sRGB image (demosaic, white balance, colour, 8-bit sRGB)
vs LibRaw dcraw_process() 8.8Γ— faster Β· 1.9Γ— less memory 15.4Γ— faster Β· 1.5Γ— less memory
vs LibRaw + Little CMS (linear ProPhoto β†’ sRGB) 15.0Γ— faster Β· 2.8Γ— less memory 21.8Γ— faster Β· 2.2Γ— less memory
vs rawler RawDevelop 6.6Γ— faster Β· 4.1Γ— less memory 6.7Γ— faster Β· 4.5Γ— less memory
vs rawpy postprocess() 9.5Γ— faster Β· 2.0Γ— less memory 20.5Γ— faster Β· 1.6Γ— less memory
vs dcraw (single-threaded) 10.0Γ— faster Β· 1.3Γ— less memory 23.0Γ— faster Β· 1.1Γ— more memory
⚑ FlashRaw GPU (Metal) vs LibRaw dcraw_process() – 17.9Γ— faster

Builds favour the others where possible (LibRaw and RawSpeed with OpenMP and -O3, -march=native on x86-64; the Little CMS transform on all cores; dcraw has no threads). Every number, the method, per-camera and per-format tables, one-thread results, and the cases where FlashRaw is not the fastest (RawSpeed on some uncompressed and Sony lossless files, GoPro GPR; LibRaw on some Canon CR3, rawler on the JPEG XL DNG) and uses more memory (a plain decode on all cores: FlashRaw's parallel decoders keep per-thread buffers) are in docs/benchmarks.md. Reproduce with tools/bench/compare.py.

Bar chart: RAW decode time of LibRaw, rawpy, dcraw, rawler and RawSpeed relative to FlashRaw, median over 62 popular cameras, on Linux and macOS

Bar chart: time from RAW file to an 8-bit sRGB image for LibRaw + Little CMS, LibRaw, rawpy, dcraw and rawler relative to FlashRaw (CPU and GPU), median over 62 popular cameras

🧠 Memory

Bar chart: peak memory of each library relative to FlashRaw, for RAW to sRGB and for decoding only

To produce an sRGB image FlashRaw develops in one fused pass (demosaic, white balance, colour matrix and sRGB encoding per pixel, straight into the output), so it never holds the full-size intermediate images of a classic pipeline: LibRaw keeps a 4-channel 16-bit working image, the LibRaw + Little CMS pipeline adds a 16-bit RGB copy and the 8-bit output, rawler develops in 32-bit floats. Decoding alone needs about the same memory everywhere (the file plus a 16-bit image); with all cores FlashRaw's speculative parallel decoders add per-thread buffers, so a plain all-core decode can take more memory than LibRaw's (details in docs/benchmarks.md).

πŸ“· Per camera

Dot plot: decode and RAW to RGB time in milliseconds for 12 popular cameras, FlashRaw, LibRaw, RawSpeed and rawler, Apple M1 Ultra

🏭 Used in production

LightFlow Professional photo correction and colour grading, running locally in your browser with WebGPU
ImageFlow A Photoshop-class image editor in your browser

Both open camera RAW files with FlashRaw's WebAssembly package, right in the browser.

✨ Why FlashRaw

⚑ Speed

  • Multithreaded decoding wherever the format allows it, including single-stream codecs (lossless JPEG, Nikon, Pentax, Hasselblad, Panasonic v8, Sony ARW1) through speculative parallel decoding; a few strictly sequential codecs (for example Samsung V2) decode on one thread
  • SIMD unpack kernels for packed data (SSSE3/SSE4.1 on x86-64, NEON on aarch64, WASM SIMD128), picked at run time; the rest of the pipeline relies on the compiler's auto-vectorisation
  • 64-bit bit pumps, table-driven Huffman, zero-copy parsing, memory-mapped input
  • Fused single-pass RAW β†’ RGB, on the CPU or the GPU, in sRGB, Display P3, Adobe RGB, ProPhoto or Rec. 2020 at no extra per-pixel cost (ICC-tagged, any ICC profile via icc)
  • Region and tile decodes, zero-copy embedded previews: no full decode when you don't need one

πŸ›‘οΈ Security

  • Memory-safe Rust: parsers that read untrusted files cannot overflow buffers or use freed memory; the few unsafe blocks are audited and justified
  • Bounds-checked parsing, checked offset arithmetic, IFD loop and depth tracking
  • Allocation limits checked before allocating
  • 15 fuzz targets, run locally before releases; crash inputs replayed forever
  • Malformed-file and truncation suites, and an explicit truncation policy: never silent garbage

πŸ“· Support

  • ~40 RAW formats: 1,980 of the 2,016 raw.pixls.us corpus files decode (98.2%), 1,965 with pinned output hashes; the other 36 are formats not supported yet (mainly Nikon High Efficiency NEF)
  • CR3 incl. C-RAW, JPEG XL DNG, lossy and deflate DNG, GoPro GPR, Sigma X3F, Fuji compressed, Panasonic v4–v8, IIQ, 3FR/FFF, …
  • GPU: Metal, Vulkan, DX12 and WebGPU
  • Everywhere: Rust, C, C++, Python, npm (browsers, Web Workers, Node)
  • Tested on Linux x86-64 and macOS arm64; WebAssembly in browsers and Node.js; Windows and other architectures are not regularly tested

πŸ†š Feature comparison

⚑ FlashRaw LibRaw RawSpeed rawler / dnglab dcraw rawpy Little CMS ¹
What it is RAW decoder + developer RAW decoder + developer RAW decoder (darktable) RAW decoder + DNG converter RAW converter program Python binding of LibRaw colour-management engine
Written in πŸ¦€ Rust C++ C++ πŸ¦€ Rust C Python + C++ (LibRaw) C
Memory-safe βœ… (a few audited unsafe blocks) ❌ ❌ ⚠️ Rust, but it panics on bad input and advises against untrusted files ❌ ❌ ❌
Uses the GPU βœ… Metal, Vulkan, DX12, WebGPU ❌ ❌ ❌ ❌ ❌ ❌
Multithreading βœ… wherever the format allows (speculative decoding of sequential streams) + develop ⚠️ OpenMP builds: CR3, Fuji, Panasonic v8, some demosaics ⚠️ OpenMP: tiled/sliced codecs βœ… rayon: tiles, several codecs, develop ❌ ⚠️ as LibRaw; wheels: OpenMP on Linux/Windows, not macOS ⚠️ one thread per transform (threaded plugin: GPL-3 or commercial)
SIMD kernels ⚠️ packed-bit unpack only: SSSE3/SSE4.1, NEON, WASM SIMD128 ❌ ⚠️ one SSE2 routine ⚠️ compiler multiversioning, no intrinsics ❌ ❌ ⚠️ SSE2 in the fast_float plugin (GPL-3 or commercial)
Cameras 1,980 of the 2,016 raw.pixls.us corpus files decode (98.2%), 1,600+ entries in its camera DB, any DNG 1,284 (its own count) ~920 make/model ~850 make/model ~570 colour profiles (last release 2018) as LibRaw –
Canon CR3 (incl. C-RAW) βœ… βœ… ❌ βœ… ❌ βœ… –
JPEG XL DNG (DNG 1.7, e.g. Samsung Galaxy) βœ… ⚠️ Adobe DNG SDK builds only ❌ βœ… ❌ ❌ –
GoPro GPR (VC-5) βœ… ⚠️ GPR SDK builds only βœ… ❌ ❌ ❌ –
DNG opcode lists βœ… all radiometric opcodes, per stage ⚠️ Adobe DNG SDK builds only ⚠️ stage-1 subset ❌ ⚠️ one opcode (lossy DNG) ⚠️ as LibRaw –
RAW β†’ sRGB image βœ… one fused pass, CPU or GPU βœ… dcraw_process(), many demosaics ❌ raw data only βœ… RawDevelop βœ… βœ… postprocess() ⚠️ converts colours, does not decode RAW
ICC colour management βœ… built-in spaces (sRGB, P3, Adobe RGB, ProPhoto, Rec. 2020) + embedded profiles; any ICC v2/v4 profile via the icc feature ⚠️ with Little CMS compiled in ❌ ❌ ⚠️ with Little CMS ⚠️ as LibRaw (Little CMS in the wheels) βœ… ICC v2/v4
Embedded previews βœ… zero-copy βœ… copied ❌ ⚠️ some formats, decoded βœ… -e βœ… extract_thumb() –
Region / tile decode βœ… ❌ ❌ ❌ ❌ ❌ –
C / C++ API βœ… flashraw.h, flashraw.hpp βœ… ⚠️ C++ only ❌ Rust only ❌ command line only ❌ βœ… C
Python βœ… official, NumPy zero-copy, releases the GIL ⚠️ third-party (rawpy) ❌ ❌ ❌ βœ… ⚠️ third-party
JavaScript / WASM, runs in the browser βœ… official npm package (WASM SIMD, threads, WebGPU) ⚠️ third-party ports ❌ ❌ ⚠️ third-party ports ❌ ⚠️ third-party ports
Rust βœ… native ⚠️ third-party bindings ❌ βœ… native ❌ ❌ ⚠️ third-party crate
LibRaw-compatible C API βœ… subset (recompile and relink) – ❌ ❌ ❌ – –
Fuzzed βœ… 11 in-tree targets, run locally before releases βœ… OSS-Fuzz βœ… OSS-Fuzz + in-tree ⚠️ 8 in-tree targets ❌ ⚠️ relies on LibRaw βœ… OSS-Fuzz + in-tree
License MIT / Apache-2.0 LGPL-2.1 / CDDL-1.0 LGPL-2.1+ LGPL-2.1 own licence (Foveon code: GPL-2+) MIT (bundles LibRaw) MIT

ΒΉ Little CMS is not a RAW decoder: it is compared as the colour-management stage of a typical LibRaw-based pipeline (see the benchmarks above). Checked against the sources and docs of LibRaw 0.22, RawSpeed (develop, c835b05), rawler 0.8.0, dcraw 9.28, rawpy 0.27.1 and Little CMS 2.19.1 (October 2026). Camera counts are not directly comparable: LibRaw counts its own way, the RawSpeed and rawler figures are unique make/model pairs in their camera lists, dcraw's is the size of its colour table. Nikon High Efficiency (TicoRAW) is a proprietary codec no open decoder supports.

🏁 Benchmarks: popular cameras

Milliseconds per file on an Apple M1 Ultra (20 cores, macOS), all cores, median of 7 runs, lower is better, bold = fastest. Decode = open + full sensor decode to 16-bit samples. RAW β†’ sRGB = decode + demosaic + white balance + colour conversion to an 8-bit sRGB image (RawSpeed has no RGB pipeline; LibRaw + Little CMS develops to linear ProPhoto RGB and converts it with Little CMS). ❌ = not supported.

Camera Format Β· codec MP Decode
FlashRaw
LibRaw RawSpeed rawler RAW β†’ sRGB
FlashRaw
LibRaw LibRaw +
Little CMS
rawler
Canon EOS R5 (RAW) CR3 · canon-crx 47 80.9 138 ❌ 237 109 737 1104 492
Canon EOS R6 Mark II (RAW) CR3 · canon-crx 25 45.7 76.9 ❌ 136 61.0 406 617 265
Canon EOS 5D Mark IV (RAW) CR2 Β· lossless-jpeg 31 20.5 431 110 251 39.8 837 1100 415
Nikon Z8 (14-bit lossless) NEF Β· nikon-huffman 46 44.0 544 218 356 72.4 1156 1542 601
Nikon Z6III (lossless) NEF Β· nikon-huffman 24 25.0 285 110 186 41.5 629 824 320
Nikon D850 (14-bit lossless) NEF Β· nikon-huffman 46 45.1 488 170 325 72.7 1102 1496 575
Sony A7 IV (lossless) ARW Β· sony-lossless 37 19.2 414 11.7 38.7 40.8 870 1158 220
Sony A7 IV (compressed) ARW Β· sony-arw2 33 5.86 87.4 8.77 8.38 26.3 536 814 192
Sony A7R V (lossless) ARW Β· sony-lossless 65 33.2 761 24.2 61.3 70.5 1538 2097 395
Sony A1 (compressed) ARW Β· sony-arw2 50 8.14 132 13.4 11.8 39.3 797 1220 283
Fujifilm X-T5 (lossless compressed) RAF Β· fuji-compressed 41 35.5 151 44.4 93.9 74.4 697 1032 388
Fujifilm X-H2 (compressed) RAF · fuji-compressed 41 46.5 173 ❌ 127 94.8 878 1203 420
Fujifilm GFX100 II (lossless compressed) RAF Β· fuji-compressed 103 67.3 267 76.6 143 125 1569 2451 688
Panasonic Lumix S5II RW2 Β· panasonic-v8 24 15.1 131 62.4 119 30.4 477 686 226
Panasonic Lumix GH6 RW2 Β· panasonic-v8 25 17.0 133 63.5 117 32.9 498 718 232
OM System OM-1 Mark II ORF Β· olympus 20 116 381 162 225 126 678 846 341
Pentax K-3 Mark III PEF Β· pentax-huffman 27 29.8 204 83.6 174 48.2 562 772 321
Ricoh GR III DNG Β· lossless-jpeg 25 18.1 316 96.2 214 34.1 659 864 343
Leica Q2 DNG Β· uncompressed 47 4.04 293 49.2 4.54 33.1 926 1324 259
Leica M10-R DNG Β· lossless-jpeg 41 28.9 559 197 381 54.9 1085 1456 605
Hasselblad X2D 100C (compressed) FFF · hasselblad 105 76.6 1013 451 ❌ 144 2316 3204 ❌
Phase One IQ4 150MP (IIQ L) IIQ · phaseone 154 42.1 1006 ❌ 341 133 4305 5692 952
Sigma fp L DNG Β· lossless-jpeg 60 28.0 761 22.4 40.9 72.1 1566 2081 364
Apple iPhone 12 Pro (ProRAW) DNG · lossless-jpeg 12 15.4 469 ❌ 27.8 24.3 592 688 74.4
Google Pixel 8 Pro DNG Β· lossless-jpeg 50 23.1 656 18.9 34.2 59.1 1446 1891 303
Samsung Galaxy S23 Ultra DNG Β· lossless-jpeg 50 129 1916 550 1135 178 2194 2507 1292
Samsung Galaxy S22 (JPEG XL) DNG · jpeg-xl 12 272 ❌ ❌ 173 289 ❌ ❌ 212
DJI Phantom 4 Pro DNG Β· uncompressed 20 1.22 17.0 0.99 1.36 18.9 292 474 117
GoPro HERO11 Black (GPR) DNG · gopro-vc5 27 39.8 ❌ 18.5 ❌ 60.2 ❌ ❌ ❌

The full list (62 bodies, phones, drones and action cameras), the same tables on Linux x86-64, rawpy and dcraw, single-thread results, GPU timings, peak memory per camera and per-format results are in docs/benchmarks.md. Reproduce everything with tools/bench/compare.py; the charts come from tools/bench/charts.py and their numbers are in docs/charts/data.json.

πŸ›‘οΈ Security

RAW files are untrusted input: they come from the web, from email, from memory cards. FlashRaw is built for that.

  • βœ… Memory-safe Rust. Parsing and decoding are safe Rust. unsafe is confined to SIMD intrinsics, zeroed allocation, a disjoint-tile writer, the input self-reference, the WASM clock hook and the mmap call, each with a SAFETY: justification and tests (architecture).
  • βœ… Fuzzed. Fifteen cargo-fuzz targets (the whole open/decode pipeline over every format under both corruption policies, the RGB develop path with several outputs, colour spaces and backends, parallel against single-threaded decode, region and tile decode against the full decode, varying resource limits, TIFF and BMFF parsers, bit pumps against a model, Huffman, lossless JPEG, packed unpack, deflate/float, DCT, DNG opcodes, ICC profiles) are run locally before releases (tools/ci/local.sh, 30 s per target; longer runs by hand); there is no continuous fuzzing service. Every crash input is kept and replayed.
  • βœ… Malformed-file suite. Truncation at every cut point, invalid offsets and counts, recursive IFDs, overflowing dimensions, broken Huffman tables, corrupted entropy data, garbage behind every magic number, and mutations of a real file of every extension: each must finish without panicking within a time bound (testing).
  • βœ… Resource limits. Image sizes are checked against Limits (samples, dimensions) before anything is allocated, so a 20-byte file cannot ask for 64 GB.
  • βœ… No silent garbage. Sensor data that runs past the end of the file is recovered with structured warnings or rejected, as you choose (see truncated and corrupt sensor data).
  • βœ… Sandboxable. The npm package runs the decoder inside WebAssembly: a decoder bug cannot corrupt the host process.

πŸ“· Supported formats, cameras and platforms

Formats DNG (uncompressed, lossless JPEG, deflate int/float, lossy JPEG, JPEG XL, GoPro VC-5, Blackmagic), CR2 (incl. sRAW/mRAW), CR3 (CRX and C-RAW), CRW, NEF/NRW, ARW/SR2/SRF/ARQ, RAF (incl. compressed X-Trans), RW2/RWL (Panasonic v4–v8), ORF/ORI, PEF, SRW, MRW, IIQ, MOS, 3FR/FFF, KDC/DCR/DCS, ERF, MEF, X3F, ARI, headerless and CHDK raws
Cameras 1,980 of the 2,016 raw.pixls.us corpus files decode (98.2%; 1,965 with pinned hashes), 1,600+ entries in the built-in camera database, and DNG from cameras, phones and drones
Not supported yet 36 corpus files are valid files in formats that are not implemented: 28 Nikon High Efficiency NEF (proprietary TicoRAW codec), Light L16, Casio CAM, JPEG-in-DNG and a few rare variants (see formats). Their metadata and embedded previews still work
Outputs raw u16 / f32, normalised f32, linear RGB f32, display RGBA8 (bilinear or half-size demosaic, as-shot white balance, colour matrix, sRGB, orientation), GPU-resident textures
GPU wgpu: Metal, Vulkan and DX12 natively, WebGPU in browsers; Backend::Auto uses the GPU only where it measures a win (GPU)
Languages Rust, C, C++, Python, JavaScript/TypeScript, plus a LibRaw-compatible C API
Platforms tested on Linux x86-64 and macOS arm64; WebAssembly in browsers (Chromium, Firefox and WebKit are in the browser test suite), Web Workers and Node.js, multithreaded where the page is cross-origin isolated. Windows and Linux ARM64 are not regularly tested

Support is tracked per format and mode; flashraw supported (and docs/formats.md) lists each with one of these statuses:

Status Meaning
verified representative corpus files decode with full-buffer hashes pinned by the regression suite, metadata checked, tests and corruption handling in place
partial decodes a documented part of the mode (for example only the first frame of a burst or pixel-shift file, or without the vendor's calibration block); the notes say what is missing
experimental implemented from the format structure with few or no sample files, so not covered by pinned hashes
unsupported detected and rejected with a clear error
supported used for a feature rather than a codec mode (currently only DNG opcode lists): implemented, applied by default and covered by tests and fuzzing, with no per-file pinned hash

Platforms

Tested by the maintainer with tools/ci/local.sh and tools/ci/mac.sh (the repository has no automatic GitHub workflows): Linux x86-64 and macOS arm64 (Rust, C, Python, npm, WASM). The workflows in .github/workflows/ define builds for Linux ARM64, macOS x86-64 and Windows (wheels, the C library, the LibRaw shim) but run only on manual dispatch and have not been run regularly; Windows paths are untested. See docs/testing.md.

πŸš€ Quick start

πŸ¦€ Rust

[dependencies]
flashraw = "0.1"
use flashraw::{FlashRaw, DecodeOptions, Backend, OutputFormat};

fn main() -> flashraw::Result<()> {
    let raw = FlashRaw::open_path("photo.NEF")?;          // memory-mapped, headers only

    let m = raw.metadata();
    println!("{} {}x{} ISO {:?}", m.camera(), m.width, m.height, m.exposure.iso);

    // Embedded JPEG preview, zero-copy, without decoding the RAW payload:
    if let Some(p) = raw.preview() {
        std::fs::write("preview.jpg", raw.preview_bytes(p)?)?;
    }

    // Sensor data:
    let frame = raw.decode(DecodeOptions {
        backend: Backend::Auto,
        output: OutputFormat::RawU16,
        ..Default::default()
    })?;
    println!(
        "{}x{} CFA {:?} black {:?} white {:?} via {}",
        frame.width(), frame.height(), frame.cfa, frame.black_level.values,
        frame.white_level.values, frame.stats().backend.decode
    );

    // Display-ready RGBA8 (bilinear demosaic, as-shot WB, colour matrix, sRGB, orientation):
    let rgba = raw.decode(DecodeOptions { output: OutputFormat::Rgba8, ..Default::default() })?;
    Ok(())
}

Input sources: FlashRaw::open(&[u8]) (zero-copy borrow), open(Vec<u8>), open(Arc<[u8]>), open(memmap2::Mmap), open_path(path), from_reader(reader).

Other entry points: decode_region(rect, opts), decode_tile(index, opts), units() (independently decodable tiles/strips with byte ranges), capabilities(), previews() / thumbnail() / decode_preview_rgb(), flashraw::read_metadata(bytes), flashraw::supports(bytes).

Output formats: RawU16 (default), RawF32, NormalizedF32 (black-subtracted, white-scaled), linear sRGB-primaries RGB as LinearRgbF32, LinearRgbF16 (half floats, half the memory) or LinearRgb16, and display-ready sRGB as Rgba8 or Rgb16 (16-bit, orientation applied). RGB outputs take exposure (EV) and black_point / white_point convenience controls. Statistics (frame.stats()) report per-stage timings and the backend that ran.

🐍 Python

pip install flashraw
import flashraw

with flashraw.open("photo.NEF") as raw:
    print(raw.metadata.camera.name, raw.metadata.exposure.iso)
    sensor = raw.decode().array                  # numpy uint16 (h, w), zero-copy
    rgba = raw.decode(output="rgba8").array      # numpy uint8 (h, w, 4), display-ready

Decoding releases the GIL, so Python threads decode files in parallel. See docs/python.md.

🌐 Browser and Node.js (npm)

import { FlashRaw } from "flashraw";

const buffer = await (await fetch("/photo.CR3")).arrayBuffer();
const raw = await FlashRaw.open(buffer);

console.log(raw.metadata.camera, raw.metadata.width, raw.metadata.height);
const preview = await raw.preview();                      // embedded JPEG, instantly
const frame = await raw.decode({ backend: "auto", output: "raw-u16" });
console.log(frame.width, frame.height, frame.cfa, frame.backend, frame.timings);

const img = await raw.decode({ output: "rgba8" });
ctx.putImageData(new ImageData(img.data, img.width, img.height), 0, 0);
raw.close();

See npm/README.md for Node, React/Vite, Web Workers, metadata-only, preview extraction, GPU and CPU fallback examples, and docs/wasm.md.

πŸ”§ C

#include <flashraw.h>

flashraw_file *f = NULL;
if (flashraw_open_path("photo.NEF", NULL, &f) != FLASHRAW_OK) {
    fprintf(stderr, "%s\n", flashraw_last_error_message());
    return 1;
}
flashraw_decode_options o;
flashraw_decode_options_init(&o);
o.output = FLASHRAW_OUTPUT_RGBA8;
flashraw_frame *fr = NULL;
if (flashraw_decode(f, &o, &fr) == FLASHRAW_OK) {
    const uint8_t *rgba = flashraw_frame_data(fr);  /* width x height x 4 bytes */
    flashraw_frame_free(fr);
}
flashraw_file_free(f);

βž• C++

#include <flashraw.hpp>

flashraw::file raw = flashraw::file::open("photo.CR3");   // throws flashraw::error
flashraw::decode_options o;
o.output = flashraw::output_format::linear_rgb_f32;
flashraw::frame img = raw.decode(o);
flashraw::span<const float> rgb = img.f32();             // no copy

Build with cargo build --release -p flashraw-capi or install with CMake (find_package(flashraw), pkg-config). See docs/c-api.md.

πŸ” Coming from LibRaw?

flashraw-libraw-compat implements the commonly used part of the LibRaw C API (libraw_init, libraw_open_file, libraw_unpack, libraw_dcraw_process, libraw_dcraw_make_mem_image, …) on top of FlashRaw. Keep your #include <libraw/libraw.h>, recompile against its headers and link -lraw from FlashRaw. See docs/libraw-compat.md for the supported subset and deviations.

πŸ’» CLI

cargo install flashraw-cli                          # CPU only: no `gpu` and no `jpegxl` feature
cargo install flashraw-cli --features gpu,jpegxl    # + GPU backend (`--backend gpu`) and JPEG XL DNG
# from a checkout: cargo install --path crates/flashraw-cli [--features gpu,jpegxl]

flashraw info photo.CR3                     # summary (--json for machine output)
flashraw dump-metadata photo.ARW            # everything, as JSON
flashraw decode photo.NEF out.raw           # raw u16 samples (.pgm/.tiff also supported)
flashraw decode photo.RAF out.jpg --output rgba8 --profile
flashraw decode photo.DNG tile.pgm --tile 12
flashraw decode photo.DNG crop.pgm --region 1000,800,512,512
flashraw preview photo.CR2 preview.jpg      # embedded full-size JPEG
flashraw thumbnail photo.CR2 thumb.jpg
flashraw benchmark photo.NEF --iterations 10 --threads 8
flashraw verify photo.ORF --json            # geometry, levels and full-buffer hashes
flashraw verify cut.DNG --json --strict     # fail on truncated data (default: recover + warnings)
flashraw supported                          # format/mode support matrix
flashraw camera-db validate|generate|compare|stats|coverage|crops|probe

Options of decode and benchmark (flashraw decode --help is authoritative): --format, --backend auto|cpu|simd|gpu, --output raw-u16|raw-f32|normalized|linear-rgb|linear-rgb16|linear-rgb-f16|rgba8|rgb16, --region x,y,w,h, --tile N, --threads N, --demosaic bilinear|half, --no-opcodes, --exposure EV, --black-point V, --white-point V, --strict, --preview-fallback, --color-space srgb|display-p3|adobe-rgb|prophoto|rec2020, --output-profile FILE.icc, --intent perceptual|relative|saturation|absolute, --bpc, --profile, --json. Names differ slightly between bindings: docs/api-names.md.

$ flashraw decode photo.DNG out.jpg --output rgba8 --profile
Container parse:             0.05 ms
Pixel unpack:                4.75 ms
Post-process:               28.82 ms
Total:                      34.85 ms

Backend:
  decode: CPU SSE4.1 x16 threads
  postprocess: CPU x16

πŸ“š Reference

Truncated and corrupt sensor data

A strip, tile or other sensor-data range that runs past the end of the file is never silently zero-filled. DecodeOptions::corruption_policy decides what happens:

  • CorruptionPolicy::Recover (default): every complete sample is decoded, missing ones are zero, and the frame says so: frame.is_incomplete() and frame.warnings(), each a DecodeWarning with the unit (strip/tile index), the bytes expected and available and the samples filled ("recovered truncated strip 17: 48291 / 131072 bytes available; 41390 missing samples were zero-filled").
  • CorruptionPolicy::Strict: the decode fails with Error::CorruptFile; e.truncation() (or e.corrupt_reason()) gives the same details ("truncated image data: strip 17 expected 131072 bytes at offset 24576000, but only 48291 bytes remain before EOF"). e.kind() stays "CorruptFile".

Only the data a decode needs is checked: metadata, previews and region decodes that do not touch the missing data work normally. Short data that still holds every sample is complete: the last row needs no padding to the row stride, and a declared length beyond the last needed sample may run past the end of the file. Recovery covers uncompressed and bit-packed payloads in every format and compressed DNG strips/tiles (a unit that is truncated or fails to decode is zero-filled with a warning), and Nikon and Pentax Huffman and Samsung SRW streams cut short by the end of the file (the rows decoded from real data are kept). Other compressed streams (one entropy-coded stream per image) fail under both policies; when their declared range runs past the end of the file the error carries the same truncation details. A file cut inside its directory structure, before the raw image's IFD, fails with Error::CorruptFile: a thumbnail or preview is never decoded in place of the sensor image.

Undecodable sensor data

Sensor data in a codec FlashRaw cannot decode (Nikon High Efficiency NEF) fails with Error::UnsupportedCompression, also for RGB outputs. With DecodeOptions::preview_fallback, Rgba8 and LinearRgbF32 decodes return the embedded full-size preview instead, explicitly marked: frame.source == FrameSource::Preview and a DecodeWarning whose reason is CorruptReason::PreviewFallback. Such a frame is the camera's 8-bit rendering, not sensor data; raw outputs and regions never fall back. The CLI (--preview-fallback), JavaScript (previewFallback), Python (preview_fallback=) and C (flashraw_decode_options.preview_fallback, flashraw_frame_source()) expose the same option.

GPU

Build with --features gpu. Packed/uncompressed payloads are unpacked on the GPU; black subtraction, demosaic, white balance, colour conversion and sRGB encoding run as WGSL compute shaders shared by native (Vulkan/Metal/DX12) and browser (WebGPU) builds; results can stay GPU-resident. Backend::Auto only picks the GPU when its measured cost model predicts a win. See docs/gpu.md.

API stability

FlashRaw follows semver. The public API is everything documented at the crate root: FlashRaw, Source, DecodeOptions and its enums, RawFrame, the metadata types, Error, and the gpu module (feature gpu).

  • Enums that can gain variants (formats, compressions, outputs, backends, errors, stages, preview kinds, ...) are #[non_exhaustive]: match them with a wildcard arm.
  • Result and metadata structs (RawFrame, RawMetadata, FrameInfo, DecodeStatistics, Limits, ...) are #[non_exhaustive]: read their fields; build Limits with Limits::default().with_max_samples(..).
  • Build DecodeOptions with ..Default::default() (new options are added as fields with backwards-compatible defaults, e.g. corruption_policy).
  • Error::CorruptFile and its structured cause (CorruptReason, Truncation, DataUnit) and DecodeWarning are #[non_exhaustive]; read them through their fields and Error::truncation() / Error::corrupt_reason().
  • Backend, OutputFormat, Demosaic and CorruptionPolicy implement FromStr/Display with the names the CLI and the JS bindings use.
  • Minimum supported Rust version: 1.85 for the library with default features and serde; 1.87 with gpu (and for the CLI and WASM crates); 1.88 with jpegxl. Raising it is a minor-version change.
  • Hidden items (low_level, process, decode_raw_image) expose internals for tooling and carry no stability guarantee.

Cargo features

feature default effect
rayon yes multithreaded decoding
simd yes SIMD unpack kernels (SSSE3/SSE4.1 on x86-64 with runtime dispatch, NEON, WASM SIMD128)
mmap yes memory-mapped open_path
gpu no wgpu backend (Vulkan / Metal / DX12 / WebGPU)
jpegxl no JPEG XL compressed DNG (DNG 1.7)
icc yes arbitrary ICC output profiles (DecodeOptions::output_profile); the built-in colour spaces and their profiles need no feature. Every feature and dependency: docs/building.md
serde no Serialize and Deserialize for metadata and option types

πŸ“– Documentation

Architecture Β· Formats Β· Building and features Β· Colour management Β· Benchmarks Β· GPU Β· WASM Β· C and C++ API Β· LibRaw-compatible API Β· Python Β· Packages Β· Releasing Β· Names across bindings Β· Testing Β· Profiling Β· Camera DB Β· Contributing Β· Security Β· Decoder contract Β· For coding agents Β· References

πŸ“„ License

Code and data (including crates/flashraw-camera-db/data/): MIT OR Apache-2.0, at your option. See LICENSE-MIT and LICENSE-APACHE.


FlashRaw
Built by @ybouane Β· Live demo Β· used in LightFlow and ImageFlow

About

Fast, memory-safe camera RAW decoder in Rust: ~40 formats, SIMD, GPU, WebAssembly, ICC colour management. CLI, C, C++, Python and JavaScript bindings.

Topics

Resources

Contributing

Security policy

Stars

114 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages