web: NOISE REDUCTION takes the colour speckle out of the frame and leaves every strand of it where it was
The knob was a `MakeBlur` image filter on the draw of the graded photo — one
sigma over all three channels — so at NOISE REDUCTION 100 a 1024-pixel preview
lost every edge finer than 0.6 of a pixel of its own and nothing brought the
luminance detail back. That is thay_doi_thong_so_giong_lightroom.md §4's own
warning ("Noise Reduction sẽ làm nhòe toàn bộ chi tiết sợi tóc và vân da") written
into the engine, and the filter had a second cost: a paint filter is handed the
shader's INPUT, so the pass could never read the graded pixels it was supposed to
correct.
It is a two-child pass now, NR_SKSL, run after the draw: the frame, and a blurred
copy of it (blurredFrame — the snapshot read back through the same MakeBlur the
ramp's base and the negative sharpening use). The output takes its CHROMA from
the blurred child and its LUMA from the frame, the split the tone ramp's header
already describes (`rgb - luma`), so the output's brightness is the input's at
every amount by construction — the eye is nearly blind to a hue change at that
scale, which is the whole reason the colour half is free. The reference reaches
NR_CHROMA_SPAN = 0.4% of the frame's width, the doc's own 3..5 pixels of a
full-resolution frame, where the knob's blur was 0.6 of a pixel.
The luminance half of §4.1 (its bilateral filter) is deliberately not here: it is
the half that costs detail and no frame has shown grain the colour half left
behind. ponytail: add it as a second child of this same pass when one does.
Checked: `tsc --noEmit` clean; `denoise-check.mjs` (new) compiles NR_SKSL on
CanvasKit, asserts the engine still wires both children and no longer blurs the
draw, and renders the pass at five amounts against a flat pair — amount 0 is the
pixel exactly, amount 1 carries the neighbourhood's colour difference, and the
luma never moves at any of them; `highlight-knee-check.mjs`, `tone-base-check.mjs`
and `mask-wb-check.mjs` still green.
This commit is contained in:
@@ -745,6 +745,48 @@ vec4 main(vec2 xy) {
|
||||
// of the reference (0..1, the same units MASK's CLARITY uses on it).
|
||||
export const CLARITY_GAIN = 3.2;
|
||||
|
||||
// NOISE REDUCTION — thay_doi_thong_so_giong_lightroom.md §4.1, the colour half.
|
||||
// The eye is sensitive to a change in brightness and nearly blind to one in hue
|
||||
// at the same scale, so the knob is spent where it costs no detail: the CHROMA
|
||||
// comes from a blurred copy of the frame and the LUMA from the frame itself, and
|
||||
// a strand of hair comes back exactly where it was. The split is this file's own
|
||||
// lightness/chroma one, `rgb - luma`, the same the tone ramp's header describes.
|
||||
//
|
||||
// Until now the whole frame was blurred instead — `MakeBlur` on the draw, one
|
||||
// sigma over all three channels — which is the doc's own §4 warning ("Noise
|
||||
// Reduction sẽ làm nhòe toàn bộ chi tiết sợi tóc và vân da") written into the
|
||||
// engine: the knob could not take a colour speckle out without taking the
|
||||
// picture's edges with it.
|
||||
//
|
||||
// `amount` is the share of the blurred chroma: 0 leaves the pixel exactly as it
|
||||
// was and 1 hands it the neighbourhood's hue with its own brightness still on
|
||||
// it, so the two ends of the knob are the identity and the blur and nothing in
|
||||
// between moves a pixel's luma at all.
|
||||
//
|
||||
// The LUMA half of §4.1 (its bilateral filter) is deliberately not here: it is
|
||||
// the half that costs detail, and no frame has yet shown grain the chroma half
|
||||
// left behind. ponytail: add it as a second child of this same pass if one does.
|
||||
export const NR_SKSL = `
|
||||
uniform shader sharp;
|
||||
uniform shader blurred;
|
||||
uniform float amount;
|
||||
const float3 NR_LUM = vec3(0.2126, 0.7152, 0.0722);
|
||||
vec4 main(vec2 xy) {
|
||||
vec3 s = clamp(sharp.eval(xy).rgb, 0.0, 1.0);
|
||||
vec3 b = clamp(blurred.eval(xy).rgb, 0.0, 1.0);
|
||||
float ys = dot(s, NR_LUM);
|
||||
float yb = dot(b, NR_LUM);
|
||||
return vec4(clamp(vec3(ys) + mix(s - vec3(ys), b - vec3(yb), amount), 0.0, 1.0), 1.0);
|
||||
}
|
||||
`;
|
||||
|
||||
// How far the chroma filter reaches, as a fraction of the frame's width — the
|
||||
// doc's 3..5 pixels of a full-resolution frame, which is 0.4% of it, so a
|
||||
// preview and a file average the same share of the picture. The knob's own blur
|
||||
// was 0.6 of a pixel at NOISE REDUCTION 100, which is under the doc's patch and
|
||||
// under a colour speckle as well.
|
||||
export const NR_CHROMA_SPAN = 0.004;
|
||||
|
||||
// DEHAZE — raw_parameter_processing_gradient_mask_algorithms.md, section 3.2.
|
||||
// Haze is scattered light: it lifts the DARKEST channel of every patch, which is
|
||||
// the Dark Channel Prior. The dark channel is the MINIMUM of min(r,g,b)/A over
|
||||
|
||||
Reference in New Issue
Block a user