Optional ReadonlycontrastContrast about a linear 0.5 pivot; defaults to 1. Finite, >= 0.
Optional ReadonlyexposureLinear-light multiplier; defaults to 1. Finite and >= 0 (§85).
ReadonlykindRequired, and the only value — the discriminant.
Optional Readonlysaturation0 greyscale … 1 unchanged … above oversaturates. Finite, >= 0.
§70's "color grading": exposure, contrast, and saturation over the source, in the order named here.
Every field is optional and defaults to
1— the identity value of its operation — so{ kind: "grade" }is a copy with three multiplies, and a grade that names one field leaves the other two exactly alone. See COLOR_GRADE_DEFAULTS.What the numbers mean, precisely
The pipeline is linear-light on the GPU backends (§60a), and these operate on the values as stored — there is no perceptual curve in this tier, because the output transform that would supply one is staged (see the module header). Stated as arithmetic, per colour channel, with
auntouched throughout:rgb *= exposure. A linear-light scale, so2is one stop brighter, not "twice as bright" perceptually.rgb = (rgb - 0.5) * contrast + 0.5. The pivot is0.5linear, which is not perceptual mid-grey; the pivot moves to mid-grey when §60a's transform lands, and that is a deliberate difference recorded here rather than a rounding of it.rgb = mix(vec3(luma), rgb, saturation), withlumathe Rec. 709 linear-light weighted sum (0.2126 R + 0.7152 G + 0.0722 B);0is greyscale,1is unchanged, above1oversaturates.Nothing is clamped by the effect: the destination surface's format does the clamping it does (
rgba8saturates), and a float target — when R-4's format union widens — will not. Alpha is never touched, so an effect pass over a target with a transparent background composites afterwards exactly as the source would have.