Skip to main content

Module filters

Module filters 

Source
Expand description

Filter wire types, built-in filters, and the filter registry: the layer-based filter style chain.

This module owns the filter wire format and registry the way crate::canvas/crate::animations own theirs: protocol/ reconcile/ui_map reference these types by module path, never the reverse. On the wire a filter is one {"name", "params"} object or an ordered array of them; params stays an untyped JSON map at this layer — typed validation happens later against the registry, not during decode.

Decoding follows the protocol’s warn-don’t-abort convention (see Length’s custom Deserialize): a malformed value emits a decode_warn and the whole chain degrades to empty, never failing the containing Style’s deserialization.

The typed layer below the wire — ReactFilter, the built-in param structs, and FilterRegistry — turns a FilterUse into ResolvedFilterPasses: deserialize the raw params (strict: deny-unknown-fields), pack them into a Vec4 uniform array with a ParamSlot layout (no-straddle rule: a slot never crosses a Vec4 boundary), and pick the pass shader. The thirteen built-ins register via register_builtin_filters; custom filters are #[react_filter] structs registered with add_react_filter.

The module also owns the runtime write path of a chain. resolve_chains turns a promoted root’s FilterInput into a ResolvedFilterChain component — resolving each entry against the registry (invalid entries skip with a filterParams warning), rewriting Length slots to physical px, stamping ResolvedFilterPass::wire_index, and summing the chain outset. The interpolation primitives (lerp_packed_params, lerp_angle) blend packed param arrays layout-aware for the easing paths. Exactly three systems write ResolvedFilterChain and bump its version counter: the resolver’s snap, the transition filter channel’s whole-value ease (transition.rs, planned by [plan_filter_ease] with identity-padding for chain extensions), and the animation per-param filter[<i>].<param> bindings (animations) — see ResolvedFilterChain::version for the writer registry and precedence.

File map: wire (the wire format + warn-don’t-abort decode), params (packing layout, caps, param value types, interpolation), registry (the ReactFilter trait, resolved passes, the registry), builtin (the thirteen built-ins), morph (the morphFilter style’s wire type + resolver instance + runtime state), transition (whole-value filter transition planning), resolve (the chain resolve system). Submodules are private; everything is re-exported here, so crate::filters::X is the one path.

Structs§

BackdropInput
The wire backdropFilter chain of a node, mirrored off the applied style by crate::ui_map::apply_style_masked’s BACKDROP arm — present iff the style carries a non-empty chain. Same contract as FilterInput: the style apply owns writes (the applied style may be hover/press/focus-merged), the resolver only reads.
BloomParams
bloom: glow where bright areas bleed light — a bright-pass thresholded at threshold, blurred by radius, and added back onto the original scaled by intensity. { name: "bloom" } with no params is a visible glow (shorthand-default convention); the true identity is intensity: 0.
BlurParams
blur: separable Gaussian blur. radius defaults to 0 — CSS blur() with the value omitted means 0 (identity).
BrightnessParams
brightness: amount defaults to 1.0 — CSS brightness() with the value omitted means 1 (identity).
ChromaticAberrationParams
chromaticAberration: directional color fringing — the R channel’s image shifts offset px along angle (degrees, clockwise from +X in screen space; bare number = degrees, "0.25turn" etc. accepted), B the same distance opposite, G stays put. The split is uniform across the layer. { name: "chromaticAberration" } with no params is visible fringing (shorthand-default convention); the true identity is offset: 0.
ContrastParams
contrast: amount defaults to 1.0 — CSS contrast() with the value omitted means 1 (identity).
CrossfadeParams
crossfade: a noise-staggered two-input blend. A smooth fbm value-noise field gives each pixel a stagger offset (n * spread) so blob-shaped regions cross-dissolve earlier than others; each pixel then fades linearly over a softness-wide window of the progress range. spread: 0 is the plain uniform mix(from, to, progress) (bit-exact — the shader fast-paths it), and noise is ON by default.
FilterChain
An ordered filter chain. Decodes from a single {"name", "params"} object (a 1-element chain) or an array of them (applied in order); any malformed entry — or non-object/array garbage — warns and degrades the whole value to an empty chain.
FilterColor
A color filter parameter: linear (shader-ready) straight-alpha RGBA.
FilterInput
The wire filter chain of a node, mirrored off the applied style by the apply path (crate::ui_map::apply_style_masked’s FILTER arm) — present iff the style carries a non-empty chain. The thin input side of the resolve_chains system, mirroring the crate::transition::TransitionInput pattern: the style apply owns writes, the resolver only reads. The applied style may be a hover/press/focus- merged one (the field is overlay), so an interaction flip re-stamps the merged chain here.
FilterRegistration
One registered filter: everything the chain resolver needs, baked into AssetServer-free fn pointers at registration time (shader loading stays lazy — the pointers receive the &AssetServer).
FilterRegistry
Known filters, keyed by wire name. Populated by register_builtin_filters; consumed by resolve_chains.
FilterUse
One filter invocation in a chain: a registry name plus its raw, untyped parameter map (empty when the wire object has no params or params is null).
GradientMapParams
gradientMap: recolor the layer’s pixels with a multi-stop linear gradient, keeping alpha — gradient text (wrap the <text> in a <node>), gradient-tinted icons. angle follows backgroundGradient’s convention (bare number = degrees, 0 = to top, clockwise); the gradient line spans the node’s border box (aspect-correct CSS math — the outset ring of a chained blur/outline does NOT stretch it, via the prelude’s content_uv). amount mixes the original color toward the gradient (a stop’s alpha scales the mix locally; source alpha is always kept); the true identity is amount: 0. Gradient interpolation is linear-RGB (the packed space — a v1 divergence from backgroundGradient’s oklab default).
GradientMapStop
One gradient stop: a CSS color and an optional position as a fraction 0..1 along the gradient line.
GrayscaleParams
grayscale: amount defaults to 1.0 — CSS grayscale() with the value omitted means 1 (full effect; identity is 0), so a bare {name:"grayscale"} applies the effect.
HueRotateParams
hueRotate: angle defaults to 0deg — CSS hue-rotate() with the value omitted means 0deg (identity). Packs radians at params[1].z of the shared color-matrix packing.
InvertParams
invert: amount defaults to 1.0 — CSS invert() with the value omitted means 1 (full effect; identity is 0).
LinearWipeParams
linearWipe: the “to” image sweeps in along angle (degrees, clockwise from +X in screen space, like every angle in the system; 0 wipes left-to-right, 90 top-to-bottom), with a softness-px eased band around the wipe edge. The sweep is extended by the band at both ends so progress 0 is fully “from” and progress 1 exactly the live content (the identity contract).
MorphFilter
The decoded morphFilter style value: the morph identity key (a string or number — a change is what triggers the morph) plus the two-input filter to blend with, as a regular registry FilterUse.
MorphInput
The wire morphFilter of a node, mirrored off the applied style by crate::ui_map::apply_style_masked’s MORPH arm — present iff the style carries the field. Same contract as FilterInput: the style apply owns writes, the resolver only reads. The key rides outside the chain: the resolver never looks at it (a key-only change re-resolves to identical output, version-quiet) — retarget detection is the morph transition channel’s job.
MorphState
The live state of a node’s morph transition, written by the morph channel in crate::transition (retarget on key change, per-frame progress) and read by render extraction (the freeze request + blend pass). Inserted on first activation; removed on demotion.
OutlineParams
outline: paint a color ring of width px around the content’s alpha silhouette, UNDER the content (source-over) — text outlines (wrap the <text> in a <node>), sticker-style icon rings. softness feathers the ring’s outer edge over that many extra px, doubling as a glow. The outline follows whatever the chain has produced so far: [gradientMap, outline] outlines the recolored glyphs, [blur, outline] the blurred silhouette. { name: "outline" } is a crisp 2px black outline (shorthand-default convention); the true identity is width: 0, softness: 0.
ParamSlot
Where one named parameter lands in a pass’s packed Vec4 array: vec is the Vec4 index, comp the starting component within it, len how many consecutive components the param spans.
PixelizeParams
pixelize: both images sample the center of a shared mosaic cell whose size peaks mid-transition, over a plain progress crossfade. Upstream uniforms map 1:1 — squaresMin (ivec2, the cell count when the mosaic is coarsest) and steps (int, quantizes the cell-size ramp; <= 0 disables the stepping for a continuous ramp).
ResolvedBackdropChain
A node’s fully resolved backdropFilter chain — a newtype over ResolvedFilterChain so extract, transitions, animations, and devtools reuse the inner type via .0. Attached to promoted roots by the backdrop resolve_chains instance; absent when the chain has no valid entries. Removed on demotion by evaluate_layer_promotions’s cleanup (like the content chain).
ResolvedFilterChain
A node’s fully resolved filter chain, attached to promoted layer roots by resolve_chains. Absent on a promoted root whose chain has no valid entries (pure capture/composite — no filter machinery).
ResolvedFilterPass
One resolved render pass of a filter chain.
ResolvedMorphChain
A node’s fully resolved morph filter — a newtype over ResolvedFilterChain (always exactly one pass, per MorphInput::validate_entry) so extract, animations, and devtools reuse the inner type via .0. Attached to promoted roots by the morph resolve_chains instance; absent when the entry is invalid (the morph degrades to a snap). Removed on demotion by evaluate_layer_promotions’s cleanup.
SaturateParams
saturate: amount defaults to 1.0 — CSS saturate() with the value omitted means 1 (identity).
SepiaParams
sepia: amount defaults to 1.0 — CSS sepia() with the value omitted means 1 (full effect; identity is 0).
ShadowParams
shadow: a CSS-drop-shadow-style shadow — the content’s alpha silhouette, tinted color, shifted by offsetX/offsetY px (positive = right/down), Gaussian-blurred by spread, composited UNDER the content. { name: "shadow" } with no params is a soft black shadow below (shorthand-default convention); the true identity is color: "transparent".

Constants§

MAX_CHAIN_LEN
Longest decodable chain. ResolvedFilterPass::wire_index is a u8, so only entry indices 0..=u8::MAX are addressable; a longer chain is nonsense input and degrades whole-value like any other malformed chain.
MAX_FILTER_OUTSET_PX
Defensive cap on a chain’s summed outset, physical px per side. Far beyond any sane blur (quality is bounded well before this — see blur.wgsl’s MAX_HALF note), it bounds the inflation math: 2 * outset adds at most 2048 texels to the capture, leaving real headroom under wgpu’s default 8192 max_texture_dimension_2d for the content itself. (Texture-limit safety proper is the allocator’s concern, not this cap’s.)
MAX_FILTER_PARAM_VECS
Cap on the packed Vec4 array per pass — the fixed-size uniform array the filter shaders declare.
MAX_GRADIENT_STOPS
The most stops one gradientMap use can carry — the packing budget: 6 color vec4s + 6 positions + angle + amount is exactly MAX_FILTER_PARAM_VECS.
MORPH_MAX_USER_PARAM_VECS
User params of a morph filter may occupy at most this many Vec4 slots: the last two of MAX_FILTER_PARAM_VECS (= 8) are engine-reserved on a morph pass — params[7].x carries the eased progress (see the prelude’s morph_progress; params[6] is reserved-unused spare).

Traits§

ChainInput
Input side of one resolve_chains instance: which wire-chain component feeds it, its diag kinds, and per-instance semantics. Implemented by FilterInput (the content filter chain) and BackdropInput (backdropFilter).
ReactFilter
A typed, named filter: how its params deserialize (strict — built-ins use #[serde(deny_unknown_fields)]), pack into shader uniforms, and resolve into render passes.
ReactMorphFilter
Marker for two-input morph filters (morphFilter names). Implemented by #[react_morph_filter] (and the built-in morphs); the bound on add_react_morph_filter gives compile-time guidance — registration truth rides ReactFilter::IS_MORPH.
ResolvedChain
Output side of one resolve_chains instance — a component wrapping (or being) a ResolvedFilterChain. The newtype projection keeps every downstream consumer (extract, transitions, animations, devtools) on the one inner type.

Functions§

length_logical_px
A Length filter param’s logical-px value. Only Px has a fixed logical size here — percent/viewport units have no basis for a filter param — so any other unit is rejected with a message naming it, instead of silently resolving to 0.0. Bare wire numbers decode as Px and stay accepted.
lerp_angle
Shortest-arc interpolation between two angles in radians, for packed ValueKind::Angle filter params.
lerp_packed_params
Interpolate two packed param arrays of the same layout, slot-by-slot: the layout (not the raw components) decides how each param blends.
quantize_outset
Quantize a physical-px outset up to the next multiple of 16 so an animated radius grows a layer texture in coarse steps instead of reallocating every frame.
register_builtin_filters
Register the sixteen built-in filters. Called by ReactUiPlugin::build. Deliberately AssetServer-free: shader loads happen lazily inside each entry’s resolve.
resolve_chains
Turn each promoted root’s wire chain input I into a packed resolved chain R. Two instances run in Update after the interaction restyle (the last input writer this frame) and before the transition/animation appliers — both write onto the resolved chain: the content instance (FilterInputResolvedFilterChain, the filter style) and the backdrop instance (BackdropInputResolvedBackdropChain, the backdropFilter style — see crate::filters::backdrop).
resolve_single_pass
The default single-pass resolve body: ReactFilter::pack + the param-vec cap check + one pass tagged wire_index: 0.