Skip to main content

valo_dl/
shader.rs

1use valo_geometry::{Color, Matrix, Point};
2
3/// `Shader` determines the color painted at each point of a drawing operation.
4///
5/// Shader coordinates begin in the draw's local coordinate space, so shaders
6/// follow the same transforms as their geometry.
7// Serialize ONLY, now that a pattern can hold an `Image`: the dump records a
8// texture's identity, and no deserializer can turn that back into a live GPU
9// handle. Same call `Path` already makes.
10#[derive(Clone, Debug, PartialEq)]
11#[cfg_attr(feature = "serde", derive(serde::Serialize))]
12pub enum Shader {
13    /// `Linear` interpolates colors along the line from `start` to `end`.
14    Linear {
15        /// `start` is the zero-offset point in local coordinates.
16        start: Point,
17        /// `end` is the one-offset point in local coordinates.
18        end: Point,
19        /// `stops` defines the colors along the gradient.
20        stops: Vec<GradientStop>,
21        /// `spread` controls colors outside the zero-to-one span.
22        spread: SpreadMode,
23        /// `local` transforms the shader independently of the drawn geometry.
24        ///
25        /// Use [`Matrix::IDENTITY`] when no additional transform is needed.
26        local: Matrix,
27    },
28    /// `Radial` interpolates between a start circle and an end circle.
29    Radial {
30        /// `center` is the end circle's center in local coordinates.
31        center: Point,
32        /// `radius` is the end circle's radius in local coordinates.
33        radius: f32,
34        /// `stops` defines the colors along the gradient.
35        stops: Vec<GradientStop>,
36        /// `spread` controls colors outside the zero-to-one span.
37        spread: SpreadMode,
38        /// `focus` is the optional start circle.
39        ///
40        /// `None` starts at a zero-radius circle centered on `center`.
41        focus: Option<FocalCircle>,
42        /// `local` transforms the shader independently of the drawn geometry.
43        local: Matrix,
44    },
45    /// `Sweep` interpolates colors around one full clockwise turn.
46    Sweep {
47        /// `center` is the sweep origin in local coordinates.
48        center: Point,
49        /// `start_angle` is the zero-offset angle in radians clockwise from +x.
50        start_angle: f32,
51        /// `stops` defines the colors around the sweep.
52        stops: Vec<GradientStop>,
53        /// `local` transforms the shader independently of the drawn geometry.
54        local: Matrix,
55    },
56    /// `Image` samples an image across the drawn geometry.
57    Image {
58        /// `image` supplies the sampled pixels.
59        image: crate::Image,
60        /// `sampling` controls filtering, mipmaps, and tiling.
61        sampling: crate::Sampling,
62        /// `local` transforms the image pattern independently of the geometry.
63        local: Matrix,
64    },
65}
66
67/// `SpreadMode` controls a gradient outside its zero-to-one span.
68#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
69#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
70pub enum SpreadMode {
71    /// `Pad` extends the nearest edge color.
72    #[default]
73    Pad,
74    /// `Repeat` repeats the gradient in the same direction.
75    Repeat,
76    /// `Reflect` repeats the gradient with alternating direction.
77    Reflect,
78}
79
80/// `GradientStop` assigns a color to one position along a gradient.
81#[derive(Clone, Copy, Debug, PartialEq)]
82#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
83pub struct GradientStop {
84    /// `offset` is the position from zero to one.
85    ///
86    /// Supply stops in nondecreasing offset order.
87    pub offset: f32,
88    /// `color` is the straight-alpha sRGB color at this offset.
89    pub color: Color,
90}
91
92/// `FocalCircle` defines the start circle of a radial gradient.
93#[derive(Clone, Copy, Debug, PartialEq)]
94#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
95pub struct FocalCircle {
96    /// `center` is the start circle's center in local coordinates.
97    pub center: Point,
98    /// `radius` is the start circle's radius.
99    ///
100    /// Zero represents a focal point.
101    pub radius: f32,
102}
103
104impl FocalCircle {
105    /// `point` creates a zero-radius focal circle.
106    pub fn point(center: Point) -> Self {
107        Self {
108            center,
109            radius: 0.0,
110        }
111    }
112}
113
114/// `MAX_GRADIENT_STOPS` is the largest gradient stored directly in uniforms.
115///
116/// Gradients with more stops use a cached texture ramp.
117pub const MAX_GRADIENT_STOPS: usize = 8;
118
119impl Shader {
120    /// `stops` returns this gradient's stops or an empty slice for image shaders.
121    pub fn stops(&self) -> &[GradientStop] {
122        match self {
123            Shader::Linear { stops, .. }
124            | Shader::Radial { stops, .. }
125            | Shader::Sweep { stops, .. } => stops,
126            Shader::Image { .. } => &[],
127        }
128    }
129
130    /// `fold_color_filter` applies a color filter directly to gradient stops.
131    ///
132    /// It returns `false` for image shaders, whose colors must be filtered
133    /// during sampling.
134    pub fn fold_color_filter(&mut self, filter: &crate::ColorFilter) -> bool {
135        let stops = match self {
136            Shader::Linear { stops, .. }
137            | Shader::Radial { stops, .. }
138            | Shader::Sweep { stops, .. } => stops,
139            // A pattern's colours live in texels; the image fragment applies
140            // the filter as it samples.
141            Shader::Image { .. } => return false,
142        };
143        let mut folded = Vec::with_capacity(stops.len());
144        for stop in stops.iter() {
145            match filter.folded_into(stop.color) {
146                Some(color) => folded.push(GradientStop { color, ..*stop }),
147                None => return false,
148            }
149        }
150        *stops = folded;
151        true
152    }
153
154    /// `linear` creates a two-color padded linear gradient.
155    pub fn linear(start: Point, end: Point, from: Color, to: Color) -> Self {
156        Shader::Linear {
157            start,
158            end,
159            stops: vec![
160                GradientStop {
161                    offset: 0.0,
162                    color: from,
163                },
164                GradientStop {
165                    offset: 1.0,
166                    color: to,
167                },
168            ],
169            spread: SpreadMode::Pad,
170            local: Matrix::IDENTITY,
171        }
172    }
173}