web: SHARPENING lifts only the edge it was pointed at, so a flat half of the frame keeps the grain it came with
SHARPENING was the doc's §4.2 kernel with the two parts of §4.2 missing from
it. `CLARITY_SKSL` evaluated the 3x3 unsharp mask with Mask = 1 on every pixel
and no coring at all, so a flat sky, a cheek and a noise speckle all took the
gain an eyelash took. That is `thay_doi_thong_so_giong_lightroom.md` §1 written
out as a bug: "khi Sharpen, ảnh nổi đầy sạn hạt cát" — the knob could not raise
the contrast of an edge without raising the noise of everything beside it, and
on a grainy frame the second effect won.
`SHARPEN_SKSL` is the doc's own line, `Image + Amount x HighPass x Mask`, with
the two terms it names:
- EDGE DETECTION: the Sobel magnitude G = sqrt(Gx^2 + Gy^2) on luminance, put
through the doc's soft threshold smoothstep(T, T + 0.1, G). Flat fields
read G = 0 and get Mask = 0 — the pixel is handed back untouched.
- DETAIL (halo coring): a high-pass under SHARPEN_CORE is a speckle, not a
detail, and is suppressed. The coring is soft (a ramp across the threshold,
not a cliff) so a detail sitting on it is not switched on and off from one
pixel to the next.
The HIGH-PASS is the doc's Radius, held at one image pixel — 0.7-0.9px on a
Retina panel — and it is a LUMINANCE high-pass carried by all three channels.
A per-channel kernel sharpens a red edge against a green one and draws a colour
fringe down every contour; the file's own §3 rule is to keep R/L, G/L and B/L
where they were.
`scripts/sharpen-check.mjs` pins the three properties the old kernel could not
have: a flat field and a field of grain come back unchanged, a step below the
threshold comes back unchanged, and a hard step moves apart on both sides while
the flat halves beside it stay put. `CLARITY_SKSL` and `clarityUniforms` are
gone with it, and the header note that said CanvasKit had two convolution steps
to replace now says the one it has.
Checked: node scripts/sharpen-check.mjs; node scripts/denoise-check.mjs; node
scripts/tone-base-check.mjs; node scripts/highlight-knee-check.mjs; node
scripts/auto-tone-check.mjs; node scripts/half-check.mjs; node
scripts/mask-wb-check.mjs; node scripts/preview-match-check.mjs; node
scripts/raw-develop-check.mjs; node scripts/white-level-check.mjs; node
scripts/wb-table-check.mjs; npx tsc --noEmit.
This commit is contained in:
@@ -607,27 +607,76 @@ export function glowUniformArray(): number[] {
|
||||
// NAME (a flat array is only valid for the JS makeShaderWithChildren API).
|
||||
export const GLOW_UNIFORMS = { t0: GLOW_T0, t1: GLOW_T1 };
|
||||
|
||||
// CLARITY (positive): unsharp 3x3 with epsilon 0 — the kernel export pass 4
|
||||
// builds with MakeMatrixConvolution, re-expressed as a plain shader because RN
|
||||
// Skia 2.6 exposes no convolution image filter to the declarative JSX writer.
|
||||
// `px` is one ORIGINAL image pixel expressed in the caller's canvas units, so
|
||||
// the preview, the camera worklet and the file all sharpen at the same radius.
|
||||
export const CLARITY_SKSL = `
|
||||
// SHARPENING — thay_doi_thong_so_giong_lightroom.md §4.2, all four of its
|
||||
// parameters in the one knob the panel has. The doc's SHARPENING is not an
|
||||
// unsharp mask: it is an unsharp mask MASKED BY AN EDGE DETECTOR, which is what
|
||||
// keeps a flat sky and a cheek from gaining grain, and CORED, which is what
|
||||
// keeps a noise speckle from being amplified into a white dot.
|
||||
//
|
||||
// Image_sharp = Image + Amount x HighPass x Mask
|
||||
//
|
||||
// EDGE DETECTION is the doc's Sobel magnitude G = sqrt(Gx² + Gy²) on luminance,
|
||||
// put through its soft threshold Mask = smoothstep(T, T + 0.1, G). This pass was
|
||||
// the same 3x3 kernel with Mask = 1 everywhere and no coring, so every
|
||||
// low-contrast pixel — every pixel of a flat sky, every pore of a face — took
|
||||
// the same gain an eyelash did, and a rising SHARPENING raised the frame's noise
|
||||
// with it. That is the doc's §1 complaint word for word ("khi Sharpen, ảnh nổi
|
||||
// đầy sạn hạt cát").
|
||||
//
|
||||
// The HIGH-PASS is the doc's Radius, held at one image pixel (its 0.7-0.9px for
|
||||
// a Retina panel), and it is a LUMINANCE high-pass carried by all three channels
|
||||
// rather than a per-channel one: a per-channel kernel sharpens a red edge
|
||||
// against a green one and draws a colour fringe down every contour.
|
||||
//
|
||||
// SHARPEN_CORE is the doc's Detail. Coring is SOFT (a ramp over the threshold,
|
||||
// not a cliff), so a detail crossing it is not switched on and off from one
|
||||
// pixel to the next.
|
||||
//
|
||||
// `amount` is signed the way the knob is: the negative side is the caller's
|
||||
// blur and never reaches this shader. `px` is one ORIGINAL image pixel in the
|
||||
// caller's canvas units, so the preview, the camera worklet and the file all
|
||||
// sharpen at the same radius. The three constants are the doc's own shape; the
|
||||
// doc leaves their value to the panel, and this panel has one SHARPENING knob,
|
||||
// so they are fixed here. No frame has yet asked for a second one.
|
||||
export const SHARPEN_CORE = 0.02;
|
||||
export const SHARPEN_MASK_LO = 0.1;
|
||||
export const SHARPEN_MASK_HI = SHARPEN_MASK_LO + 0.1;
|
||||
|
||||
export const SHARPEN_SKSL = `
|
||||
uniform shader src;
|
||||
uniform float a;
|
||||
uniform float2 px;
|
||||
const float3 SHARPEN_LUM = vec3(0.2126, 0.7152, 0.0722);
|
||||
const float SHARPEN_CORE = ${SHARPEN_CORE};
|
||||
const float SHARPEN_MASK_LO = ${SHARPEN_MASK_LO};
|
||||
const float SHARPEN_MASK_HI = ${SHARPEN_MASK_HI};
|
||||
float sharpenLum(vec2 p) {
|
||||
return dot(clamp(src.eval(p).rgb, 0.0, 1.0), SHARPEN_LUM);
|
||||
}
|
||||
vec4 main(vec2 xy) {
|
||||
vec4 c = src.eval(xy);
|
||||
vec4 s = src.eval(xy + float2(0.0, -px.y))
|
||||
+ src.eval(xy + float2(0.0, px.y))
|
||||
+ src.eval(xy + float2(-px.x, 0.0))
|
||||
+ src.eval(xy + float2( px.x, 0.0));
|
||||
return vec4(clamp(c.rgb * (1.0 + 4.0 * a) - a * s.rgb, 0.0, 1.0), c.a);
|
||||
vec2 dx = float2(px.x, 0.0);
|
||||
vec2 dy = float2(0.0, px.y);
|
||||
float yc = sharpenLum(xy);
|
||||
float yl = sharpenLum(xy - dx);
|
||||
float yr = sharpenLum(xy + dx);
|
||||
float yt = sharpenLum(xy - dy);
|
||||
float yb = sharpenLum(xy + dy);
|
||||
float ytl = sharpenLum(xy - dx - dy);
|
||||
float ytr = sharpenLum(xy + dx - dy);
|
||||
float ybl = sharpenLum(xy - dx + dy);
|
||||
float ybr = sharpenLum(xy + dx + dy);
|
||||
float gx = (ytl + 2.0 * yl + ybl) - (ytr + 2.0 * yr + ybr);
|
||||
float gy = (ytl + 2.0 * yt + ytr) - (ybl + 2.0 * yb + ybr);
|
||||
float mask = smoothstep(SHARPEN_MASK_LO, SHARPEN_MASK_HI, sqrt(gx * gx + gy * gy));
|
||||
float hp = yc - 0.25 * (yl + yr + yt + yb);
|
||||
hp *= smoothstep(SHARPEN_CORE, 2.0 * SHARPEN_CORE, abs(hp));
|
||||
vec4 c = clamp(src.eval(xy), 0.0, 1.0);
|
||||
return vec4(clamp(c.rgb + a * hp * mask, 0.0, 1.0), c.a);
|
||||
}
|
||||
`;
|
||||
|
||||
// Named uniforms for <Shader uniforms>, same names as CLARITY_SKSL declares.
|
||||
export function clarityUniforms(a: number, pxX: number, pxY: number) {
|
||||
// Named uniforms for <Shader uniforms>, same names as SHARPEN_SKSL declares.
|
||||
export function sharpenUniforms(a: number, pxX: number, pxY: number) {
|
||||
return { a, px: [pxX, pxY] };
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user