AI-assisted RAW photo developer: GPT vision advisor proposes an EditRecipe, a deterministic Rust engine renders it — Lightroom-compatible XMP sidecars, 24 RAW formats + baked images, measured (not guessed) Lightroom mask geometry, local GUI/web UI, AI denoise & segmentation sidecars
246
stars
832
commits
Rust
primary language
Sep 9, 2026
updated
AI-assisted automatic development of RAW photographs.
An AI decides what to change. A deterministic Rust engine does it. In the recipe-development path, the AI never touches a pixel.
EditRecipe — bounded controls, a rationale, a
confidence — rendered by one local Rust engine behind the app, the CLI and
the web UI.analyze, auto and Analyze propose an editable
recipe from preview, EXIF and histogram, check it data-only, render it, and
may buy one bounded revision.match estimates an engine recipe from any target look,
measures how far its content diverged before trusting it, then fits
global, semantic, luminance-range and colour-range corrections behind
evidence gates.Out of scope in this release: bit-exact Adobe rendering (parity is measured), an exact X-Trans demosaic (the plane fit is approximate) and a notarised macOS build (a decision, not a gap — the bundle stays ad-hoc signed, so the first launch needs one explicit 「Open Anyway」 per machine).
The techniques below are the ones you will not find in another RAW developer. Each ends at the document that carries the rest; the last subsection lists what is designed but not yet shipped.
Zoom and pan this diagram at autoshade.dev/#pillar-analysis.
One photograph, four looks. The straight conversion of a hazy
lakeside frame and three AI develops of the same RAW at the same
--style 1.0 --strength 0.9 against the photographer's full
index — 169 Lightroom RAW+XMP edits and a 94-photo finished-look library —
where only the direction text changes. Since v1.2.3 a written
direction leads and those edits become background: mean saturation
28 % / 11 % / 30 % for moody / golden / vivid against the
conversion's 17 %, mean brightness 43 % / 58 % / 70 % against
47 %. The vivid develop's recipe crops — its cell is 9504×5702, 7 % off
the top and 3 % off the bottom — while moody, golden and the conversion are
the full 9504×6336 frame. On v1.2.2 the same three directions on the same index came back
at 23 % / 11 % / 17 % saturation and 54 % / 58 % / 55 % brightness — inside those
edits' cool, hazy register, four points of brightness apart. Judge trails,
prompts and the finished-look-only run in docs/SHOWCASE.md;
model-judge scores are automated review, not human aesthetic approval.
autoshade style-index <dir> turns every Lightroom RAW+XMP pair you finished
into an exemplar (src/style.rs); a photo retrieves its 4
most similar past shots as a soft reference.
--describe, one Qwen3-VL-2B sentence about the grade; nothing
leaves the machine.d14 + W_EMB·(1−cos(q_img,e_img)) + W_TXT·(1−cos(q_txt,e_img)) + W_DESC·(1−cos(q_txt,e_desc)),
shipped at W_EMB = 4, W_TXT = 0.5, W_DESC = 0.5 — the calibration
harness's winners on the real corpus, hubness removed before the z-score.W_LOOK = 1.0 is the unmeasured term: the look library carries no develop
settings for that objective to see, so it ships inside a stable band.style_pull (0.18 at the shipped Style 0.3, full at Style 1.0) moves the
proposal toward your historical means, unless a Direction at Adherence above
40 % leads; a --looks library guides the proposer but never becomes a
recipe target.Details: docs/TECH_STACK.md#ai-advisor-and-reverse-fit.
Zoom and pan this diagram at autoshade.dev/#pillar-reimagine-fit.
Stone viaduct. Top row: the straight conversion, a 3520×2352
gpt-image-2 target asked for a clearer afternoon, a little more
contrast, a slightly deeper blue sky, everything else unchanged
(D = 0.180, under the 0.35 threshold, so the full solve ran),
and the recovered recipe rendered on the 9504×6336 RAW, fitted at panel
Strength 100 % (the product default is 65 %): look error
0.161 → 0.023 at confidence 0.63 through a global solve whose cast
curves were projected to t = 0.485, a four-band colour mixer at the 45
ceiling, two semantic zones, two boundary-gated tiles and one field mask.
Bottom row: the same window of the frame at each source's native
resolution — the recipe carries the look, the RAW carries the detail, and
the generated frame carries neither at full size. At the default 65 % the
same pair fits to 0.047 at confidence 0.25 with the mixer capped at 18, and
v1.2.2's fit of it is where the seam fix was measured, on the top-left sky
tile: cross-boundary step 0.0278 → 0.0042, the delivered seam +3.15 → +0.92
codes on the mask-free ruler.
Cornwall lighthouse islet. The same three stages on a frame shot with the body set to a 4:3 aspect, which is how it found the two frame defects v1.2.2 fixes: sized from the sensor frame the same prompt bought a target at D = 0.136 (0.304 when the request was sized from the cropped preview), and the fit ran on a neutral develop of the full frame with the calibration composed into the solve — look error 0.137 → 0.027 at confidence 0.66, two semantic zones, four boundary-gated tiles and two field masks. This is the frame that found v1.2.3's cast defect: the v1.2.2 fit admitted three channel curves that passed every hue veto and still fanned the sky 33.1° across luminance (violet at the top, green-cyan in the bright cloud). A fourth veto now reads that fan, and the curves are shrunk toward one shared shape (t = 0.363) until it clears — the delivered sky spread is 9.6° against the target's 1.6°. Full measurements and prompts in docs/SHOWCASE.md.
match recovers an editable recipe from any finished rendition of the same
frame (src/fit.rs). A generated target is not pixel-aligned
with its source, so the solve is distribution-level, not per-pixel
regression:
Details: docs/TECH_STACK.md#ai-advisor-and-reverse-fit.
A structural reading D — gradient correlation and a five-band pyramid energy
error — measures whether the target still shows the same scene.
D ≥ 0.35) → bounded
Atmosphere mode: EV ±1, WB gain [0.80, 1.25], saturation ±30, curve
slope [0.5, 1.5], confidence capped at 0.50, no per-channel curves, and a
structure-blind ruler that stops asking replaced content to survive.Details: docs/TECH_STACK.md#reverse-fit-freedom-budget.
On divergent pairs the fit consults a DIFT correspondence field — Stable
Diffusion 2.1's UNet as a featurizer (t = 261, 768² inputs, up_blocks[1]
features, an 8-draw ensemble run one at a time to bound VRAM) — whose 48×48
grid weights a Full zone's pixel pairs and reads shifted content where it
moved.
Details: docs/TECH_STACK.md#ai-advisor-and-reverse-fit.
Details: docs/TECH_STACK.md#zone-scoped-evidence-view.
After the zones or bands, a frozen-evidence quadtree visits the strongest supported nodes first and stops at a 4×4 grid.
Details: docs/TECH_STACK.md#layered-spatial-reverse-fit-and-mask-refinement.
Before any local producer runs, a read-only 12×8×8 bilateral grid (x, y, luma) of five develop parameters is solved by conjugate gradients in f64 — λ = 1 Tikhonov toward the global fit, a Laplacian smoother, ≤ 90 iterations, weights = frozen evidence × structural support × unclipped.
Details: docs/TECH_STACK.md#local-field-analyzer.
Details: docs/TECH_STACK.md#layered-spatial-reverse-fit-and-mask-refinement.
Zoom and pan this diagram at autoshade.dev/#pillar-lightroom-math.
The tone LUT, the two-arm Texture model (A1 = 0.172443, A2 = 0.304888;
45 of 45 Lightroom anchors within ±0.02), the 290×11 radial feather LUT,
the brush law (1 − ρ^m)^n with the measured flow constant κ = 0.1284
(D1 error 874 px → 9.8 px), and the lens mask-frame transport built from
Sony's own 16 native samples (radial 41/41 vectors within 1 px; linear
openly not pixel-closed, RMS 9.748/7.025/6.336 px) were each fitted to
Lightroom output. The XMP layer is hand-rolled on purpose — no XML crate —
so a catalogue sidecar is merged into byte for byte — down to the SVD fold
between Lightroom's pixel-space radial tilt and the engine's normalised
rotation, and down to its tiff:Orientation, rewritten only when the
photographer's own turn has moved away from it — and Lightroom's Brotli-packed
brush dab streams are imported and verified (MD5 → .acr → Brotli). Two of
those fits were re-measured in
v1.2.4 against Lightroom's own coverage rather than exported luma, on a
46-export pack: the LINEAR falloff moved onto the abscissa t^1.124
(α rms 0.0293 → 0.0074), and the radial boundary was shown to be a pure
0.99876 scale of the stored ellipse — no dilation law.
reimagine composes the prompt onto an unconditional faithfulness scaffold
(because input_fidelity is silently dropped by gpt-image-2), measures the
result's structural divergence with the same D the reverse-fit uses, warns
at D ≥ 0.35, and can spend one bounded retry keeping the closer image.heal only ever copies, shifts and averages pixels that already exist, and
anything that changed pixels lives on its own card as a pixel source — never
disguised as a Lightroom adjustment.Details: docs/TECH_STACK.md#ai-advisor-and-reverse-fit.
Nothing. Everything that used to sit here has shipped: the style-retrieval
expansion (finished exports as a look library, the SigLIP 2 text tower, local
Qwen3-VL descriptions, the GUI embedding switch and the Direction-adherence
axis) landed across steps 14 and S1–S3; the eased linear-gradient falloff — the
C1 Hermite smoothstep, RMS 0.0045 against 0.017 for a straight ramp on its
first measurement — shipped in v1.2.0, and v1.2.4 moved its abscissa onto
t^1.124 against Lightroom's own 46 exports (α rms 0.0064; 0.0315 for the
plain smoothstep, 0.0598 for a straight ramp); and v1.2.4 closed the last two
entries: the colour-range producer (the reverse-fit's second range family:
one mask keyed to each ACR hue band's own mean colour, written as the
colour range mask Lightroom itself writes) and a Linux x64 command-line archive built and
published from the tag beside the Windows and macOS assets.
Twenty components, nineteen connections and three boundaries, generated from autoshade.architecture.json by scripts/architecture_diagram.py: no position in the picture is chosen by hand, and the shared checker in scripts/diagram_check.py refuses to write the file when any two labels, borders or arrows touch. Zoom and pan it at autoshade.dev/architecture.html.
src/decode.rs decodes the RAW into a preview, EXIF and a
histogram; the advisor in src/advisor/ turns those into an
EditRecipe (src/recipe.rs), and a verifier that
receives recipe, EXIF, histogram and clipping data — never pixels — checks
it.src/render.rs applies it; the image, the recipe and a
Lightroom-readable sidecar (src/xmp.rs) go to the per-user
develop store, and local masks, style retrieval, reverse-fit and the
generative tools hang off that path unchanged.EditRecipe is the only channel between the AI and the pixels: strict
json_schema, every control bounded and clamped on entry, missing fields
defaulted so older recipes stay readable, one struct behind GUI, CLI, web UI
and the XMP projection.Details: docs/ARCHITECTURE.md.
Every figure is reproduced from the section that owns it; none is an estimate.
Sources are the pinned claims in docs/TECH_STACK.md and
the tests scripts/check_docs.py re-derives.
| What | Measured | Where |
|---|---|---|
| Automated test battery | 1400 library / 24 CLI / 164 GUI / 2+2 contract tests; check_docs re-derives the pinned release claims | Tech stack |
| RAW coverage | 24 extensions, 725 camera bodies; nine-camera format zoo 9/9 at the last release gate | Supported formats |
| Lightroom Texture parity | 45 of 45 period/depth anchors within ±0.02 | Develop pipeline |
| Radial mask closure | 41 of 41 measured vectors within ≤1 px | Lens correction |
| Linear mask closure (openly not pixel-closed) | RMS 9.748 / 7.025 / 6.336 px with lens correction on, 12.449 / 9.943 / 4.979 px off | Lens correction |
| Linear falloff vs Lightroom coverage (46-export pack) | smoothstep on t^1.124: α rms 0.0064 against 0.0315 for the plain smoothstep and 0.0598 for a straight ramp; half-coverage contour +34.2/+38.2 px → +0.9/+5.0 px | Lens correction |
| Radial boundary (46-export pack) | a pure 0.99876 scale of the stored ellipse (sd 4×10⁻⁵ over masks 0.30/0.50/0.70 of frame): −1.12/−1.96/−2.79 px, no dilation law | Lens correction |
| Roundness (tilted 2:1 ellipse, feather 25/50/75) | Lightroom's R−100/0/+100 exports differ by max|Δ| = 0 DN over 26 Mpx; the engine draws one ellipse too | Masks |
| Brush geometry | D1 error 874 px → 9.8 px after pixel-centre sampling and the pixel/aspect metric | Masks |
| X-Trans demosaic (approximate) | X-S10 G/R ratio 1.5503 → 0.9476 | RAW decode |
| Reverse-fit, stone viaduct (full solve, panel Strength 100 %) | look error 0.161 → 0.023 at confidence 0.63 (a global solve with the cast curves projected to t = 0.485, the per-band mixer on Orange/Yellow/Aqua/Blue at the 45 ceiling, two semantic zones, two boundary-gated tiles and one field mask), D = 0.180; at the default 65 % the pair fits to 0.047 at confidence 0.25 with the mixer capped at 18, four tiles and two field masks, and v1.2.2's fit of it is where the seam fix was measured: sky tile 0.0278 → 0.0042 (k 0.121), delivered +3.15 → +0.92 codes | What is new §2 |
| Reverse-fit, Cornwall islet (full solve, composed calibration) | look error 0.137 → 0.027 at confidence 0.66, D = 0.136 sized from the sensor frame (0.304 from the cropped preview); the global cast projected to t = 0.363, delivered sky hue spread 9.6° (v1.2.2 shipped 33.1°) | docs/SHOWCASE.md |
| Local-field ceiling, calibration pair | global fit 0.0961 against a ceiling of 0.0700; the accepted sky zone realizes 0.134 of the distance | What is new §7 |
| AI develop, model judge | 2026-09-02 four-looks batch on the full 169 + 94 index at --style 1.0 --strength 0.9, the direction leading: moody 68 → 70 → 78 (both adopted) → 69 (discarded), verdict Accept; golden 87 → 84 (discarded) after the verifier twice sent the proposal back for the grain it never set, verdict Revise — unsaved, the figure renders the proposal; vivid 70 → 84 (adopted) → 82 (discarded), verdict Accept. The finished-look-only run (2026-09-01) and v1.2.2's full-index run are on the showcase page | AI advisor |
| Style retrieval weights | corpus harness (169 described exemplars, 156 queries): W_EMB=4, W_TXT=0.5, W_DESC=0.5, standardised variant with the text-hubness correction — MAE 0.688864 vs baseline 0.713143, +0.024280, CI [+0.005837, +0.041111] under the prose proxy; the corrected point at the old W_TXT=4 regresses with CI [−0.069654, −0.005140], which is why the weight moved; under the tag-string proxy nothing beats the text-free row; W_LOOK=1.0 is unmeasured (the harness cannot see the look library) and its scale is a real ratio against the direction terms — it ships inside a stable band, order unchanged to 2x and first moving at 4x | AI advisor |
| Memory budget | 1800 MB per photo from a 1771 MB reference probe; 4 GiB RAW admission gate | Application |
The v1.2.6 release is built by GitHub Actions from the tag: the Windows front
ends, two macOS universal (arm64 + x86_64) archives and a Linux x64
command-line archive; checksums.txt carries the SHA-256 of every asset.
| File | Size | SHA-256 |
|---|---|---|
autoshade.exe (CLI) | 20,654,080 bytes | c6e20f5d95761f6db973d8149c1bc96f7cd985394b9d91ee034df16de5c48a83 |
autoshade-gui.exe (desktop app) | 26,869,760 bytes | 90d2a50de6d834f98883a7ceb4dd8dc4fd52e322c5399ddafb2c6faaa25f2e83 |
AutoShade-Setup-1.2.6.exe (installer) | 14,397,689 bytes | 1fa235dd0d17c7be1cae282ed2a1e14c16ebebb24fdb53e3a47d9e36626c4bdd |
autoshade-1.2.6-windows-x64.zip (portable archive) | 19,159,416 bytes | 41cacddc95f06a0b89a1262210d4afa1590c441d4ef23efb49957707dd9f1972 |
AutoShade-1.2.6-macos-universal.zip (macOS app bundle) | 38,836,406 bytes | d8c939dd554c649b276cd12bbe19967172b40459a5d6b10b515eb8b4350c7f97 |
AutoShade-1.2.6-linux-x64.zip (Linux command line only) | 9,305,042 bytes | f0e8d8a1858221fb6737a07bfc0c0410536ea3d36bf2b53ff6c04010714516f6 |
AutoShade-1.2.6-macos-cli.zip (macOS command line only) | 16,811,684 bytes | fc42eb8b0413d79a104ab56b374935c75efe49d071527bbdb61ec05f53490680 |
Download from the v1.2.6 release page:
\
AutoShade-Setup-1.2.6.exe. It installs for
the current user without administrator access, adds Start Menu shortcuts, and
offers optional desktop and user PATH tasks.PATH entry, shortcuts replaced rather than duplicated, and
your develop store and downloaded model weights left exactly as they were.
A running AutoShade is closed for you first. An OLDER installer is refused
and names both versions when it refuses. Upgrading over a pre-rename install
also deletes the executables, icon and fonts that carried the old name.%LOCALAPPDATA%\autoshade. It names the
size it found for each, and keeping both is the default, so a later install
starts where you left off.AutoShade-Setup-1.2.6.exe /VERYSILENT /SUPPRESSMSGBOXES /NORESTART installs or upgrades with no window and no
prompt, and unins000.exe /VERYSILENT /SUPPRESSMSGBOXES in the install
directory uninstalls the same way. The silent uninstall keeps your weights
and develop store unless you add /DELETEDATA=1.autoshade-1.2.6-windows-x64.zip to a directory
you can keep intact and run either executable from there, beside the bundled
assets/ and python/ sidecars.Both macOS archives are universal (Apple silicon and Intel in one binary);
unpack either with Finder or ditto -x -k <zip> <dir>.
AutoShade-1.2.6-macos-universal.zip is the app: move AutoShade.app to
/Applications. The command line travels inside it
(AutoShade.app/Contents/MacOS/autoshade), so this download alone serves a
terminal user; AutoShade-1.2.6-macos-cli.zip is that binary alone.The Linux archive, AutoShade-1.2.6-linux-x64.zip, is the command line for
x86-64 Linux, built on Ubuntu 22.04 with the same payload as the macOS
command-line archive: the binary, the Python sidecars without their weights,
the assets, LICENSE and README. Unpack it anywhere and run ./autoshade;
there is no Linux desktop app.
AutoShade uses Rust edition 2024 and rustc/cargo 1.94.
cargo build --release
cargo build --release --features gui --bin autoshade-gui
The first builds the CLI, the second the desktop app, whose dependencies stay
behind the gui feature. The local AI tools also need Python packages
(weights download on first use and are not committed): BiRefNet
pip install torchvision timm einops against a torchvision matched to
torch; U²-Net fallback pip install rembg; OneFormer sky and SAM
2.1 pip install transformers torch; SCUNet denoise
(python/denoise.py) a torch build plus OpenCV, NumPy,
einops and requests — under CUDA:
pip install torch --index-url https://download.pytorch.org/whl/cu128
pip install opencv-python numpy einops requests
autoshade-gui.Ctrl+O), drag a photo in, or use Open
folder….Ctrl+Shift+E to open Export, choose a destination and format, then
export a copy. The original remains untouched.Decode a preview and metadata, then make a manual recipe render:
autoshade decode "photo.ARW" -o "preview.jpg"
autoshade apply "photo.ARW" "recipe.json" -o "developed.tif"
With the image/vision role configured, an end-to-end AI develop is:
autoshade auto "photo.ARW" --guidance "natural color; protect highlights" -o "developed.tif"
The full manual is docs/USER_MANUAL.md — the Develop panel and its Save/XMP rules, local masks, versions and variants with the Reverse-fit walkthrough, export, the CLI reference, Lightroom/XMP interoperability, the AI roles and the privacy boundary. The essentials:
apply, local match, XMP, masks, SCUNet denoise, style
indexing and the local AI masks need no API key; analyze/auto,
match --style-prompt/--ai-judge/--deep, reimagine/retouch and
automatic heal detection use the configured role, and the verifier gets
data, never pixels.OPENAI_API_KEY / AUTOSHADE_ANALYSIS_API_KEY configure
the roles; AUTOSHADE_PYTHON names the sidecar interpreter and
AUTOSHADE_WEIGHTS_DIR moves the weight cache all five share. Those
come only from the environment or the per-user settings file — a
./autoshade.local.json beside your photos may select model and provider
preferences and nothing else.![]() .cr2 · Canon EOS 40D | ![]() .cr3 · Canon EOS R6 | ![]() .nef · Nikon D700 |
![]() .arw · Sony α7 III | ![]() .orf · Olympus E-M5 | ![]() .rw2 · Panasonic DMC-GX85 |
![]() .pef · Pentax K-5 | ![]() .dng · Ricoh GR II | ![]() .raf · Fujifilm X-S10 — X-Trans, approximate |
This grid is also the nine-camera RAW zoo: one real CC0 file per format tile, fully decoded and neutral-rendered rather than copied from an embedded preview. The corpus cannot ship here, so the suite is environment-gated; the last recorded release gate was 9/9.
Camera RAW — 24 extensions, one predicate app-wide (decode::is_raw):
arw, dng, raw, raf, nef, cr2, cr3, orf, rw2, pef, srw, 3fr,
fff, iiq, mef, mos, erf, kdc, dcr, dcs, crw, nrw, mrw, ari
Decoding is rawler 0.7.2, which carries 725 camera models. No embedded
preview: 12 of the 24 formats store none. They are orf, srw, nrw, mef,
mos, kdc, dcr, dcs, erf, iiq, crw, and ari; AutoShade shows its
own neutral rendition instead and says so.
Baked rasters — 8 extensions: jpg, jpeg, png, tif, tiff, bmp,
webp, gif. ICC profiles on baked imports are converted through qcms when
present.
Degradation and refusal are explicit: an untagged 16-bit baked image is read as sRGB and flagged; monochrome and four-colour arrays are refused; unknown make, unknown model and no matching decoder are differentiated and point at the DNG route; and a parser panic is a named per-file error, so one bad file cannot end a batch.
The canonical page is Tech stack and algorithms — equations, provenance, measured results, honesty markers and source paths behind each summary below. Numbers already in Measured numbers are not repeated.
src/decode.rs uses rawler for RAW decode, 24 formats, with 725 bodies
in the release database; orient_f32 applies the composed orientation — the
RAW's EXIF state plus the photographer's own quarter turns — at the head of
the chain, and an imported Lightroom sidecar's tiff:Orientation chooses
those turns, so a rotation made in Lightroom survives the import.src/render.rs is a deterministic f32 pipeline: linear-light vignette and
dehaze, a monotone Fritsch–Carlson tone LUT with tone_knot_weights and
Highlights inside it, then RGB curves, HSL, colour grade, clarity/Texture,
saturation, NR, sharpening and local edits.A1=0.172443,
A2=0.304888) with a calibrated hyperbolic depth law.src/recipe.rs, src/render.rs and src/xmp.rs implement radial, linear,
brush, bitmap, luminance-range and colour-range masks with ordered
Add/Subtract/Intersect composition.alpha(rho, feather) LUT; brush dabs
use (1-rho^m)^n and the measured kappa=0.1284 flow law over pixel-centre
sampling and the pixel/aspect metric, and MaskBrushTable import validates
MD5→.acr→Brotli.src/segment.rs and python/segment.py run commit-pinned BiRefNet subject
selection with a named U²-Net fallback, OneFormer ADE20K sky selection
through the 150-class checked-in table, and SAM 2.1 objects from ordered
gesture points over the gp1 IPC.src/lensmeta.rs, src/lcp.rs and src/render.rs combine Sony 0x7037's 16
native (i+1)/16 samples, a 2048-node/64-knot mask solve, and guarded
Newton inversion for rectilinear .lcp profiles while refusing fisheye-only
entries.m_lr^-1 ∘ T_engine transport; linear H2 keeps
corrected-frame handles but is openly not pixel-closed, and brushes remain
in the raw frame.src/xmp.rs uses scoped, typed XML traversal, including
nested Look, and conservatively merges owned edits while preserving
unmodeled fields; Save writes the develop store and beside-RAW export is
explicit.LR_MASK_FRAME_SCALE=1.0, LocalExposure2012=EV/4, local Hue is
degrees/180, the other measured local family is /100, global Sharpness
is 1:1, and polarity comes from MaskInverted rather than Flipped.src/advisor/ validates AI proposals into bounded recipes, keeps Responses
at store:false, gives the verifier data rather than pixels, and adopts a
guided revision only when it does not lower the score.src/style.rs retrieves z-scored RAW+XMP exemplars with four optional
cosine terms (image, direction text, description text, and the separate
finished-photo look library); the shipped weights are W_EMB = 4,
W_TXT = 0.5 and W_DESC = 0.5 from the calibration harness, plus
W_LOOK = 1.0, the one term that harness cannot score.src/fit.rs runs the luminance-CDF, exposure, basis, tone, saturation and
cast inverse stages behind a >45°/≥5% foreign-hue veto, consulting the DIFT
(SD 2.1) field of src/correspond.rs + python/correspond.py on divergent
pairs; src/generative.rs negotiates gpt-image-2 sizes and src/retouch.rs
is the deterministic heal.image, qcms, rayon, clap, serde, ureq, eframe/egui and
tiny_http back the shared library, CLI, desktop GUI and loopback web UI.sidecar_wrote contract; a 1771 MB reference probe sets
the 1800 MB per-photo budget, and a 4 GiB RAW gate bounds admission.build workflow covers default and GUI
feature sets on Ubuntu and macOS; model weights are not stored here. The
current battery is 1400 library (1386 pass + 14 #[ignore]d forensic probes) / 24 CLI / 164 GUI / 2+2 contract tests, and
scripts/check_docs.py re-derives the pinned
release claims.scripts/mps_probe.py
(device, forward time, peak memory, whether deform_conv2d falls back to
the CPU — the numbers are in the release notes); Linux ships a
command-line archive and has no desktop app.LensProfile
frame facts is refused by older binaries rather than misread, and six
families of existing content may rerender — both in
docs/ARCHITECTURE.md, with the ledger and standing
rulings in docs/ROADMAP.md.AutoShade is MIT-licensed — see LICENSE.
The nine files behind the format grid come from raw.pixls.us under CC0 1.0 Public Domain; their recorded SHA-256 values were verified against that index before use.
| Format | Camera | MP | Sample |
|---|---|---|---|
| CR2 | Canon EOS 40D | 10.08 | RAW (3:2) |
| CR3 | Canon EOS R6 | 19.96 | 3:2 |
| NEF | Nikon D700 | 12.2 | 14bit compressed (Lossless) (3:2) |
| RAF | Fujifilm X-S10 | 26.7 | 14bit compressed (3:2) |
| ORF | Olympus E-M5 | 16.11 | 16bit (4:3) |
| RW2 | Panasonic DMC-GX85 | 15.9 | 4:3 |
| PEF | Pentax K-5 | 16.39 | 14bit (3:2) |
| DNG | Ricoh GR II | 16.27 | 12bit (3:2) |
| ARW | Sony ILCE-7M3 | 24.34 | 14bit compressed (3:2) |
The showcase photographs are the author's own Sony α7R IVA frames — © 2026 skymanbp, all rights reserved. They document AutoShade's output, are not covered by the MIT license, omit EXIF and carry no watermark.
The GUI bundles subset Noto faces under the SIL Open Font License (texts under
assets/fonts/); model weights download separately, remain their authors'
property, and none are redistributed here.
| Model | Purpose | License |
|---|---|---|
| SCUNet | AI denoise | Apache-2.0 |
| BiRefNet | Subject segmentation | MIT |
| U²-Net | Subject fallback | Apache-2.0 |
| OneFormer ADE20K | Sky segmentation | MIT |
| SAM 2.1 | Point-prompted object masks | Apache-2.0 |
| SigLIP 2 | Optional style embeddings | Apache-2.0 |
| Qwen3-VL-2B-Instruct | Optional local look descriptions | Apache-2.0 |
The project acknowledges the rawler, image, qcms, rayon, clap, serde, ureq, egui/eframe, tiny_http and local-model communities whose work makes these pipelines possible.
832 commits
Rust
91.1%
Python
5.1%
HTML
2.4%
AI-assisted RAW photo developer: GPT vision advisor proposes an EditRecipe, a deterministic Rust engine renders it — Lightroom-compatible XMP sidecars, 24 RAW formats + baked images, measured (not guessed) Lightroom mask geometry, local GUI/web UI, AI denoise & segmentation sidecars
246
stars
832
commits
Rust
primary language
Sep 9, 2026
updated
AI-assisted automatic development of RAW photographs.
An AI decides what to change. A deterministic Rust engine does it. In the recipe-development path, the AI never touches a pixel.
EditRecipe — bounded controls, a rationale, a
confidence — rendered by one local Rust engine behind the app, the CLI and
the web UI.analyze, auto and Analyze propose an editable
recipe from preview, EXIF and histogram, check it data-only, render it, and
may buy one bounded revision.match estimates an engine recipe from any target look,
measures how far its content diverged before trusting it, then fits
global, semantic, luminance-range and colour-range corrections behind
evidence gates.Out of scope in this release: bit-exact Adobe rendering (parity is measured), an exact X-Trans demosaic (the plane fit is approximate) and a notarised macOS build (a decision, not a gap — the bundle stays ad-hoc signed, so the first launch needs one explicit 「Open Anyway」 per machine).
The techniques below are the ones you will not find in another RAW developer. Each ends at the document that carries the rest; the last subsection lists what is designed but not yet shipped.
Zoom and pan this diagram at autoshade.dev/#pillar-analysis.
One photograph, four looks. The straight conversion of a hazy
lakeside frame and three AI develops of the same RAW at the same
--style 1.0 --strength 0.9 against the photographer's full
index — 169 Lightroom RAW+XMP edits and a 94-photo finished-look library —
where only the direction text changes. Since v1.2.3 a written
direction leads and those edits become background: mean saturation
28 % / 11 % / 30 % for moody / golden / vivid against the
conversion's 17 %, mean brightness 43 % / 58 % / 70 % against
47 %. The vivid develop's recipe crops — its cell is 9504×5702, 7 % off
the top and 3 % off the bottom — while moody, golden and the conversion are
the full 9504×6336 frame. On v1.2.2 the same three directions on the same index came back
at 23 % / 11 % / 17 % saturation and 54 % / 58 % / 55 % brightness — inside those
edits' cool, hazy register, four points of brightness apart. Judge trails,
prompts and the finished-look-only run in docs/SHOWCASE.md;
model-judge scores are automated review, not human aesthetic approval.
autoshade style-index <dir> turns every Lightroom RAW+XMP pair you finished
into an exemplar (src/style.rs); a photo retrieves its 4
most similar past shots as a soft reference.
--describe, one Qwen3-VL-2B sentence about the grade; nothing
leaves the machine.d14 + W_EMB·(1−cos(q_img,e_img)) + W_TXT·(1−cos(q_txt,e_img)) + W_DESC·(1−cos(q_txt,e_desc)),
shipped at W_EMB = 4, W_TXT = 0.5, W_DESC = 0.5 — the calibration
harness's winners on the real corpus, hubness removed before the z-score.W_LOOK = 1.0 is the unmeasured term: the look library carries no develop
settings for that objective to see, so it ships inside a stable band.style_pull (0.18 at the shipped Style 0.3, full at Style 1.0) moves the
proposal toward your historical means, unless a Direction at Adherence above
40 % leads; a --looks library guides the proposer but never becomes a
recipe target.Details: docs/TECH_STACK.md#ai-advisor-and-reverse-fit.
Zoom and pan this diagram at autoshade.dev/#pillar-reimagine-fit.
Stone viaduct. Top row: the straight conversion, a 3520×2352
gpt-image-2 target asked for a clearer afternoon, a little more
contrast, a slightly deeper blue sky, everything else unchanged
(D = 0.180, under the 0.35 threshold, so the full solve ran),
and the recovered recipe rendered on the 9504×6336 RAW, fitted at panel
Strength 100 % (the product default is 65 %): look error
0.161 → 0.023 at confidence 0.63 through a global solve whose cast
curves were projected to t = 0.485, a four-band colour mixer at the 45
ceiling, two semantic zones, two boundary-gated tiles and one field mask.
Bottom row: the same window of the frame at each source's native
resolution — the recipe carries the look, the RAW carries the detail, and
the generated frame carries neither at full size. At the default 65 % the
same pair fits to 0.047 at confidence 0.25 with the mixer capped at 18, and
v1.2.2's fit of it is where the seam fix was measured, on the top-left sky
tile: cross-boundary step 0.0278 → 0.0042, the delivered seam +3.15 → +0.92
codes on the mask-free ruler.
Cornwall lighthouse islet. The same three stages on a frame shot with the body set to a 4:3 aspect, which is how it found the two frame defects v1.2.2 fixes: sized from the sensor frame the same prompt bought a target at D = 0.136 (0.304 when the request was sized from the cropped preview), and the fit ran on a neutral develop of the full frame with the calibration composed into the solve — look error 0.137 → 0.027 at confidence 0.66, two semantic zones, four boundary-gated tiles and two field masks. This is the frame that found v1.2.3's cast defect: the v1.2.2 fit admitted three channel curves that passed every hue veto and still fanned the sky 33.1° across luminance (violet at the top, green-cyan in the bright cloud). A fourth veto now reads that fan, and the curves are shrunk toward one shared shape (t = 0.363) until it clears — the delivered sky spread is 9.6° against the target's 1.6°. Full measurements and prompts in docs/SHOWCASE.md.
match recovers an editable recipe from any finished rendition of the same
frame (src/fit.rs). A generated target is not pixel-aligned
with its source, so the solve is distribution-level, not per-pixel
regression:
Details: docs/TECH_STACK.md#ai-advisor-and-reverse-fit.
A structural reading D — gradient correlation and a five-band pyramid energy
error — measures whether the target still shows the same scene.
D ≥ 0.35) → bounded
Atmosphere mode: EV ±1, WB gain [0.80, 1.25], saturation ±30, curve
slope [0.5, 1.5], confidence capped at 0.50, no per-channel curves, and a
structure-blind ruler that stops asking replaced content to survive.Details: docs/TECH_STACK.md#reverse-fit-freedom-budget.
On divergent pairs the fit consults a DIFT correspondence field — Stable
Diffusion 2.1's UNet as a featurizer (t = 261, 768² inputs, up_blocks[1]
features, an 8-draw ensemble run one at a time to bound VRAM) — whose 48×48
grid weights a Full zone's pixel pairs and reads shifted content where it
moved.
Details: docs/TECH_STACK.md#ai-advisor-and-reverse-fit.
Details: docs/TECH_STACK.md#zone-scoped-evidence-view.
After the zones or bands, a frozen-evidence quadtree visits the strongest supported nodes first and stops at a 4×4 grid.
Details: docs/TECH_STACK.md#layered-spatial-reverse-fit-and-mask-refinement.
Before any local producer runs, a read-only 12×8×8 bilateral grid (x, y, luma) of five develop parameters is solved by conjugate gradients in f64 — λ = 1 Tikhonov toward the global fit, a Laplacian smoother, ≤ 90 iterations, weights = frozen evidence × structural support × unclipped.
Details: docs/TECH_STACK.md#local-field-analyzer.
Details: docs/TECH_STACK.md#layered-spatial-reverse-fit-and-mask-refinement.
Zoom and pan this diagram at autoshade.dev/#pillar-lightroom-math.
The tone LUT, the two-arm Texture model (A1 = 0.172443, A2 = 0.304888;
45 of 45 Lightroom anchors within ±0.02), the 290×11 radial feather LUT,
the brush law (1 − ρ^m)^n with the measured flow constant κ = 0.1284
(D1 error 874 px → 9.8 px), and the lens mask-frame transport built from
Sony's own 16 native samples (radial 41/41 vectors within 1 px; linear
openly not pixel-closed, RMS 9.748/7.025/6.336 px) were each fitted to
Lightroom output. The XMP layer is hand-rolled on purpose — no XML crate —
so a catalogue sidecar is merged into byte for byte — down to the SVD fold
between Lightroom's pixel-space radial tilt and the engine's normalised
rotation, and down to its tiff:Orientation, rewritten only when the
photographer's own turn has moved away from it — and Lightroom's Brotli-packed
brush dab streams are imported and verified (MD5 → .acr → Brotli). Two of
those fits were re-measured in
v1.2.4 against Lightroom's own coverage rather than exported luma, on a
46-export pack: the LINEAR falloff moved onto the abscissa t^1.124
(α rms 0.0293 → 0.0074), and the radial boundary was shown to be a pure
0.99876 scale of the stored ellipse — no dilation law.
reimagine composes the prompt onto an unconditional faithfulness scaffold
(because input_fidelity is silently dropped by gpt-image-2), measures the
result's structural divergence with the same D the reverse-fit uses, warns
at D ≥ 0.35, and can spend one bounded retry keeping the closer image.heal only ever copies, shifts and averages pixels that already exist, and
anything that changed pixels lives on its own card as a pixel source — never
disguised as a Lightroom adjustment.Details: docs/TECH_STACK.md#ai-advisor-and-reverse-fit.
Nothing. Everything that used to sit here has shipped: the style-retrieval
expansion (finished exports as a look library, the SigLIP 2 text tower, local
Qwen3-VL descriptions, the GUI embedding switch and the Direction-adherence
axis) landed across steps 14 and S1–S3; the eased linear-gradient falloff — the
C1 Hermite smoothstep, RMS 0.0045 against 0.017 for a straight ramp on its
first measurement — shipped in v1.2.0, and v1.2.4 moved its abscissa onto
t^1.124 against Lightroom's own 46 exports (α rms 0.0064; 0.0315 for the
plain smoothstep, 0.0598 for a straight ramp); and v1.2.4 closed the last two
entries: the colour-range producer (the reverse-fit's second range family:
one mask keyed to each ACR hue band's own mean colour, written as the
colour range mask Lightroom itself writes) and a Linux x64 command-line archive built and
published from the tag beside the Windows and macOS assets.
Twenty components, nineteen connections and three boundaries, generated from autoshade.architecture.json by scripts/architecture_diagram.py: no position in the picture is chosen by hand, and the shared checker in scripts/diagram_check.py refuses to write the file when any two labels, borders or arrows touch. Zoom and pan it at autoshade.dev/architecture.html.
src/decode.rs decodes the RAW into a preview, EXIF and a
histogram; the advisor in src/advisor/ turns those into an
EditRecipe (src/recipe.rs), and a verifier that
receives recipe, EXIF, histogram and clipping data — never pixels — checks
it.src/render.rs applies it; the image, the recipe and a
Lightroom-readable sidecar (src/xmp.rs) go to the per-user
develop store, and local masks, style retrieval, reverse-fit and the
generative tools hang off that path unchanged.EditRecipe is the only channel between the AI and the pixels: strict
json_schema, every control bounded and clamped on entry, missing fields
defaulted so older recipes stay readable, one struct behind GUI, CLI, web UI
and the XMP projection.Details: docs/ARCHITECTURE.md.
Every figure is reproduced from the section that owns it; none is an estimate.
Sources are the pinned claims in docs/TECH_STACK.md and
the tests scripts/check_docs.py re-derives.
| What | Measured | Where |
|---|---|---|
| Automated test battery | 1400 library / 24 CLI / 164 GUI / 2+2 contract tests; check_docs re-derives the pinned release claims | Tech stack |
| RAW coverage | 24 extensions, 725 camera bodies; nine-camera format zoo 9/9 at the last release gate | Supported formats |
| Lightroom Texture parity | 45 of 45 period/depth anchors within ±0.02 | Develop pipeline |
| Radial mask closure | 41 of 41 measured vectors within ≤1 px | Lens correction |
| Linear mask closure (openly not pixel-closed) | RMS 9.748 / 7.025 / 6.336 px with lens correction on, 12.449 / 9.943 / 4.979 px off | Lens correction |
| Linear falloff vs Lightroom coverage (46-export pack) | smoothstep on t^1.124: α rms 0.0064 against 0.0315 for the plain smoothstep and 0.0598 for a straight ramp; half-coverage contour +34.2/+38.2 px → +0.9/+5.0 px | Lens correction |
| Radial boundary (46-export pack) | a pure 0.99876 scale of the stored ellipse (sd 4×10⁻⁵ over masks 0.30/0.50/0.70 of frame): −1.12/−1.96/−2.79 px, no dilation law | Lens correction |
| Roundness (tilted 2:1 ellipse, feather 25/50/75) | Lightroom's R−100/0/+100 exports differ by max|Δ| = 0 DN over 26 Mpx; the engine draws one ellipse too | Masks |
| Brush geometry | D1 error 874 px → 9.8 px after pixel-centre sampling and the pixel/aspect metric | Masks |
| X-Trans demosaic (approximate) | X-S10 G/R ratio 1.5503 → 0.9476 | RAW decode |
| Reverse-fit, stone viaduct (full solve, panel Strength 100 %) | look error 0.161 → 0.023 at confidence 0.63 (a global solve with the cast curves projected to t = 0.485, the per-band mixer on Orange/Yellow/Aqua/Blue at the 45 ceiling, two semantic zones, two boundary-gated tiles and one field mask), D = 0.180; at the default 65 % the pair fits to 0.047 at confidence 0.25 with the mixer capped at 18, four tiles and two field masks, and v1.2.2's fit of it is where the seam fix was measured: sky tile 0.0278 → 0.0042 (k 0.121), delivered +3.15 → +0.92 codes | What is new §2 |
| Reverse-fit, Cornwall islet (full solve, composed calibration) | look error 0.137 → 0.027 at confidence 0.66, D = 0.136 sized from the sensor frame (0.304 from the cropped preview); the global cast projected to t = 0.363, delivered sky hue spread 9.6° (v1.2.2 shipped 33.1°) | docs/SHOWCASE.md |
| Local-field ceiling, calibration pair | global fit 0.0961 against a ceiling of 0.0700; the accepted sky zone realizes 0.134 of the distance | What is new §7 |
| AI develop, model judge | 2026-09-02 four-looks batch on the full 169 + 94 index at --style 1.0 --strength 0.9, the direction leading: moody 68 → 70 → 78 (both adopted) → 69 (discarded), verdict Accept; golden 87 → 84 (discarded) after the verifier twice sent the proposal back for the grain it never set, verdict Revise — unsaved, the figure renders the proposal; vivid 70 → 84 (adopted) → 82 (discarded), verdict Accept. The finished-look-only run (2026-09-01) and v1.2.2's full-index run are on the showcase page | AI advisor |
| Style retrieval weights | corpus harness (169 described exemplars, 156 queries): W_EMB=4, W_TXT=0.5, W_DESC=0.5, standardised variant with the text-hubness correction — MAE 0.688864 vs baseline 0.713143, +0.024280, CI [+0.005837, +0.041111] under the prose proxy; the corrected point at the old W_TXT=4 regresses with CI [−0.069654, −0.005140], which is why the weight moved; under the tag-string proxy nothing beats the text-free row; W_LOOK=1.0 is unmeasured (the harness cannot see the look library) and its scale is a real ratio against the direction terms — it ships inside a stable band, order unchanged to 2x and first moving at 4x | AI advisor |
| Memory budget | 1800 MB per photo from a 1771 MB reference probe; 4 GiB RAW admission gate | Application |
The v1.2.6 release is built by GitHub Actions from the tag: the Windows front
ends, two macOS universal (arm64 + x86_64) archives and a Linux x64
command-line archive; checksums.txt carries the SHA-256 of every asset.
| File | Size | SHA-256 |
|---|---|---|
autoshade.exe (CLI) | 20,654,080 bytes | c6e20f5d95761f6db973d8149c1bc96f7cd985394b9d91ee034df16de5c48a83 |
autoshade-gui.exe (desktop app) | 26,869,760 bytes | 90d2a50de6d834f98883a7ceb4dd8dc4fd52e322c5399ddafb2c6faaa25f2e83 |
AutoShade-Setup-1.2.6.exe (installer) | 14,397,689 bytes | 1fa235dd0d17c7be1cae282ed2a1e14c16ebebb24fdb53e3a47d9e36626c4bdd |
autoshade-1.2.6-windows-x64.zip (portable archive) | 19,159,416 bytes | 41cacddc95f06a0b89a1262210d4afa1590c441d4ef23efb49957707dd9f1972 |
AutoShade-1.2.6-macos-universal.zip (macOS app bundle) | 38,836,406 bytes | d8c939dd554c649b276cd12bbe19967172b40459a5d6b10b515eb8b4350c7f97 |
AutoShade-1.2.6-linux-x64.zip (Linux command line only) | 9,305,042 bytes | f0e8d8a1858221fb6737a07bfc0c0410536ea3d36bf2b53ff6c04010714516f6 |
AutoShade-1.2.6-macos-cli.zip (macOS command line only) | 16,811,684 bytes | fc42eb8b0413d79a104ab56b374935c75efe49d071527bbdb61ec05f53490680 |
Download from the v1.2.6 release page:
\
AutoShade-Setup-1.2.6.exe. It installs for
the current user without administrator access, adds Start Menu shortcuts, and
offers optional desktop and user PATH tasks.PATH entry, shortcuts replaced rather than duplicated, and
your develop store and downloaded model weights left exactly as they were.
A running AutoShade is closed for you first. An OLDER installer is refused
and names both versions when it refuses. Upgrading over a pre-rename install
also deletes the executables, icon and fonts that carried the old name.%LOCALAPPDATA%\autoshade. It names the
size it found for each, and keeping both is the default, so a later install
starts where you left off.AutoShade-Setup-1.2.6.exe /VERYSILENT /SUPPRESSMSGBOXES /NORESTART installs or upgrades with no window and no
prompt, and unins000.exe /VERYSILENT /SUPPRESSMSGBOXES in the install
directory uninstalls the same way. The silent uninstall keeps your weights
and develop store unless you add /DELETEDATA=1.autoshade-1.2.6-windows-x64.zip to a directory
you can keep intact and run either executable from there, beside the bundled
assets/ and python/ sidecars.Both macOS archives are universal (Apple silicon and Intel in one binary);
unpack either with Finder or ditto -x -k <zip> <dir>.
AutoShade-1.2.6-macos-universal.zip is the app: move AutoShade.app to
/Applications. The command line travels inside it
(AutoShade.app/Contents/MacOS/autoshade), so this download alone serves a
terminal user; AutoShade-1.2.6-macos-cli.zip is that binary alone.The Linux archive, AutoShade-1.2.6-linux-x64.zip, is the command line for
x86-64 Linux, built on Ubuntu 22.04 with the same payload as the macOS
command-line archive: the binary, the Python sidecars without their weights,
the assets, LICENSE and README. Unpack it anywhere and run ./autoshade;
there is no Linux desktop app.
AutoShade uses Rust edition 2024 and rustc/cargo 1.94.
cargo build --release
cargo build --release --features gui --bin autoshade-gui
The first builds the CLI, the second the desktop app, whose dependencies stay
behind the gui feature. The local AI tools also need Python packages
(weights download on first use and are not committed): BiRefNet
pip install torchvision timm einops against a torchvision matched to
torch; U²-Net fallback pip install rembg; OneFormer sky and SAM
2.1 pip install transformers torch; SCUNet denoise
(python/denoise.py) a torch build plus OpenCV, NumPy,
einops and requests — under CUDA:
pip install torch --index-url https://download.pytorch.org/whl/cu128
pip install opencv-python numpy einops requests
autoshade-gui.Ctrl+O), drag a photo in, or use Open
folder….Ctrl+Shift+E to open Export, choose a destination and format, then
export a copy. The original remains untouched.Decode a preview and metadata, then make a manual recipe render:
autoshade decode "photo.ARW" -o "preview.jpg"
autoshade apply "photo.ARW" "recipe.json" -o "developed.tif"
With the image/vision role configured, an end-to-end AI develop is:
autoshade auto "photo.ARW" --guidance "natural color; protect highlights" -o "developed.tif"
The full manual is docs/USER_MANUAL.md — the Develop panel and its Save/XMP rules, local masks, versions and variants with the Reverse-fit walkthrough, export, the CLI reference, Lightroom/XMP interoperability, the AI roles and the privacy boundary. The essentials:
apply, local match, XMP, masks, SCUNet denoise, style
indexing and the local AI masks need no API key; analyze/auto,
match --style-prompt/--ai-judge/--deep, reimagine/retouch and
automatic heal detection use the configured role, and the verifier gets
data, never pixels.OPENAI_API_KEY / AUTOSHADE_ANALYSIS_API_KEY configure
the roles; AUTOSHADE_PYTHON names the sidecar interpreter and
AUTOSHADE_WEIGHTS_DIR moves the weight cache all five share. Those
come only from the environment or the per-user settings file — a
./autoshade.local.json beside your photos may select model and provider
preferences and nothing else.![]() .cr2 · Canon EOS 40D | ![]() .cr3 · Canon EOS R6 | ![]() .nef · Nikon D700 |
![]() .arw · Sony α7 III | ![]() .orf · Olympus E-M5 | ![]() .rw2 · Panasonic DMC-GX85 |
![]() .pef · Pentax K-5 | ![]() .dng · Ricoh GR II | ![]() .raf · Fujifilm X-S10 — X-Trans, approximate |
This grid is also the nine-camera RAW zoo: one real CC0 file per format tile, fully decoded and neutral-rendered rather than copied from an embedded preview. The corpus cannot ship here, so the suite is environment-gated; the last recorded release gate was 9/9.
Camera RAW — 24 extensions, one predicate app-wide (decode::is_raw):
arw, dng, raw, raf, nef, cr2, cr3, orf, rw2, pef, srw, 3fr,
fff, iiq, mef, mos, erf, kdc, dcr, dcs, crw, nrw, mrw, ari
Decoding is rawler 0.7.2, which carries 725 camera models. No embedded
preview: 12 of the 24 formats store none. They are orf, srw, nrw, mef,
mos, kdc, dcr, dcs, erf, iiq, crw, and ari; AutoShade shows its
own neutral rendition instead and says so.
Baked rasters — 8 extensions: jpg, jpeg, png, tif, tiff, bmp,
webp, gif. ICC profiles on baked imports are converted through qcms when
present.
Degradation and refusal are explicit: an untagged 16-bit baked image is read as sRGB and flagged; monochrome and four-colour arrays are refused; unknown make, unknown model and no matching decoder are differentiated and point at the DNG route; and a parser panic is a named per-file error, so one bad file cannot end a batch.
The canonical page is Tech stack and algorithms — equations, provenance, measured results, honesty markers and source paths behind each summary below. Numbers already in Measured numbers are not repeated.
src/decode.rs uses rawler for RAW decode, 24 formats, with 725 bodies
in the release database; orient_f32 applies the composed orientation — the
RAW's EXIF state plus the photographer's own quarter turns — at the head of
the chain, and an imported Lightroom sidecar's tiff:Orientation chooses
those turns, so a rotation made in Lightroom survives the import.src/render.rs is a deterministic f32 pipeline: linear-light vignette and
dehaze, a monotone Fritsch–Carlson tone LUT with tone_knot_weights and
Highlights inside it, then RGB curves, HSL, colour grade, clarity/Texture,
saturation, NR, sharpening and local edits.A1=0.172443,
A2=0.304888) with a calibrated hyperbolic depth law.src/recipe.rs, src/render.rs and src/xmp.rs implement radial, linear,
brush, bitmap, luminance-range and colour-range masks with ordered
Add/Subtract/Intersect composition.alpha(rho, feather) LUT; brush dabs
use (1-rho^m)^n and the measured kappa=0.1284 flow law over pixel-centre
sampling and the pixel/aspect metric, and MaskBrushTable import validates
MD5→.acr→Brotli.src/segment.rs and python/segment.py run commit-pinned BiRefNet subject
selection with a named U²-Net fallback, OneFormer ADE20K sky selection
through the 150-class checked-in table, and SAM 2.1 objects from ordered
gesture points over the gp1 IPC.src/lensmeta.rs, src/lcp.rs and src/render.rs combine Sony 0x7037's 16
native (i+1)/16 samples, a 2048-node/64-knot mask solve, and guarded
Newton inversion for rectilinear .lcp profiles while refusing fisheye-only
entries.m_lr^-1 ∘ T_engine transport; linear H2 keeps
corrected-frame handles but is openly not pixel-closed, and brushes remain
in the raw frame.src/xmp.rs uses scoped, typed XML traversal, including
nested Look, and conservatively merges owned edits while preserving
unmodeled fields; Save writes the develop store and beside-RAW export is
explicit.LR_MASK_FRAME_SCALE=1.0, LocalExposure2012=EV/4, local Hue is
degrees/180, the other measured local family is /100, global Sharpness
is 1:1, and polarity comes from MaskInverted rather than Flipped.src/advisor/ validates AI proposals into bounded recipes, keeps Responses
at store:false, gives the verifier data rather than pixels, and adopts a
guided revision only when it does not lower the score.src/style.rs retrieves z-scored RAW+XMP exemplars with four optional
cosine terms (image, direction text, description text, and the separate
finished-photo look library); the shipped weights are W_EMB = 4,
W_TXT = 0.5 and W_DESC = 0.5 from the calibration harness, plus
W_LOOK = 1.0, the one term that harness cannot score.src/fit.rs runs the luminance-CDF, exposure, basis, tone, saturation and
cast inverse stages behind a >45°/≥5% foreign-hue veto, consulting the DIFT
(SD 2.1) field of src/correspond.rs + python/correspond.py on divergent
pairs; src/generative.rs negotiates gpt-image-2 sizes and src/retouch.rs
is the deterministic heal.image, qcms, rayon, clap, serde, ureq, eframe/egui and
tiny_http back the shared library, CLI, desktop GUI and loopback web UI.sidecar_wrote contract; a 1771 MB reference probe sets
the 1800 MB per-photo budget, and a 4 GiB RAW gate bounds admission.build workflow covers default and GUI
feature sets on Ubuntu and macOS; model weights are not stored here. The
current battery is 1400 library (1386 pass + 14 #[ignore]d forensic probes) / 24 CLI / 164 GUI / 2+2 contract tests, and
scripts/check_docs.py re-derives the pinned
release claims.scripts/mps_probe.py
(device, forward time, peak memory, whether deform_conv2d falls back to
the CPU — the numbers are in the release notes); Linux ships a
command-line archive and has no desktop app.LensProfile
frame facts is refused by older binaries rather than misread, and six
families of existing content may rerender — both in
docs/ARCHITECTURE.md, with the ledger and standing
rulings in docs/ROADMAP.md.AutoShade is MIT-licensed — see LICENSE.
The nine files behind the format grid come from raw.pixls.us under CC0 1.0 Public Domain; their recorded SHA-256 values were verified against that index before use.
| Format | Camera | MP | Sample |
|---|---|---|---|
| CR2 | Canon EOS 40D | 10.08 | RAW (3:2) |
| CR3 | Canon EOS R6 | 19.96 | 3:2 |
| NEF | Nikon D700 | 12.2 | 14bit compressed (Lossless) (3:2) |
| RAF | Fujifilm X-S10 | 26.7 | 14bit compressed (3:2) |
| ORF | Olympus E-M5 | 16.11 | 16bit (4:3) |
| RW2 | Panasonic DMC-GX85 | 15.9 | 4:3 |
| PEF | Pentax K-5 | 16.39 | 14bit (3:2) |
| DNG | Ricoh GR II | 16.27 | 12bit (3:2) |
| ARW | Sony ILCE-7M3 | 24.34 | 14bit compressed (3:2) |
The showcase photographs are the author's own Sony α7R IVA frames — © 2026 skymanbp, all rights reserved. They document AutoShade's output, are not covered by the MIT license, omit EXIF and carry no watermark.
The GUI bundles subset Noto faces under the SIL Open Font License (texts under
assets/fonts/); model weights download separately, remain their authors'
property, and none are redistributed here.
| Model | Purpose | License |
|---|---|---|
| SCUNet | AI denoise | Apache-2.0 |
| BiRefNet | Subject segmentation | MIT |
| U²-Net | Subject fallback | Apache-2.0 |
| OneFormer ADE20K | Sky segmentation | MIT |
| SAM 2.1 | Point-prompted object masks | Apache-2.0 |
| SigLIP 2 | Optional style embeddings | Apache-2.0 |
| Qwen3-VL-2B-Instruct | Optional local look descriptions | Apache-2.0 |
The project acknowledges the rawler, image, qcms, rayon, clap, serde, ureq, egui/eframe, tiny_http and local-model communities whose work makes these pipelines possible.
832 commits
Rust
91.1%
Python
5.1%
HTML
2.4%