Every background job reported into a window property of its own — library-thumbs-done, library-pin-total, library-syncing — which only the grid ever read. A pin download that outlived the view it was started from drew nothing at all once the user opened an image, and there was no answer anywhere to "what is this busy with", because the answer was spread across eight properties nothing collected. They report to one register now (ui/dr-ui/src/activity.rs). It publishes an aggregate, which draws a three-pixel bar across the top of the shell in every view, and a row per job, which the settings page lists: scans, thumbnail batches, pin and open downloads, sidecar uploads, the sync and the trash. Failures stay on the list until they are cleared; routine successes do not, or a scroll would bury them. The handle removes a still-running job when it drops, so a worker that dies mid-transfer takes its row with it rather than leaving the bar sweeping for the rest of the session. Also carries in-flight work from a parallel session — the drawn icon set and the dr-pipeline ops split. dr-pipeline's build script does not compile at this commit; ui/dr-ui does, with clippy clean and its tests passing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
87 lines
3.1 KiB
YAML
87 lines
3.1 KiB
YAML
id: white_balance
|
|
label: op.white_balance
|
|
order: 10
|
|
|
|
doc: |
|
|
White balance — temperature and tint, relative to as-shot.
|
|
|
|
Expressed as an offset from what the camera chose rather than an absolute
|
|
kelvin value. Neutral means "as shot", so the control starts where the
|
|
image already is and a reset returns there. An absolute scale would make
|
|
the neutral position depend on the file, which is exactly the confusion
|
|
Lightroom's temperature slider creates on non-raw files.
|
|
|
|
The as-shot multipliers themselves are applied by the composer's preamble
|
|
rather than here — every image has them even when this operation is
|
|
neutral, so they cannot live in a fragment that vanishes at neutral.
|
|
|
|
placement: |
|
|
First. It is a correction to how the scene was captured, and every tonal
|
|
operation after it should act on a correctly balanced image.
|
|
|
|
params:
|
|
temperature:
|
|
label: param.temperature
|
|
kind: amount
|
|
doc: |
|
|
Warmer is positive, matching every other raw developer: dragging right
|
|
makes the image warmer, even though that means *lowering* the colour
|
|
temperature being corrected for.
|
|
tint:
|
|
label: param.tint
|
|
kind: amount
|
|
|
|
# Temperature trades red against blue; tint trades green against magenta.
|
|
# Both are scaled so the full range is a strong but not destructive
|
|
# correction: ±0.5 in log2 at the extremes — half a stop of channel shift,
|
|
# which covers ordinary illuminant error without letting the slider blow a
|
|
# channel on its own.
|
|
uniforms:
|
|
mul_r: exp2(temperature / 100 * 0.5)
|
|
mul_g:
|
|
value: exp2(-(tint / 100 * 0.5))
|
|
doc: |
|
|
Green is held at unity by temperature, so the control does not double as
|
|
an exposure slider — green carries most of the luminance.
|
|
mul_b:
|
|
value: exp2(-(temperature / 100 * 0.5))
|
|
doc: Blue moves opposite red, so a neutral grey stays grey as the control moves.
|
|
|
|
wgsl: |
|
|
c = c * vec3<f32>(mul_r, mul_g, mul_b);
|
|
|
|
tests:
|
|
- name: neutral_is_as_shot
|
|
expect: { mul_r: 1.0, mul_g: 1.0, mul_b: 1.0 }
|
|
expect_active: false
|
|
|
|
- name: warming_raises_red_and_lowers_blue
|
|
set: { temperature: 100 }
|
|
expect: { mul_r: 1.4142135, mul_b: 0.70710677 }
|
|
|
|
- name: temperature_leaves_green_alone
|
|
why: |
|
|
Otherwise the control doubles as an exposure slider, because green
|
|
carries most of the luminance.
|
|
set: { temperature: 100 }
|
|
expect: { mul_g: 1.0 }
|
|
|
|
- name: tint_moves_green_against_magenta
|
|
why: Positive tint reduces green, and must not touch red or blue.
|
|
set: { tint: 100 }
|
|
expect: { mul_g: 0.70710677, mul_r: 1.0, mul_b: 1.0 }
|
|
|
|
- name: cooling_is_the_inverse_of_warming
|
|
why: |
|
|
Warming by n then cooling by n must return to neutral, so the two
|
|
directions have to be exact reciprocals rather than merely similar.
|
|
set: { temperature: -100 }
|
|
expect: { mul_r: 0.70710677, mul_b: 1.4142135 }
|
|
|
|
- name: the_extremes_stay_within_half_a_stop
|
|
why: |
|
|
A white balance control that can blow a channel by itself is a trap;
|
|
correction belongs in a range where highlights survive.
|
|
set: { temperature: 100, tint: 100 }
|
|
expect_range: { mul_r: [0.70, 1.42], mul_g: [0.70, 1.42], mul_b: [0.70, 1.42] }
|