Connect the lens vignetting correction to the pipeline
`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>
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user