Merge: a histogram that reads the sensor, beside the one that reads the frame

FR-CULL-3's other two bullets. What existed was a display histogram
tagged FR-DSP-7, counting AdjustPass's 8-bit output with r == 255
clipping counters -- it says a highlight is gone precisely where this
requirement needs it to say the highlight is recoverable.

The new reduction runs over the demosaiced scene-linear texture on a
stops-below-saturation axis: camera-native, unbalanced, unmatrixed,
uncurved, normalised by the sensor's own black and white levels, so 1.0
is saturation by construction. Four series, and the fourth is the
brightest channel rather than luma, because a weighted sum of unbalanced
values is a number about nothing. Cached per photograph, not per frame:
nothing downstream of the demosaic can move a count.

Both readings are legitimate and answer different questions, so the
panel offers a choice rather than replacing one with the other.

ARCH 5.5 is amended to match. It specified a pre-demosaic reduction;
retaining the CFA samples costs 48 MB at 24 MP and 120 MB at 60 MP
resident on every photograph opened, whether or not anyone looks at the
histogram, on the platform ARCH 6.2 exists for. The spec now records two
reductions, why the more complete one was not worth its cost, and what
the cheaper one cannot answer: it counts pixels not photosites, it
cannot see above white, and it is measured after the CFA pattern is gone.

Verified: clippy -D warnings clean, 98 dr-gpu tests, 556 dr-ui tests.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-30 00:14:34 +02:00
co-authored by Claude Opus 5
10 changed files with 1793 additions and 47 deletions
+38
View File
@@ -320,6 +320,11 @@ fn reset_view_state(window: &AppWindow) {
// beside the next one's filename is a confident, precise lie, and the gap
// before the new frame settles is exactly long enough to read it.
window.set_histogram(histogram::empty());
// And the raw reading with it. It belongs to one file more completely than
// the display histogram does — nothing about the edit can move it — which
// makes leaving it standing over the next photograph's filename worse
// rather than better: it would look exactly as current as it is wrong.
window.set_raw_histogram(histogram::raw_empty(histogram::RawAbsence::NoImage));
// TRACES: FR-CULL-3
// The marks go down with it, and for the same reason. What is *not* reset
// is whether peaking is switched on: that is a way of looking at a folder
@@ -1669,6 +1674,32 @@ pub fn run(paths: Vec<PathBuf>) -> Result<()> {
.as_ref()
.map_or_else(histogram::empty, histogram::view),
);
// **The raw reading, pushed on the same path but not
// recomputed on it.** `DevelopSession::raw_histogram`
// caches: the demosaiced source is fixed for the life
// of the session, so this costs one dispatch per
// photograph and a clone of four kilobytes per settled
// frame thereafter. It is pushed from here anyway
// rather than once at open, because this is the one
// path every photograph takes and a panel that had to
// be told separately would be empty on some route into
// develop mode.
//
// Three outcomes, said apart. A file that was never
// raw has no white level and so no scale to measure
// headroom against; a device that could not build the
// reduction has a fault worth reporting; and only the
// third is a reading.
let raw = if !s.has_sensor_data() {
histogram::raw_empty(histogram::RawAbsence::NotRaw)
} else {
s.raw_histogram().as_ref().map_or_else(
|| histogram::raw_empty(histogram::RawAbsence::NoDevice),
histogram::raw_view,
)
};
window.set_raw_histogram(raw);
}
// TRACES: FR-CULL-3 | NFR-P14
@@ -1705,6 +1736,13 @@ pub fn run(paths: Vec<PathBuf>) -> Result<()> {
// No frame, so nothing to describe. The stale plot would
// otherwise sit beside the error message looking current.
window.set_histogram(histogram::empty());
// **The raw reading is left standing, and that is not an
// oversight.** It describes the sensor data behind the
// session, which a failed *render* says nothing about: the
// demosaic succeeded or there would be no session at all.
// Emptying it here would take away the one instrument
// still telling the truth, at the moment the other one
// stopped.
// TRACES: FR-CULL-3
// And nothing to mark. Focus marks over the last frame
// that rendered, beside a message saying this one did not,