LightPhotos

Two RAW decoders, side by side

Last time I checked which cameras each app opens. This time, what happens inside. LightPhotos decodes every RAW with the rawler library, except on the Mac. LightCraft decodes every RAW with its own Rust crate, everywhere. Same files, two very different designs.

rawler · what LightPhotos uses off the Mac

one call, one fixed pipeline
RawSource · memory-mapped file
decode() → undeveloped RawImage
RawDevelop · one fixed pipeline
LightPhotos adds: parallel gamma + boost, denoise, resize
sRGB8 RGBA

LightCraft · its own crate

stages you can call on their own
probe() · recognise the container
decode() or probe_info()
normalized() · black + white + crop + DNG opcodes
demosaic() · AHD, PPG or bilinear
develop() · opcode list 3 + default crop
colour: DNG model, 52 spectral matrices, or a per-file fit
linear Rec.2020

rawler: one call, one fixed pipeline, a 738-body settings table, LGPL-2.1, used off the Mac. LightCraft: five callable stages, colour from the file, MIT or Apache-2.0, everywhere.

Select a decoder to focus it. rawler keeps its shape whatever the camera; LightCraft lets the caller stop between stages. The names in the boxes are the actual functions in each codebase.

One pipeline, or five stages

rawler hands you an image and one way to finish it. decode() reads the file; then RawDevelop runs the same eight steps in the same order every time — rescale, demosaic, Fuji rotate, crop the active area, white balance, calibrate the colour, crop the default frame, sRGB. There is no header-only read and no hook between the steps.

LightCraft's crate breaks that into stages you can call on their own. probe recognises the container. probe_info reads the geometry, orientation, colour filter array, colour data and metadata without decompressing a single pixel — a compressed Nikon NEF probes in milliseconds from its first 128 rows. Then normalized subtracts black, scales white and crops while applying the DNG opcode lists; demosaic turns the mosaic into camera RGB; develop finishes it.

That difference has a cost on our side. rawler's last step is an sRGB gamma applied one pixel at a time on rawler's own thread pool, which is most of a 24-megapixel develop. LightPhotos skips that step entirely and runs the same curve in parallel across the machine, then denoises the finished image, because RawDevelop has nowhere to insert a filter mid-pipeline. When the stages are separate, you do not need any of that.

Where the colour comes from

rawler carries a database: 738 TOML files, one per camera body, next to 48 lens files. Each body file holds its colour matrices, black and white levels, active and crop areas and filter array. The calibrate step picks a matrix by illuminant and prefers D65. DNG files instead use the matrices recorded in the file itself.

LightCraft implements the DNG colour model directly — dual-illuminant interpolation, forward matrices, camera calibration, analog balance — and turns camera RGB into linear Rec.2020. A file with no matrix falls back, in order, to 52 spectral matrices fitted to measured sensor sensitivities, a per-file fit against the camera's own JPEG, and finally a documented neutral model, with eight bundled camera profiles for the most common bodies.

One is a table of camera bodies. The other is derived from the file.

Demosaic and develop

For Bayer sensors rawler uses PPG (patterned pixel grouping). For X-Trans its develop path uses a bilinear pass; a better Markesteijn module ships in the crate but the pipeline does not use it. It also has a quick 2×2 superpixel mode that LightPhotos leans on for the first preview.

LightCraft defaults to AHD (adaptive homogeneity-directed), with PPG and bilinear for drafts and a thumbnails path, and its own edge-weighted directional demosaic for X-Trans. It also develops straight from the mosaic at a fraction of the size, so a preview never has to touch every pixel.

rawler's demosaic dispatch is written to panic on a filter array it does not know. Ordinary Bayer and X-Trans files never reach those arms, but it is why LightPhotos wraps rawler in guards and never lets it run a decode without a memory cap. LightCraft's crate is #![forbid(unsafe_code)] and denies the clippy lints for panics, unwraps and todo!, so the same class of file is an error it returns, not a crash.

What each one opens

Format rawler LightCraft
DNG Yes, including JPEG XL and linear Yes, plus floating point, ProRAW gain maps and semantic masks; writes DNG
Canon CR2 / CR3 Yes Yes; some CR3 CRX variants refused
Sony ARW Yes Yes
Nikon NEF Yes Yes; not HE / HE★
Fujifilm RAF Yes Yes; pre-X bodies refused
Panasonic RW2, Pentax PEF Yes Yes
Olympus ORF Yes, including compressed Uncompressed only
Medium format, older bodies IIQ, X3F, MRW, KDC, MOS, DCR, ERF, MEF ERF and MEF only; the rest show their preview

So rawler is the wider one on paper, and the compressed ORF it reads is the single biggest hole in LightCraft's list. LightCraft is the deeper one inside DNG, where phones and drone cameras live.

Licence, patches and panics

rawler is licensed under LGPL-2.1. That is why the Mac build of LightPhotos never links it: the App Store app must not carry a copyleft library, so on macOS the decode goes through Apple's ImageIO and Core Image instead. Off the Mac, LightPhotos pins rawler 0.8.0 and applies four local patches — a clock source for the browser, lossless-JPEG restart markers, a guard against a panic on an unusual filter array, and a Canon sRAW black-level fix. On top of the library it adds a 500-million sample cap, a Linear-DNG level normaliser, a Fujifilm dynamic-range reader, and a catch_unwind around metadata and preview reads.

LightCraft's own crate is MIT or Apache-2.0 and needs none of that. It is pure Rust, forbids unsafe, and treats a malformed file as an error it returns. Licensing is a design input here, not an afterthought.

What this means for LightPhotos

Neither decoder is simply better. rawler buys breadth across older and less common systems, at the price of an LGPL dependency we have to keep off the Mac, a patch set we maintain against an upstream crate, and defensive guards around code that can panic. LightCraft trades some of that breadth for depth inside DNG and a shape that is easy to trust: no unsafe, no panics, colour from the file.

If LightPhotos ever replaces rawler off the Mac, LightCraft's crate is the closest thing to a drop-in in spirit — and its permissive licence would remove the LGPL constraint altogether. It would just have to catch up on compressed ORF and the older containers first. That is a trade for another day.

The rawler camera database is at dnglab/rawler. LightCraft's format coverage is measured in docs/raw-parity.md. LightPhotos' rawler wrapper and its guards live in src/decode/rawler/.