dtourolle ffdd640170 Backfill the catalog once per state, not on every open
Catalog::open ran schema::backfill every time, and every worker thread
opens its own connection. A develop landing made five opens, and each
paid the RAW/JPEG pairing, the default-version anti-join over every
image, the uuid pass over every default version and the keyword check:
17 ms of CPU an open on a copy of the reference catalog, ~80 ms a
landing, to confirm that nothing had changed since the open before.

Everything the backfill repairs is a row some write added: an image a
scan inserted, a version or keyword assignment a merge brought in. So
the open now reads a stamp - user_version, max(id) of images and
versions, max(rowid) of keywords, and the file's device and inode - and
skips the backfill when the stamp matches the one recorded at this
path's last backfill in this process. The maxima are each the last page
of a b-tree; an open that skips costs ~1 ms.

The backfill still runs:
- on the first open in a process (nothing recorded yet);
- on any open that migrated the schema, unconditionally;
- after a pull: merge_remote forgets the path, so the next open
  backfills even when every incoming row collided and nothing moved;
- when the file is replaced under its name: the inode is in the stamp,
  and recovery::set_aside, the first step of a restore and a rebuild,
  forgets the path;
- when another process or thread adds rows, because the stamp is read
  from the file, not from anything this process did.

The stamp is taken before the backfill, not after. Read after, it would
describe the backfill's own inserts, and could record an image another
connection inserted in between as covered when it was not. Read before,
the worst case is one redundant pass after a backfill that did real work.

Kept in memory rather than in the catalog: a stamp row would need a
table an older build does not have and would travel in the sync
snapshot, where a flag from another device's catalog says nothing about
this one. No schema version bump, so the tablet on 0.16.0 still reads
the snapshot. Tests cover the skip, a scan's new image, a migration, a
pull and a replaced file.
2026-09-26 14:26:10 -04:00
2026-09-26 09:46:32 -04:00
2026-08-26 10:08:51 +02:00
2026-09-26 09:46:32 -04:00
2026-09-26 09:46:32 -04:00

DarkRoom

A non-destructive RAW photo editor and library for Linux and Android, with a GPU develop pipeline, a catalog that syncs between devices, and no account, no telemetry and no cloud of its own.

The library: seventy frames, the timeline beside them, the filter bar above

The manual shows every feature, pictured from the application itself. This page says what it is, how to get it, and what is still missing.

What it does

A library. Point it at a folder — on this machine, on a network mount, or one a Nextcloud client keeps in virtual-files mode, where a placeholder is treated as the photograph rather than as a one-byte file — or at a Nextcloud account directly. The grid is virtualised, ordered by capture time with a timeline beside it, and filtered by rating, flag, colour label, person and whether the file is here. Ratings, colour labels, keywords, collections and a trash that survives a crash mid-operation. Card ingest. Bursts fold. The same RAW catalogued twice — a dated folder and a backup beside it — is found, proved the same, and folded onto one copy with the spares in the trash. Face detection and identity, with the index syncing between devices.

Developing. Eighteen declared operations fused into one compute dispatch, plus the neighbourhood work that cannot be: clarity, texture, capture sharpening, noise reduction, lens correction, spectral film simulation. Crop, straighten and correct converging verticals, spot repair, and local adjustments over masks the model draws — click a subject or a category, then paint, subtract a gradient or keep only where two selections agree, grow or shrink the edge. Focus peaking and a raw histogram for judging what is recoverable. Named presets; XMP sidecars other editors read.

Segmenting an urban scene and choosing the sky as a mask

Panoramas. Select the frames, align, choose a projection, fill the ragged border rather than crop it, and the composite lands beside its sources as a DNG, with a sidecar recording what it was merged from.

Twelve hand-held frames aligned on a cylinder

From the keyboard, and with its manual. Rating, flagging and labelling have keys in the grid and in develop, as do zoom, undo and stepping through a shoot in develop, and none of them is keyboard-only. The help sheet (F1, or ? in develop) lists every key and gesture, generated from the code that binds it, and links them to the sections of the manual that show them — the manual ships with the application and opens offline.

Export. JPEG, PNG, AVIF, JPEG XL, 8- and 16-bit TIFF, with resize, output sharpening, a naming template and a colour space — to a folder here or back into the library.

On both platforms. The same core runs on a desktop and a 12-inch tablet; the interface is one layout, tuned for a wide viewport with touch targets throughout. On both, the develop view draws the compute pass's texture directly — no readback between the GPU and the screen.

Getting it

Platform How State
Arch Linux packaging/PKGBUILD — makepkg -si Built from every release
Android The APK from each CI run, or ./docker/android/package.sh --install Runs on a tablet; F-Droid not yet submitted
Windows DarkRoom-<version>-x86_64-setup.exe, cross-built by CI (windows.md) Verified under Wine only; unsigned
Flatpak packaging/flatpak/ Manifest in tree; choosing a library does not yet work in the sandbox

Or build it. Git LFS is required for the model weights, and the toolchain pins itself to 1.92.0:

git lfs install && git lfs pull
cargo run --release -p darkroom-desktop

Android, through the containerised toolchain (docker/android):

./docker/android/build.sh cargo ndk -t arm64-v8a build --release

CONTRIBUTING.md has the system packages, the four commands CI runs against what you send, and the shortest useful contribution — a develop operation is one YAML file, and it arrives with its controls, its place in the chain and its tests.

Where it stands

0.16.0, twenty-four tagged releases in. 191 numbered requirements in scope, 84% of them claimed by code and traced to it; the rest are written down rather than merely absent.

Not built: plugins (post-v1, D12), compare and survey culling, AI denoise, tiled rendering, HDR merge and focus stacking, importing a Lightroom or darktable catalog, translations beyond the launch screen, most of the Android platform integration beyond running, and the Flatpak's library chooser. The performance targets are half verified: the per-commit benchmark suite §8 requires exists for everything that does not need a frame — the catalog, the scan, the thumbnails — and not yet for the render path, so a regression there fails nothing. outstanding.md is the list, with the reasoning for each.

Documentation

docs/README.md is the index. The short version, for someone using it:

manual Every feature, pictured
gestures.md How it is driven — generated from the code, so it cannot describe a gesture that does not exist

For someone changing it:

CONTRIBUTING.md How to land a first change without reading the rest
requirements.md What the software must do — the numbered register, and the decisions
architecture.md How it is built — crates, the GPU pipeline, the data model, sync
technical-debt.md Compromises taken deliberately, each with the condition that retires it
outstanding.md What is not built, and whether that is a decision or a gap
code-health.md What a contribution costs, per seam, measured
traceability.md Generated: which requirement is claimed by which file

Designs, one per subsystem: segmentation and mask editing · spot removal · panorama · faces · inference · storage and sync · catalog · display and extension · navigation · distribution · windows · benchmarks.

Licence

GPL-3.0-or-later. The photographs in the manual and the test fixtures are the author's and are there to show and test this project, nothing else. The model weights carry their own licences — models/LICENCE.md.

S
Description
No description provided
Readme GPL-3.0
1 GiB
2026-10-07 11:27:59 +00:00
Languages
Rust 86.1%
Slint 10.3%
Python 1.1%
Shell 1%
WGSL 0.9%
Other 0.6%