Soft Proofing's Profile menu takes a printer / paper ICC profile (Other…;
`view.softProof {profile, intent, paperInk}`), RGB or CMYK, matrix or LUT.
The CMS (moxcms) builds a 33^3 proof table over the working space: forward with
the chosen intent (Perceptual / Relative), back relative colorimetric; the
destination gamut warning comes from the colorimetric round trip. Simulate
Paper & Ink renders absolute colorimetric: the profile's media white replaces
screen white and the ink black is unscaled. The pipeline gets the table by id
(`Proof::profile`, `register_proof_profile`), so Proof stays Copy and render
keys include it. Proof copies are named after the profile.
Sources: ICC.1:2022 (rendering intents, media white point, absolute
colorimetric); own design for the proof table.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
New `inpaint` crate (L1): multi-scale patch-based completion with a PatchMatch
nearest-neighbour field, seeded by a membrane fill. Deterministic, no panics on
hostile sizes, cancellable. Ported from PhotoCraft (MIT OR Apache-2.0).
Remove tool: a fourth spot mode, `fill` ("Content-Aware"), rebuilds the spot
from patches of its surroundings instead of one source; coarse-to-fine, so the
structure is chosen at a scale set by the spot size and preview and export agree
(tested). spot.add leaves its source unset.
Sources: C. Barnes et al., PatchMatch (SIGGRAPH 2009); Y. Wexler, E. Shechtman,
M. Irani, Space-Time Completion of Video (TPAMI 2007); P. Perez et al., Poisson
Image Editing (SIGGRAPH 2003) for the membrane fill; code ported from PhotoCraft
crates/algo/src/inpaint.rs + poisson.rs (a680ed22).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A noise-reduction change invalidated the scene-linear stage and with it every spatial
plane (base, clarity, dehaze, chromaticity), so each tick of an NR drag on the CPU rebuilt
all of them. The plan now also carries the linear key without the NR settings; a draft
render whose image differs from the cached one only in NR keeps those wide blurs and redoes
only log luminance and the fine texture band. Full-quality renders never take them.
Draft NR drag, single thread: prepare 18.7 -> 7 ms (render ~135 -> ~125 ms CPU).
Sources: own design.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Raws without an embedded lens correction look their EXIF lens up in the
user-supplied database; the profile goes through the existing Enable Profile
Corrections path (amount sliders, CPU/GPU geometry stage, DNG export).
Synthetic-grid test renders a distorted chart through the pipeline.
Sources: own design (see lightcraft-lensdb for the model sources).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Raws with a fitted camera look (e.g. Nikon NEF) rendered HDR with the
generic tone curve instead, more than a stop darker through the midtones
than the SDR render: PQ JPEG XL / AVIF exports looked dark everywhere,
and gain map JPEGs carried negative gains, so they went dark on HDR
displays. ToneMap::camera_hdr keeps the camera curve up to 1.5 EV over
grey and lifts highlights as ToneMap::hdr does.
Co-Authored-By: Claude <noreply@anthropic.com>
The frame dropped the flips together with the crop (flip_h: apply_crop &&
...), so the crop tool's uncropped view (render and screen <-> image
mapping) ignored Flip H / Flip V until the tool was closed. A flip isn't
part of the crop: the frame now keeps it without the crop. Cropped
renders and exports are unchanged.
The crop overlay's dimming paired the photo's corners with the crop
box's corners in data order; flipped, the box reaches the screen
mirrored and the photo was dimmed inside the box. It now takes the box's
corners in screen order (flipped_corners). Box, handles, drags and guides
already go through the frame's mapping.
Tests: geometry uncropped_frame_keeps_the_flips (fails without the fix)
and detail flipped_crop_box_corners_come_in_screen_order; headless
screenshots for H, V and H+V checked.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The highlights/shadows base (fast guided filter of log luminance, eps 0.35)
followed thin high-contrast detail, so the per-pixel shift flattened twigs,
lines and texture in bright areas (and dark ones under Shadows); Exposure
kept them.
This changes how every existing edit with Highlights or Shadows renders, so
it ships as a new rendering process (docs/process-versions.md):
- Process::V2 (version 2) is added and made LATEST. New photos and Reset get
it; existing edits stay on V1 until Update to Current Process. Settings
saved without the field still load as V1.
- From V2 the base is the same guided filter of a separable median of log
luminance (radius 0.15 % of the long edge, <= 24 px): fine detail takes its
surroundings' shift and keeps its contrast, region steps stay put (no new
halos). V1's base is untouched: local::tone_base_detail is 0 under V1 and
tone_base then is exactly guided_fast(l, sigma, BASE_EPS), as before.
- CPU (local::prepare) and GPU prepare (median_h / median_v kernels, bit for
bit with the CPU median) both branch on it via PlaneSigmas::base_detail.
- The base plane's cache key is now (sigma, process, median radius)
(local::BaseKey), so a warm cache never serves one process the other's
base. plane_at is shared with the GPU crate.
- finish::base_tone: V2 uses V1's tone map.
Tests: V2 keeps twig/local contrast (>= 0.9 of Exposure's) with no halo; V1
plane sigmas have no median and its base equals guided_fast bit for bit; V1
and V2 render alike without Highlights/Shadows; warm-cache renders across
V1/V2 switches equal fresh ones (fails if the key drops the process); GPU
matches CPU on both processes at 720 and 2400 px, cached and not.
Pentax 645D 2401 at 4000 px: main and V1 renders bit-identical (plain,
Highlights -100, Shadows +100). Highlights -100 bright rel. detail
0.095 (V1) -> 0.147 (V2), Exposure -1: 0.124; Shadows +100 dark rel. detail
0.126 -> 0.148.
RENDER_CACHE_VERSION 54 (adjust at merge).
A stationary hold recorded one pointer sample per frame and each sample laid
another dab at the same spot, so flow accumulated with hold time and the
feathered edge hardened. brush_dabs now lays no dab for a sample that hasn't
moved from the last one, and the canvas no longer records repeated samples at
the same position. Moving strokes and passes back over an area are unchanged.
While the button was held the Detail view drew one hard tinted circle per
pointer sample, so a fast stroke showed separate dabs; the committed stroke
interpolates dabs a quarter radius apart with feather and flow. The live
stroke now uses the pipeline's dab placement (push_dabs, shared with
brush_dabs), dab falloff and flow accumulation in an incremental screen
buffer (crates/ui-egui/src/brush_live.rs): each frame stamps only the new
samples' dabs and uploads only the rectangle they touched. push_dabs also
caps the dabs of a hostile segment instead of looping without bound.
Raws without matrices in the file (ARW, NEF, RW2, RAF, CR2, CR3, PEF, SRW,
ORF) were all edited on the relative white-balance scale (6500 K / 0 = as
shot), even when lightcraft_raw::spectral has measured matrices for the
camera. Those 52 bodies (ILCE-7M3, ILCE-7M4, D850, EOS 5D Mark III...) now
edit in Kelvin like a DNG and like Lightroom.
Design:
- One decision on both sides: spectral::find_camera("Make Model") backs
catalog::relative_wb_camera (Photo::relative_wb); files::WbScaleOf decides
the same from the decoded file. A corpus test asserts they agree.
Photo::fitted_look keeps the old predicate for smart previews.
- The matrices only convert: As Shot is the file's multipliers read through
the model's ColorMatrix1/2 (spectral::as_shot_white_through). The look
(JPEG fit / profile / spectral fallback) is unchanged, and the slider
adapts from that white with Bradford. The camera-space rebalance stays
reserved for files with their own matrices.
- Presets resolve to their Kelvin values (WbMode::preset, sources cited).
- The Color panel shows the white balance in effect (effective_wb).
- XMP: crs::Target::for_file takes the camera; matrix cameras read
crs:Temperature as Kelvin (marked "scale": "kelvin"), others keep the
#510 as-shot shift.
Migration (no look changes):
- WhiteBalance.scale: WbScale { Legacy (absent in JSON), Kelvin }. A
white balance without a scale keeps its old meaning. On a spectral-Kelvin
raw (SourceInfo.legacy_relative_wb) the pipeline composes the old
adaptation (6500 -> value) with the new one (as shot -> ...) in Bradford
cone space (local::legacy_white). Von Kries scalings compose exactly, so
the matrix matches to f32 and the pixels are identical
(pipeline legacy_relative_white_balance_renders_as_before_on_a_kelvin_raw,
media old_relative_white_balance_on_a_kelvin_camera_keeps_its_look...).
- Every white-balance writer (develop.set/adjust/quickAdjust/wb/wbPick/
reset) first runs local::normalize_wb: a legacy value becomes the Custom
Kelvin value with the same look and is marked Kelvin. Partials without a
scale (old presets, develop.merge) stay legacy.
- Catalog format 7: Op::SetAsShotWb replaces the 6500/0 as-shot recorded
by older imports when the file is next decoded or reloaded (system op,
not an undo step). Format-version test updated.
- The web wire carries legacy_relative_wb.
Docs: parity (LR-EDIT-COLOR-TEMP), camera-preview-colour, xmp-interop,
lightroom-catalog-import, control-protocol, mcp, whats-new.
A sibling of spot.findDust for portraits: with the YuNet detector installed, small
round spots on each face's skin that are darker or redder than the surrounding skin
become heal spots (one undo step). The detector's box and landmarks define the skin
(ellipse, skin-colour gate, eye band / brows / nostrils / mouth cut out); a candidate
must stand out from a blurred copy beyond the sensitivity threshold and 2.5x the local
texture, then grow into a small roundish blob. Conservative: faces under 160 px get
nothing, faces with more than 12 candidates (stubble, freckles, pores) get nothing, at
most 6 per face (maxPerFace). Classical, no model beyond the detector.
Tests (synthetic, no model): cheek spots found at their positions, clean face empty,
spots on features ignored, small face ignored, speckled face treated as texture,
hostile boxes and an empty image do not panic. New LR-REM-BLEMISH parity row, Find
Blemishes section in docs/faces.md.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
There was no zoom/pan reset on main to fix: what this PR changes is that a
zoomed view with the mask overlay shown is now a canvas-sized frame plus a
sharp window carrying the overlay, as without it, instead of a whole-frame
render at the zoomed size. Tests: the overlay on a window matches the same
pixels of the whole render for radial, refined linear and sky masks in
three views (pipeline tests_window.rs), and at 1:1 the loupe uses a window
with the overlay, keeps zoom and pan, and swaps tiles when the overlay is
hidden or shown (ui-egui tests_preview_limit.rs). Parity row updated. No
render output change (no cache bump).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Rendered the old and new heuristic over the 24 procedural demo scenes: the
rewrite sharpens edges on blue skies (beach, dunes, canyon), but drops warm
sunset and dark night skies to nothing, punches a hole around the sun, and
selects lavender rows, a flower and lit mountains as sky. It also changes the
render of every existing Sky mask without a process version. Back to main's
heuristic; the masking-view part of this PR stays.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Light panel: HDR switch, headroom limit and an SDR Rendition flyout
(shown with HDR on); Visualize HDR beside the switch.
- `Overlay::HdrRange` / `view.visualizeHdr`: the SDR render dimmed to grey,
with pixels the HDR rendition puts above SDR white painted in four bands by
stops above it. Rendered on the CPU from the float HDR render.
- Export dialog: "HDR output" for JPEG (gain map) and 32-bit TIFF.
- Parity (Q. HDR 2 ✅ 3 🟡, LR-EDIT-HDR-MODE ✅, LR-EXP-HDR 🟡), README, ROADMAP.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- `DevelopSettings.hdr`: enabled, headroom limit (`max_ev`) and SDR-rendition
offsets (brightness, contrast, highlights, shadows, whites, clarity), with
control specs (`hdr.maxEv`, `hdr.sdr*`) and copied with the Light group.
- `ToneMap::hdr` / `display_hdr`: the SDR curve through the midtones, with
highlights lifted up to `2^max_ev`; identical to the SDR tables at peak 1.
- `OutputDepth::F32Hdr`: unclamped linear output up to the peak. The finish
stage carries the peak through highlight desaturation, gamut mapping,
encoding, curves, grain and LUT profiles; with HDR off every branch is the
SDR code path, bit for bit.
- `settings_for`: every non-HDR render (CPU and GPU) uses the SDR rendition.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Auto was a percentile rule that set Exposure about 1.4 EV higher than
Lightroom's Auto on the same photos (mean +1.81 against +0.44 EV) and
barely used Shadows: on 24 Sony raw photos its results were further from
Lightroom's Auto results (median dE76 22.0) than leaving the photo
unedited (7.9), about 18 L* too bright.
Auto is now Lightroom's shape of rule with values chosen for our results:
- Exposure = -0.58 - 0.41 * the scene's log-average luminance (EV from
middle grey, scene-linear, under the photo's white balance, so it does
not depend on the base tone curve), clamped to -2..3 EV. The slope is
Lightroom's: its Auto's chosen Exposure against our log average, r -0.92;
- Highlights -66 and Shadows +52: Lightroom's own average Auto values;
- Contrast +68 and Vibrance +30 (our scales; our base tone is flatter);
Whites, Blacks and Saturation 0.
The exposure offset, Contrast and Vibrance were fitted jointly so our
renders match Lightroom's Auto exports on the 16 photos whose starting
look Sony's DRO doesn't brighten (#568 changes the other 8). Held out one
photo at a time, each fold fitted from its own training photos only:
median dE76 6.38 on those 16 (8.28 unedited, 18.81 with the old rule),
12 of 16 closer than unedited; 7.70 on all 24 (7.92 unedited); on the 8
DRO photos 9.39 (7.00 unedited), their brighter start showing through.
Freeing Highlights and Shadows too (5 fitted values) did no better held
out (6.51) and left them poorly determined across folds.
The log average skips pixels that aren't finite (a damaged decode) through
the proxy's resampling and white balance; with no valid pixel, Exposure 0.
Values are black-box observations of Lightroom (the settings its Auto
chose and its exports), as in #514. Auto only applies when asked for, so
existing edits keep their look; HDR merges with Auto Settings use the
same rule.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Previews were always sRGB, which wide-gamut monitors show oversaturated.
Settings > Display > Choose Profile... (and the UI command
app.displayProfile) now shows photos through the monitor's ICC profile,
matrix/TRC or LUT-based.
- codecs: DisplayProfile (moxcms): the display's primaries (colorant tags,
or measured through the A2B tables of a LUT-only profile), the proxy ->
device correction (per-channel curve tables for matrix/TRC profiles, a
CMS transform for LUT profiles) and sRGB <-> display transforms.
- pipeline: DisplaySpace / RenderRequest::display: the per-pixel stage
renders into the display's primaries and gamut maps into its gamut
(CPU and GPU: only to_out / out_luma change). Soft proofing's display
gamut warning uses the display's gamut.
- engine: RenderJob::with_display; display::present converts sRGB
results (thumbnails, cached previews, embedded JPEGs). Histograms and
the preview caches stay sRGB; exports are unchanged.
- ui: the loupe, Before, Compare, hover and second window render for the
display; everything else is converted on the render workers. Settings >
Display tab, ui.json setting, file picker opening in the system's ICC
folder.
Tests: profile parsing and transforms (sRGB, P3, gamma 2.2, a synthetic
LUT-only profile, corrupted files), display renders equal 8-bit renders
into the matching output space, sampled histograms, and a headless UI test
of the whole flow. docs/display-profiles.md describes the design.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WEuxxPyG8t6KcADbZSEAUj
Sony's Dynamic Range Optimizer (factory default Auto) brightens the darker
regions of the camera JPEG, not the raw. The tone curve fitted to that JPEG
carried the brightening into the raw's starting look: on 8 ILCE-7CR photos
with DRO on, LightCraft rendered +6.4 L* from Lightroom's default (median
dE76 7.5), against -0.2 L* (3.6) for the 16 with DRO off.
- lightcraft_raw::embedded_preview_dynamic_range_optimized reads Sony
maker-note tag 0xb025 (every documented value: 0 off; 1-3, 8-12 and
16-23, i.e. up to Lv8, on). It was present on all 264 Sony raws checked
(98 bodies); 0xb04f only says on/off and is missing on older bodies.
- With DRO on, the look keeps the colour fitted to the JPEG and its tone
curve is lowered, knot by knot, to Sony's curve without DRO wherever that
is darker (assets/camera-tone/sony-dro-off.json, pooled from 12 CC0
raw.pixls.us ARWs of 10 interchangeable-lens bodies; sources and
derivation in the file and assets/ATTRIBUTION.md). The second-last knot
is lowered only as far as keeps the shoulder past the last knot at or
below the fitted curve, so the result is never brighter anywhere.
Compacts that map raw values darker keep their own curve.
- DRO off, unknown and other makers: unchanged (byte-identical renders on
71 + 35 photos). Render cache version 23.
DRO-on photos now land at +0.8 L* from Lightroom (median dE76 3.3), band by
band like the DRO-off ones. Docs: camera-preview-colour.md, parity row.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each photo's develop settings record the rendering process they are
interpreted by (`DevelopSettings::process`). There is one process, V1, the
pipeline as it is; nothing renders differently (180 CPU 8/16-bit and GPU
renders of procedural scenes, and the saved settings JSON and hash64, are
byte-identical to main).
Settings saved without the field load as V1 (a field-level serde default),
new settings get the latest (`Default`), and V1 is left out when saving, so
files and preview-cache keys stay as they were. A damaged value (anything
but a whole number up to 2^32 - 1) reads as V1 too, so it never costs the
rest of the settings (an unparsable `lc:settings` sidecar is ignored). Reset moves a photo to the
latest process, section and slider resets don't; virtual copies inherit
it; copy / paste / sync / Auto Sync / presets don't carry it; a number from
a newer LightCraft is kept and renders with the newest known process. The
process is not an edit (Edited badges, import-look checks). The base tone
map is chosen per process in one place (`finish::base_tone`, shared by CPU
and GPU). Photo > Update to Current Process (`develop.updateProcess`) moves
selected photos on an older process to the latest as one undo step. Its
menu enablement follows the selection; a call that names photos (`ids` /
`id`) is validated on those instead, through a new opt-in
`CommandSpec::explicit_targets` (execute checks enablement before the
handler sees `ids`). `develop.get` reports the process. The rule and how to add V2:
docs/process-versions.md.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Renders a raw's DNG ProfileGainTableMap (Apple ProRAW's local tone mapping)
when the photo's Profile option "Camera local tone mapping" is on
(`profile.camera_local_tone`, off by default: Lightroom Classic renders
ProRAW without it). Built on #427's parser; supersedes the decode-time
applier of the first version of #273.
- raw/gaintable: GainTableMap::gain / evaluator (GainEval) per DNG 1.7.1:
bilinear between tables with edges replicated, input clamp(weights .
(R,G,B,min,max))^gamma in linear ProPhoto after the baseline exposure,
index input x MapPointsN clamped to the last point. Saturating cell
math; maps whose sizes don't match their gains aren't evaluated.
SourcePlacement: where a developed source sits in the active area.
- pipeline/local_tone: the gain per output pixel in the scene-linear
stage, before the white balance, mapped back through the frame (crop,
user orientation, warp) and the EXIF orientation. lin_needs_cpu routes
the stage to the CPU when it's on (CPU and GPU match); lin_key includes
it. SourceInfo::local_tone carries the map from the decoder.
- develop: Profile::camera_local_tone (omitted from JSON when off, so
existing settings and their hashes are unchanged); Profile row checkbox
when the source has a map; translations.
- RENDER_CACHE_VERSION 17. Docs: parity row and What's New as an opt-in.
Off renders pixel-identical to main on two iPhone 15 Pro ProRAWs (12 and
48 MP). On adds ~60-75 ms to the scene-linear stage at 1536x2048 on a
2-core VM, the same for both files (cost follows output pixels).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VFqow2P1EtDxt6nRjjQoTA
A custom white balance on a raw with a colour matrix (DNGs, and other raws
without a file-local look) re-develops the pixels for the chosen white
through the file's own colour model: lightcraft_raw::color::rebalance
re-evaluates the camera transform at the new white and maps the as-shot
development onto it, so a neutral lit by that white renders neutral and
saturated colours move as the camera's matrices say (DNG spec ch. 6, as
Lightroom does). The decoder passes the colour tags and the white the
pixels were developed for in SourceInfo::camera_color; other photos, and
relative white balances, keep the Bradford adaptation. The eyedropper and
Auto invert the same model (color::neutral_white), so a picked grey card
renders neutral.
The Tint sign is main's (#349): unchanged here. Render cache version 15.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Read DNG 1.6 semantic masks (SemanticName, SemanticInstanceID,
MaskSubArea; any coding the tile/strip reader decodes, e.g. ProRAW's
lossy-JPEG matte) with lightcraft_raw::semantic_masks, map them onto the
developed image, and carry Apple's sky / portrait / skin / hair / teeth /
glasses mattes in SourceInfo::mattes (SourceInfo is no longer Copy).
Sky, Subject, Background and People components sample them through
orientation, warp (Warp::frame, as geometry.rs) and crop like the image,
so preview = export, instead of the heuristics; files without mattes
render as before. Malformed mask IFDs are skipped, and only the first
MAX_MASKS mask IFDs are decoded, so a file full of them costs bounded
work. The render cache version is bumped once, in the white-balance commit.
The corpus test reads corpus/raw/dng-apple-iphone12pro-proraw.dng, the
name `cargo xtask corpus` downloads it under.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review findings: window growth for spots was capped silently and then rendered
wrongly; Auto Mask growth took the whole stroke's bounding box; hidden masks grew
windows; the wasm window memory cap was not enforced. window_for_reads_checked
returns None when what a window reads would need more than 8192 px (4096 on
wasm) along an axis, Session::region_job returns None then (the loupe keeps the
whole-frame render), Auto Mask growth is per dab near the request (once, not
chained along the stroke) and ignores hidden masks. Docs say what the limits are.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The GPU renderer renders windows (no more CPU fallback): the vignette gets the
window's offset and the whole output's size (VIG_VIEW), the airlight and noise
reduction scale come from the whole frame, and a window grown for spots or
strokes is cut back after the readback. equivalence.rs renders every settings
case as a window plus a corner vignette and a spot reading from outside, within
the existing 1 LSB. Auto Mask strokes join spots in growing the window (they read
the pixel under each dab). An Auto Mask dab's reference pixel is chosen from its
position at f32 precision, like the GPU kernel: an f64 rounding error that
depended on the window's start could put a boundary dab on either pixel.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Spot removal reads outside the spot and its automatic source depends on the
pixels at hand. A windowed render now fixes automatic sources from a fixed-size
render of the whole frame and grows the window it works on to hold every spot
that touches the requested one and the patch it reads (bounded at 8192 px),
cutting the result back to the request, so photos with spots get sharp windows
too. The wide stages (dehaze, clarity, highlights/shadows) are checked at depth
on hard-edged blocks: the cut kernels cost at most a few levels, so they are not
computed from a whole-frame proxy.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
An adversarial review found local::denoise and Refine Edges scaled with the
window's own size, so their strength changed with pan and zoom. Frame::output_long
gives the whole output's long edge to both; tests on a noisy source cover them.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A zoomed view needs the pixels it shows at the zoom scale, not a whole-frame
texture stretched over them. RenderRequest gains an optional PixelWindow of the
virtual full output that max_w x max_h describes; only the window is
materialized. It is Frame:🪟 the crop rectangle narrows to the window
(flips as drawn), so sampling, masks, spots, red eye and the lens warp need no
change. The post-crop vignette keeps the whole frame's geometry (Frame::view)
and dehaze uses the whole frame's airlight (estimated on a 384 px render), so a
window matches the same pixels of the full render. The GPU renderer declines
windowed requests (CPU for now).
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The pipeline honoured a section's eye only where it applies Effects,
Optics, Geometry and Calibration, so turning the eye off on Light, Color
or Detail changed nothing. DevelopSettings::effective() resets the
switched-off sections to their defaults (Light with the tone curve; Color
with white balance, mixers, grading and point colour; Detail), and both the
CPU and GPU renderers render those. Borrowed, so free, when every section
is on.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01912WaK6Rmew3wxZuPyX7E8