# A hand-written node, and a neighbourhood one: the veil it removes is measured # from the pixels around the one it is writing, so it runs in the detail stage # rather than as a fragment in the fused pass. See `../src/detail.rs` for why # that stage exists and `README.md`'s "Nodes that read their neighbours" for # the contract. # # As with every `rust:` node, its descriptor, parameters and behaviour come # from the type; this file exists so that `ops/` remains the one place the # pipeline's order is written down. id: dehaze order: 125 rust: Dehaze why_rust: | Haze is defined by what the pixels around a pixel are doing, and the schema above describes a function of one colour — `wgsl:` is handed `c` and no coordinate, which is the wall the detail stage exists on the other side of. It declares `Affects::Detail` and returns five `DetailPass`es: four that erode the dark channel into a per-pixel veil, and one that inverts the scattering model with the transmission that veil implies. Nor is it four facts. The erosion is split into two exact stages per axis so that a patch 1% of the frame wide costs its square root in taps, and the split is arithmetic over the render size that has to be recomputed every frame. Stretching this schema to express it would produce a worse language than Rust, aimed at one caller. placement: | First among the compositional detail nodes: after noise reduction and capture sharpening, before clarity and texture. After noise reduction because dehaze divides by a transmission below one, so it amplifies whatever noise is in the veiled distance by exactly the factor it recovers the contrast by. Running it first would ask the denoiser to remove grain that dehaze had already multiplied — the same argument clarity records, and stronger here, because the amplification is largest in the low- contrast regions where noise is most visible. Before clarity and texture, and for the reason that orders those two against each other: coarse before fine. Dehaze acts on the widest structure in the frame, the veil that varies with distance, and clarity's base should be computed on the picture as the veil has left it rather than on a modelling that is about to be divided out. What this cannot honour, and it is worth writing down rather than leaving to be rediscovered: dehaze shifts colour. It subtracts a grey term and rescales, so it changes saturation everywhere the veil is thick, and the colour controls would ideally be correcting the picture that leaves here. They cannot be. The detail stage runs as a *group* after every point operation, because a neighbourhood pass is a separate dispatch reading a texture the fused pass has already finished writing — so an `order:` placing this node ahead of `vibrance` or `colour_mixer` would be a lie the chain cannot tell. Interleaving the two would mean splitting the fused pass in half around this one, which costs a second full-frame dispatch and intermediate on every edit in the catalogue, whether or not it uses dehaze at all.