tiny_skia/shaders/
gradient.rs1use alloc::vec::Vec;
8
9use tiny_skia_path::{NormalizedF32, Scalar};
10
11use crate::{Color, ColorSpace, SpreadMode, Transform};
12
13use crate::pipeline::RasterPipelineBuilder;
14use crate::pipeline::{self, EvenlySpaced2StopGradientCtx, GradientColor, GradientCtx};
15
16pub const DEGENERATE_THRESHOLD: f32 = 1.0 / (1 << 15) as f32;
19
20#[allow(missing_docs)]
22#[derive(Clone, Copy, PartialEq, Debug)]
23pub struct GradientStop {
24 pub(crate) position: NormalizedF32,
25 pub(crate) color: Color,
26}
27
28impl GradientStop {
29 pub fn new(position: f32, color: Color) -> Self {
33 GradientStop {
34 position: NormalizedF32::new_clamped(position),
35 color,
36 }
37 }
38}
39
40#[derive(Clone, PartialEq, Debug)]
41pub struct Gradient {
42 stops: Vec<GradientStop>,
43 tile_mode: SpreadMode,
44 pub(crate) transform: Transform,
45 points_to_unit: Transform,
46 pub(crate) colors_are_opaque: bool,
47 has_uniform_stops: bool,
48}
49
50impl Gradient {
51 pub fn new(
52 mut stops: Vec<GradientStop>,
53 tile_mode: SpreadMode,
54 transform: Transform,
55 points_to_unit: Transform,
56 ) -> Self {
57 debug_assert!(stops.len() > 1);
58
59 let dummy_first = stops[0].position.get() != 0.0;
65 let dummy_last = stops[stops.len() - 1].position.get() != 1.0;
66
67 if dummy_first {
69 stops.insert(0, GradientStop::new(0.0, stops[0].color));
70 }
71
72 if dummy_last {
73 stops.push(GradientStop::new(1.0, stops[stops.len() - 1].color));
74 }
75
76 let colors_are_opaque = stops.iter().all(|p| p.color.is_opaque());
77
78 let start_index = if dummy_first { 0 } else { 1 };
80 let mut prev = 0.0;
81 let mut has_uniform_stops = true;
82 let uniform_step = stops[start_index].position.get() - prev;
83 for i in start_index..stops.len() {
84 let curr = if i + 1 == stops.len() {
85 1.0
87 } else {
88 stops[i].position.get().bound(prev, 1.0)
89 };
90
91 has_uniform_stops &= uniform_step.is_nearly_equal(curr - prev);
92 stops[i].position = NormalizedF32::new_clamped(curr);
93 prev = curr;
94 }
95
96 Gradient {
97 stops,
98 tile_mode,
99 transform,
100 points_to_unit,
101 colors_are_opaque,
102 has_uniform_stops,
103 }
104 }
105
106 pub fn push_stages(
107 &self,
108 p: &mut RasterPipelineBuilder,
109 cs: ColorSpace,
110 push_stages_pre: &dyn Fn(&mut RasterPipelineBuilder),
111 push_stages_post: &dyn Fn(&mut RasterPipelineBuilder),
112 ) -> bool {
113 p.push(pipeline::Stage::SeedShader);
114
115 let ts = match self.transform.invert() {
116 Some(v) => v,
117 None => {
118 log::warn!("failed to invert a gradient transform. Nothing will be rendered");
119 return false;
120 }
121 };
122 let ts = ts.post_concat(self.points_to_unit);
123 p.push_transform(ts);
124
125 push_stages_pre(p);
126
127 match self.tile_mode {
128 SpreadMode::Reflect => {
129 p.push(pipeline::Stage::ReflectX1);
130 }
131 SpreadMode::Repeat => {
132 p.push(pipeline::Stage::RepeatX1);
133 }
134 SpreadMode::Pad => {
135 if self.has_uniform_stops {
136 p.push(pipeline::Stage::PadX1);
141 }
142 }
143 }
144
145 if self.stops.len() == 2 {
147 debug_assert!(self.has_uniform_stops);
148
149 let c0 = cs.expand_color(self.stops[0].color);
150 let c1 = cs.expand_color(self.stops[1].color);
151
152 p.ctx.evenly_spaced_2_stop_gradient = EvenlySpaced2StopGradientCtx {
153 factor: GradientColor::new(
154 c1.red() - c0.red(),
155 c1.green() - c0.green(),
156 c1.blue() - c0.blue(),
157 c1.alpha() - c0.alpha(),
158 ),
159 bias: GradientColor::from(c0),
160 };
161
162 p.push(pipeline::Stage::EvenlySpaced2StopGradient);
163 } else {
164 let mut ctx = GradientCtx::default();
168
169 ctx.factors.reserve((self.stops.len() + 1).max(16));
175 ctx.biases.reserve((self.stops.len() + 1).max(16));
176
177 ctx.t_values.reserve(self.stops.len() + 1);
178
179 let (first_stop, last_stop) = if self.stops.len() > 2 {
182 let first = if self.stops[0].color != self.stops[1].color {
183 0
184 } else {
185 1
186 };
187
188 let len = self.stops.len();
189 let last = if self.stops[len - 2].color != self.stops[len - 1].color {
190 len - 1
191 } else {
192 len - 2
193 };
194 (first, last)
195 } else {
196 (0, 1)
197 };
198
199 let mut t_l = self.stops[first_stop].position.get();
200 let mut c_l = GradientColor::from(cs.expand_color(self.stops[first_stop].color));
201 ctx.push_const_color(c_l);
202 ctx.t_values.push(NormalizedF32::ZERO);
203 for i in first_stop..last_stop {
205 let t_r = self.stops[i + 1].position.get();
206 let c_r = GradientColor::from(cs.expand_color(self.stops[i + 1].color));
207 debug_assert!(t_l <= t_r);
208 if t_l < t_r {
209 let f = GradientColor::new(
212 (c_r.r - c_l.r) / (t_r - t_l),
213 (c_r.g - c_l.g) / (t_r - t_l),
214 (c_r.b - c_l.b) / (t_r - t_l),
215 (c_r.a - c_l.a) / (t_r - t_l),
216 );
217 ctx.factors.push(f);
218
219 ctx.biases.push(GradientColor::new(
220 c_l.r - f.r * t_l,
221 c_l.g - f.g * t_l,
222 c_l.b - f.b * t_l,
223 c_l.a - f.a * t_l,
224 ));
225
226 ctx.t_values.push(NormalizedF32::new_clamped(t_l));
227 }
228
229 t_l = t_r;
230 c_l = c_r;
231 }
232
233 ctx.push_const_color(c_l);
234 ctx.t_values.push(NormalizedF32::new_clamped(t_l));
235
236 ctx.len = ctx.factors.len();
237
238 debug_assert_eq!(ctx.factors.len(), ctx.t_values.len());
240 debug_assert_eq!(ctx.biases.len(), ctx.t_values.len());
241
242 while ctx.factors.len() < 16 {
244 ctx.factors.push(GradientColor::default());
245 ctx.biases.push(GradientColor::default());
246 }
247
248 p.push(pipeline::Stage::Gradient);
249 p.ctx.gradient = ctx;
250 }
251
252 if !self.colors_are_opaque {
253 p.push(pipeline::Stage::Premultiply);
254 }
255
256 push_stages_post(p);
257
258 true
259 }
260
261 pub fn apply_opacity(&mut self, opacity: f32) {
262 for stop in &mut self.stops {
263 stop.color.apply_opacity(opacity);
264 }
265
266 self.colors_are_opaque = self.stops.iter().all(|p| p.color.is_opaque());
267 }
268}