web: print what the temperature ruler does, not the colour of the light
COLOR TEMP's swatch was Tanner Helland's blackbody fit, so it painted the colour of the LIGHT the number names: 2500K came out orange #ff9f46 while the engine cooled the frame on the same knob. Read off the render of a neutral 128-grey (mean of the painted preview, CCT via McCamy), the two ran opposite ways at every stop: K old swatch swatch CCT picture mean picture CCT 2500 #ff9f46 2384K 115,135,204 47691K (blue) 3200 #ffb87b 3096K 128,141,171 11246K 4000 #ffcea6 3931K 135,141,156 8203K 5500 #ffedde 5414K 144,139,143 6479K (unity) 6500 #fffefa 6322K 148,138,139 6029K 7500 #e6ebff 7730K 149,138,132 5555K 10000 #cadaff 10024K 153,138,128 5180K (warm) So the number is a Kelvin of the scene's light — which is exactly why the engine warms the picture as K rises — and the swatch was the one thing on the ruler that disagreed with the ruler. It is now painted from kelvinToRGB itself, the same gains the render puts on every pixel, on a mid grey, so the band under the knob can never drift from the frame above it. kelvinToHex goes away with it: one Kelvin->colour mapping in the app, not two. Measured after: the swatch's R-B tracks the render's own cast at every stop (-82 vs -89.8 at 2500K, +23 vs +24.0 at 10000K), 5500K is flat #808080 — the engine's unity point — and 3200K sits cool / 7500K warm either side of it. temp-swatch-test.cjs (scratchpad): 33 PASS / 0 FAIL.
This commit is contained in:
+13
-11
@@ -33,7 +33,7 @@ import {
|
||||
type HslBandId,
|
||||
type Recipe,
|
||||
} from '../shared/types';
|
||||
import { HSL_BANDS, isMonochromeBase, nearestHslBand, rgbToHsl } from '../shared/utils/colorUtils';
|
||||
import { HSL_BANDS, isMonochromeBase, kelvinToRGB, nearestHslBand, rgbToHsl } from '../shared/utils/colorUtils';
|
||||
import type { MsgKey } from './i18n/vi';
|
||||
|
||||
// Mirrors the API's MAX_PHOTOS_PER_USER: shown on SAVE PHOTO, enforced there.
|
||||
@@ -117,15 +117,17 @@ const TEMP_PRESETS: { label: string; kelvin: number }[] = [
|
||||
{ label: 'FLUOR', kelvin: 4000 },
|
||||
];
|
||||
|
||||
// Kelvin -> sRGB (Tanner Helland's approximation, what every WB widget uses):
|
||||
// 2500K reads warm orange, 5500K a near-neutral white, 10000K blue.
|
||||
function kelvinToHex(k: number): string {
|
||||
const t = Math.min(40000, Math.max(1000, k)) / 100;
|
||||
const chan = (v: number) => Math.round(Math.min(255, Math.max(0, v))).toString(16).padStart(2, '0');
|
||||
const r = t <= 66 ? 255 : 329.698727446 * (t - 60) ** -0.1332047592;
|
||||
const g = t <= 66 ? 99.4708025861 * Math.log(t) - 161.1195681661 : 288.1221695283 * (t - 60) ** -0.0755148492;
|
||||
const b = t >= 66 ? 255 : t <= 19 ? 0 : 138.5177312231 * Math.log(t - 10) - 305.0447927307;
|
||||
return `#${chan(r)}${chan(g)}${chan(b)}`;
|
||||
// Kelvin -> the CAST the ruler prints. Not the colour of a blackbody at that K:
|
||||
// the number names the light the scene was shot under, so the engine compensates
|
||||
// for it and WARMS the picture as K rises (kelvinToRGB). A swatch painted with
|
||||
// the light's own colour would therefore run the other way from the picture —
|
||||
// orange under a knob that blue-d the frame. Measuring the ruler means painting
|
||||
// what the render does: the engine's own gains on a mid grey, which is the same
|
||||
// matrix step the preview puts on every pixel, so this can never drift from it.
|
||||
function temperatureSwatch(k: number): string {
|
||||
const g = kelvinToRGB(k);
|
||||
const chan = (v: number) => Math.round(Math.min(255, Math.max(0, v * 255))).toString(16).padStart(2, '0');
|
||||
return `#${chan(0.5 * g.r)}${chan(0.5 * g.g)}${chan(0.5 * g.b)}`;
|
||||
}
|
||||
|
||||
// HSL -> #rrggbb (CSS keeps hsl() opaque to a test, and the swatch prints its
|
||||
@@ -144,7 +146,7 @@ function hslToHex(h: number, s: number, l: number): string {
|
||||
// visible next to the number. TINT runs green (-10) through a neutral grey (0)
|
||||
// to magenta (+10) — the axis every WB tool labels G..M.
|
||||
const SLIDER_SWATCH: Partial<Record<string, (v: number) => string>> = {
|
||||
temperature: kelvinToHex,
|
||||
temperature: temperatureSwatch,
|
||||
tint: (v) => hslToHex(140 + ((v + 10) / 20) * 160, Math.abs(v) * 7, 58 + (10 - Math.abs(v)) * 3),
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user