`ops/vignetting.rs` has carried a complete descriptor, polynomial, helper and test suite without an entry in `ops/`, so it was never in `chain()`. It reached no photograph and no panel, and `Attribute::Optics` was an empty category in consequence — filtered out of the tab strip for having no rows, by a chain that had never been given its only member. Declaring it needs the one thing the operation was written against and which did not exist. `wgsl_body` reads `radius`, and the module claimed "the composer publishes `radius` in the shader prologue for exactly this reason". It did not. `sample_source` now does, in both sampling branches, beside the `source_px` it already published for the same class of caller. It is corner-normalised there, which is the part that is easy to leave out. `p` spans ±0.5·aspect, so its length at the corner is 0.5·length(aspect) — about 0.901 on a 3:2 frame, not 1. Lensfun's polynomials are fitted against a corner radius of 1, so passing `length(p)` straight in evaluates every one of them short of where it was measured, by a factor that changes with the aspect ratio. It would have read as a correction that is simply too weak, which is indistinguishable from a bad profile. Both `lens.rs` and `framing.rs` asserted the normalisation `p` does not have; corrected. `order: 5` puts the correction ahead of the tonal stages, and the ordering is load-bearing rather than tidy. Recovering a corner means dividing by an attenuation below one — about two stops for a fast prime wide open — so run after the highlights have been rolled off and clipped, the lift has nowhere to go and the corners posterise instead of brightening. `layer_chain` now drops `Optics` as well as the neighbourhood operations. A local vignetting slider would have worked, which is what makes it worth excluding: `radius` measures from the centre of the whole photograph and a mask cannot move the optical axis, so it would lay a frame-centred radial ramp across the picture and multiply it by the mask. The existing exclusion covers operations that move and do nothing; this one covers an operation that moves and does something its name does not promise. The rule both share is now written down. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
41 lines
1.9 KiB
YAML
41 lines
1.9 KiB
YAML
# A hand-written node, and the first thing in `Attribute::Optics`.
|
|
#
|
|
# Written, tested and unreferenced until now: `src/ops/vignetting.rs` has
|
|
# existed with a full descriptor and a working polynomial, and without an entry
|
|
# here it was never in `chain()` — so it reached no photograph and no panel.
|
|
# This file is the whole of what was missing, which is the point of `ops/`
|
|
# being the one place the pipeline's order is written down.
|
|
id: vignetting
|
|
order: 5
|
|
|
|
attributes: [optics]
|
|
rust: Vignetting
|
|
|
|
why_rust: |
|
|
It carries a lens profile's `pa` coefficients, which are not parameters: they
|
|
come from the body and lens that took the photograph, not from the
|
|
photographer, and no `uniforms:` expression could produce them. The one
|
|
parameter that *is* theirs — the manual trim — is composed with `k1` in Rust,
|
|
because the profile and the trim have to reach the shader as a single
|
|
polynomial rather than as two the fragment would have to add up.
|
|
|
|
placement: |
|
|
First in the chain, ahead of white balance and exposure.
|
|
|
|
Vignetting is what the lens did to the light before the sensor measured it,
|
|
so undoing it belongs with reading the file rather than with editing the
|
|
picture — everything downstream is then working on the frame the lens would
|
|
have delivered had it been even.
|
|
|
|
The ordering is load-bearing rather than tidy. Correcting a corner means
|
|
*dividing* by an attenuation below one, which pushes those pixels up: a fast
|
|
prime wide open needs about two stops there. Run after the tonal stages, that
|
|
recovery happens once the highlights have already been rolled off and
|
|
clipped, so it lifts values that no longer have anywhere to go and the
|
|
corners posterise instead of brightening. Run here, the headroom to hold them
|
|
still exists (ARCH §5.2).
|
|
|
|
Before distortion and chromatic aberration in intent, though those are
|
|
`lens::Warp`s rather than nodes and compose ahead of the fetch, so no `order:`
|
|
relates the two.
|