Build Luminar Neo
KINDAback to the verdict
A non-destructive photo editor whose hard problems are a pipeline that shows at preview size what it will produce at full size, colour handled in a working space rather than in whatever the file happened to be, and a recipe format that still opens years later, with the AI tools as optional local models rather than the foundation.
Before step 1
Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.
- installfree
Why Builds the interface and runs its tests.
Get it nodejs.org, or nvm install 22. open ↗
Verify
node --version prints v22 or higher - installfree
Why The pixel pipeline runs in Rust, and the interface is a Tauri shell around it.
Get it rustup.rs for the toolchain, then the Tauri 2 prerequisites page for your platform. open ↗
Verify
cargo --version prints and a blank Tauri 2 application launches - have readyfree
Why A sideways portrait, a file with no profile, a 24 megapixel frame and a gradient sky are where orientation, colour and banding go wrong, and a stock sample has none of them.
Get it Your own photographs, including at least one of each of those cases, in JPEG and TIFF.
Verify
At least a thousand files with the four awkward cases identified - installfree
Why The only way to know an export's colour is right is to open it somewhere you did not write.
Get it Any mature viewer or editor with colour management on your platform.
Verify
A known profiled fixture displays identically in it and in your editor - have readyfree
Why Subject and sky masks come from it, and the editor must work identically when it is absent.
Get it A published segmentation model in a format your runtime loads, with its licence file kept beside it.
Verify
It produces a mask for a portrait and the editor still works when the file is deleted
Data model
Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.
- photos(id, path, content_hash, width, height, orientation, embedded_profile, imported_at) · a reference to the original, never a copy of it - recipes(photo_id, schema_version, ops_json, updated_at) · the sidecar, one per photograph, operations in application order - an op carries kind, params_json, mask_id and enabled, and every spatial parameter is a fraction of the image size rather than a count of pixels - masks(id, photo_id, kind, params_json, bitmap_ref) · brush and gradient masks are parametric; a model's output is a compressed bitmap - versions(photo_id, name, recipe_snapshot_json, created_at) · named states of the recipe, not copies of pixels - exports(id, photo_id, recipe_hash, output_path, format, profile, metadata_policy, at) · every export records what produced it - models(name, path, sha256, licence, loaded) · no rows means manual masks only, which is a complete mode - The original's hash is checked after every command in the suite, so the one promise this editor makes is tested rather than intended.
Environment variables
These go in a .env file the app reads at startup. The pack's .env.example is this table as a file · copy it, never commit the filled-in version.
| Variable | Needed | Example | Where the value comes from |
|---|---|---|---|
LIBRARY_DB | required | ~/.local/share/photos/library.db | References and hashes. Disposable; the sidecars beside the files are the record. |
SIDECAR_SUFFIX | required | .recipe.json | Appended to the photograph's name. Never written into the photograph. |
WORKING_SPACE | required | linear_srgb | The linear floating point space every operation runs in. |
DEFAULT_EXPORT_PROFILE | required | sRGB | Converted to and embedded on export. A file without one looks wrong everywhere. |
METADATA_POLICY_DEFAULT | required | keep_except_location | Per export. Location stripping removes coordinates and nothing else. |
MODELS_DIR | optional | ~/.local/share/photos/models | Optional local models. Empty means manual masks, which is a complete mode. |
WRITE_ORIGINALS | required | never | Present as a constant. There is no other value. |
The build, in order
Importing without touching anything
A photograph is referenced, oriented and profiled, and its bytes are never changed.
The path and the hash are what you keep; the bytes stay where they were.
Files
src-tauri/src/library.rssrc-tauri/src/sidecar.rsA sideways portrait and an untagged file are both routine.
Files
src-tauri/src/decode.rsThe promise is tested against each operation, not stated once.
Files
src-tauri/tests/untouched.rs
done when · tick each as it passeswatch out- Writing a star rating into the file's own metadata block seems harmless, and it rewrites the file, changes its hash and on some formats re-encodes the image, so the first thing a photographer notices is that every original was modified on the day it was imported. Everything you know about a photograph lives in the sidecar, and the suite proves the original is untouched after every operation.
A preview that is the export, smaller
What the screen shows at viewport size is what the file contains at full size.
Gamma-encoded eight-bit arithmetic bands, and the banding appears after the third adjustment.
Files
src-tauri/src/pipeline/mod.rssrc-tauri/src/colour.rsA radius in pixels is the reason previews lie.
Files
src-tauri/src/pipeline/ops.rsNumerically, per operation, with a stated tolerance.
Files
src-tauri/tests/preview_matches_export.rs
done when · tick each as it passeswatch out- A sharpening radius of two pixels means something different on a 1,200 pixel preview and a 6,000 pixel export, so the preview looks crisp and the file looks untouched, and the person exports four times before deciding the tool is broken. Express every spatial parameter as a fraction of the image dimensions, run one pipeline at both sizes, and compare the results numerically in the suite.
Masks, including the ones a model draws
Adjustments apply to part of an image, and a model's mask is a starting point you can edit.
A brush stroke is points and a radius, not a six megapixel bitmap.
Files
src-tauri/src/masks.rsIt is a proposal, and a portrait needs the missing hairs.
Files
src-tauri/src/models/segment.rssrc/panels/MaskPanel.tsxTested by deleting the model files, not by reading the code.
Files
src-tauri/tests/no_model.rs
done when · tick each as it passeswatch out- Treating the model's mask as the mask, uneditable, means every hair it missed and every window it included is permanent, and a subject mask that is ninety percent right is useless for a portrait. Store the model's output as a bitmap the brush can add to and subtract from, and keep the parametric masks parametric so the sidecar stays small.
History, versions and doing it to a hundred files
Any step can be undone or revisited, and a look transfers to other photographs without transferring the crop.
Undo is a change to a list, not a stack of pixel buffers.
Files
src-tauri/src/recipe.rssrc/state/history.tsGlobal adjustments copy; crop and masks do not, and the skip is reported.
Files
src-tauri/src/transfer.rsWith no partial file left behind on cancel.
Files
src-tauri/src/batch.rs
done when · tick each as it passeswatch out- Pasting a recipe including its crop and its subject mask onto a photograph framed differently produces a crop through the subject's face and a mask over the wrong region, and it does so on a hundred files at once. Transfer only the operations that do not depend on where things are in the frame, list the ones skipped, and let the person add them by hand.
Export that keeps the colour and survives the yearsproduct builder
Files leave with the right profile and the chosen metadata, and today's sidecar opens in a future version.
A file without a colour space looks wrong everywhere.
Files
src-tauri/src/export.rssrc-tauri/src/colour.rsLocation stripping removes coordinates and nothing else.
Files
src-tauri/src/metadata.rssrc-tauri/tests/metadata_policy.rsThe recipes are the years of work; the pixels are reproducible from them.
Files
src-tauri/src/sidecar_migrate.rsfixtures/sidecars/
done when · tick each as it passeswatch out- Exporting sixteen-bit linear data straight into an eight-bit JPEG without converting to an output profile produces a dark, flat file that looks nothing like the preview, and the report will be that the colours are wrong when in fact the file has no colour space at all. Convert to the output profile, embed it, and check the export in a second application against the suite's fixtures.
That is the whole plan for Luminar Neo. What it deliberately does not cover is below · check the gaps before you call it a replacement.
- Raw decoding.
- Generative sky or object replacement.
- A cloud library or mobile companion.
- Print and soft-proofing colour management.
- large template and asset libraries
- color-management edge cases
- cloud collaboration and mobile apps
- high-fidelity color, format, and export handling
- pixel-perfect professional tooling
- Lens correction from a published profile database, applied as one more operation in the recipe
- Local denoising and upscaling models under the same optional-model rule
- A watched-folder import for a shoot in progress, which the reference-only library already suits
Need the files? The project pack on the verdict page hands your agent the whole brief · more design.