LR-EDIT-LIGHT-HIGHLIGHTS: keep local contrast under Highlights/Shadows, as process V2 (#632)

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).
This commit is contained in:
radialmonster committed 2026-10-10 13:46:14 -04:00
1 parent b70ae1741e
commit 70103a7e55
13 files changed
+516 -38

No files matched your search

+6 -2
View File
@@ -81,16 +81,20 @@ impl ProcessVersion {
pub enum Process {
/// The rendering of LightCraft before process versions existed (2026-10).
V1,
/// Highlights and Shadows keep the local contrast of the tones they move: their edge-aware base
/// is taken of a fine median of log luminance (issue #632).
V2,
}
impl Process {
/// Every process, oldest first.
pub const ALL: [Process; 1] = [Process::V1];
pub const LATEST: Process = Process::V1;
pub const ALL: [Process; 2] = [Process::V1, Process::V2];
pub const LATEST: Process = Process::V2;
pub const fn version(self) -> ProcessVersion {
match self {
Process::V1 => ProcessVersion(1),
Process::V2 => ProcessVersion(2),
}
}
}