Keep the photo's colour when the highlight and shadow sliders move

Highlight/Shadow scaled R, G and B by one luma gain. That keeps the ratio
but crushes absolute chroma, so -SH turned a saturated blue into near-black
and -HL turned bright colours grey, and both sliders only bit at the very
ends of the range (knees 0.80..1.00 / 0.00..0.30) — they read as dead on
any photo without true whites or blacks.

The curve now moves the luma and carries the colour difference (rgb - luma)
along at clamp(gain, 0.55, 1.35), so hue survives darkening and lifting
instead of collapsing to black or white. Both knobs are pure additive luma
shifts with soft knees over the upper/lower half: the 0.50 midpoint moves
under 3%, and the curve stays monotonic (the old multiplicative form was
not — with hl=-1 a grey 0.73 came out darker than 0.80).

Kotlin applyTone port and the native bench probe gates follow.

Verified on device (4200x2800 probe: grey ramp + colour patches), library
export at ratio 4:3: -10 highlight leaves the darks alone and drops white
243->189 with the sky still blue; -10 shadow keeps blue as dark blue
(0,0,254)->(0,3,146), never black, ramp stays monotonic. Export keeps the
source 4200x2800 as well, so nothing is cropped.
This commit is contained in:
2026-09-10 11:41:43 +07:00
parent 4ad26b514e
commit bba2d76957
3 changed files with 41 additions and 53 deletions
+2 -2
View File
@@ -53,8 +53,8 @@ export async function nativeBenchProbe(): Promise<void> {
console.error('[BENCH] classic-neg-on-black matrix[0..4]=' + matrix.slice(0, 5).join(','));
// P2 tone parity on solid ramps (expected values computed from TONE_SKSL):
// white(255) hl=-1 -> 140; gray64 sh=+1 -> 75; white dr=1 -> 209;
// gray26 sh=+1 -> 104; gray26 hl=+1 sh=-1 -> 11.
// white(255) hl=-1 -> 173; gray64 sh=+1 -> 113; white dr=1 -> 209;
// gray26 sh=+1 -> 105; gray26 hl=+1 sh=-1 -> 0.
const toneGates: Array<{ rgb: [number, number, number]; tone: { dr: number; hl: number; sh: number }; tag: string }> = [
{ rgb: [255, 255, 255], tone: { dr: 0, hl: -1, sh: 0 }, tag: 'white-hl-1' },
{ rgb: [64, 64, 64], tone: { dr: 0, hl: 0, sh: 1 }, tag: 'gray64-sh+1' },