web: make AUTO write the tone and the colour it reads off the frame
The AUTO chip measured the photo and wrote one knob, EV. It now writes the four
the measurement actually names, off the same binned ramp (ui/Histogram.tsx),
which is why it is one chip and not four: the means it needs are all in the
histogram the exposure answer already reads.
EV the mean luma, unchanged
HIGHLIGHT the top 1% (p99 > 0.9 pulls back), to -5 of the ruler at most
SHADOW the bottom 1% (p01 < 0.02 opens up), to +5
TEMPERATURE/TINT the gain that puts the three channel means on each other,
green as the anchor: gray-world on linearised means, then the
closest of 76 temperatures x 21 tints under the renderer's own
kelvinToRGB, so the pair cannot drift from what the ruler applies.
The ends rather than the average is what keeps a small blown window from
dragging the whole frame: a specular in the corner wants HIGHLIGHT, not a
flatter picture everywhere. Both ends stop at half the ruler, so the frame is
corrected and a hand can still finish the move; the knobs then report the
numbers AUTO chose, the way the EV knob does.
Each reading is a pure function of the ramp, so pressing AUTO twice lands on the
same recipe by construction, and a frame with a dead channel leaves the WB ruler
where it is rather than inventing a cast. ponytail: one linear ramp per end and
no scene analysis; add a curve, or weight by how much of the frame is clipped,
when AUTO starts overshooting a scene with a genuine specular in it.
scripts/auto-tone-check.mjs holds the three readings: the percentile walk, the
thresholds that leave a knob alone, and the scan landing back on the gain it was
asked for.
This commit is contained in:
+18
-11
@@ -47,7 +47,7 @@ import { curveIsActive } from '../shared/utils/toneCurve';
|
||||
import { HEAL_DEFAULT_R } from '../shared/utils/heal';
|
||||
import { MOSAIC_DEFAULT_R } from '../shared/utils/mosaic';
|
||||
import { MASK_DEFAULT_FEATHER, MASK_EXPOSURE_MAX } from '../shared/utils/gradientMask';
|
||||
import { readHistogram, autoExposureStops } from './ui/Histogram';
|
||||
import { readHistogram, autoExposureStops, autoTone, autoWhiteBalance } from './ui/Histogram';
|
||||
import type { MsgKey } from './i18n/vi';
|
||||
|
||||
// Mirrors the API's MAX_PHOTOS_PER_USER: shown on SAVE PHOTO, enforced there.
|
||||
@@ -2587,20 +2587,27 @@ export function Workspace() {
|
||||
// for the mono stock and swaps right back. Adjustments are never touched, so
|
||||
// a knob moved while the switch is on survives the trip back.
|
||||
const monoOn = isMonochromeBase(recipe.baseFilter);
|
||||
// AUTO (LIGHT tab): the one chip that WRITES a value instead of naming one. It
|
||||
// measures the LOADED PHOTO — not the preview — so the number does not depend
|
||||
// on the knobs already in play: pressing it twice lands on the same EV, the way
|
||||
// Lightroom's Auto does. readHistogram gives the average luminance,
|
||||
// autoExposureStops turns it into stops, and the EV knob takes it, rounded to
|
||||
// the 0.1 that knob's own readout prints.
|
||||
const autoExposure = async () => {
|
||||
// AUTO (LIGHT tab): the one chip that WRITES values instead of naming one. It
|
||||
// measures the LOADED PHOTO — not the preview — so the numbers do not depend
|
||||
// on the knobs already in play: pressing it twice lands on the same recipe, the
|
||||
// way Lightroom's Auto does. readHistogram gives the binned ramp, which the
|
||||
// three readings in ui/Histogram take their answer from: the mean luma for EV,
|
||||
// the top and bottom 1% for HIGHLIGHT/SHADOW, the channel means for the WB
|
||||
// ruler. AUTO is on LIGHT but it does write WB — the colour cast is part of
|
||||
// what the frame is asking for, and the TEMPERATURE/TINT knobs report it.
|
||||
const autoTune = async () => {
|
||||
if (!previewBytes) return;
|
||||
const url = URL.createObjectURL(new Blob([previewBytes as BlobPart], { type: 'image/jpeg' }));
|
||||
try {
|
||||
const hist = await readHistogram(url, () => true);
|
||||
if (hist) {
|
||||
remember();
|
||||
setAdjustment({ exposureCompensation: Math.round(autoExposureStops(hist.lum) * 10) / 10 });
|
||||
setAdjustment({
|
||||
// EV is the one knob printed to a tenth; the others are whole units.
|
||||
exposureCompensation: Math.round(autoExposureStops(hist.lum) * 10) / 10,
|
||||
...autoTone(hist.lum),
|
||||
...autoWhiteBalance(hist.r, hist.g, hist.b),
|
||||
});
|
||||
}
|
||||
} catch {
|
||||
// A photo that will not decode leaves the recipe alone — a broken preview
|
||||
@@ -2683,8 +2690,8 @@ export function Workspace() {
|
||||
case 'light':
|
||||
return [
|
||||
// AUTO rides at the head of the strip because it is the one chip here
|
||||
// that is an ACTION rather than a knob or a look (see autoExposure).
|
||||
{ key: 'auto', label: 'AUTO', onClick: () => void autoExposure() },
|
||||
// that is an ACTION rather than a knob or a look (see autoTune).
|
||||
{ key: 'auto', label: 'AUTO', onClick: () => void autoTune() },
|
||||
...paramChips(PARAM_DEFS.iq),
|
||||
groupChip('dr'),
|
||||
// TONE CURVE is not a row of sliders: it opens the graph on the photo
|
||||
|
||||
Reference in New Issue
Block a user