1 | // Copyright 2006 The Android Open Source Project |
2 | // Copyright 2020 Yevhenii Reizner |
3 | // |
4 | // Use of this source code is governed by a BSD-style license that can be |
5 | // found in the LICENSE file. |
6 | |
7 | use alloc::vec::Vec; |
8 | |
9 | use tiny_skia_path::{NormalizedF32, Scalar}; |
10 | |
11 | use crate::{Color, SpreadMode, Transform}; |
12 | |
13 | use crate::pipeline::RasterPipelineBuilder; |
14 | use crate::pipeline::{self, EvenlySpaced2StopGradientCtx, GradientColor, GradientCtx}; |
15 | |
16 | // The default SCALAR_NEARLY_ZERO threshold of .0024 is too big and causes regressions for svg |
17 | // gradients defined in the wild. |
18 | pub const DEGENERATE_THRESHOLD: f32 = 1.0 / (1 << 15) as f32; |
19 | |
20 | /// A gradient point. |
21 | #[allow (missing_docs)] |
22 | #[derive (Clone, Copy, PartialEq, Debug)] |
23 | pub struct GradientStop { |
24 | pub(crate) position: NormalizedF32, |
25 | pub(crate) color: Color, |
26 | } |
27 | |
28 | impl GradientStop { |
29 | /// Creates a new gradient point. |
30 | /// |
31 | /// `position` will be clamped to a 0..=1 range. |
32 | 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)] |
41 | pub 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 | |
50 | impl 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 | // Note: we let the caller skip the first and/or last position. |
60 | // i.e. pos[0] = 0.3, pos[1] = 0.7 |
61 | // In these cases, we insert dummy entries to ensure that the final data |
62 | // will be bracketed by [0, 1]. |
63 | // i.e. our_pos[0] = 0, our_pos[1] = 0.3, our_pos[2] = 0.7, our_pos[3] = 1 |
64 | let dummy_first = stops[0].position.get() != 0.0; |
65 | let dummy_last = stops[stops.len() - 1].position.get() != 1.0; |
66 | |
67 | // Now copy over the colors, adding the dummies as needed. |
68 | 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 | // Pin the last value to 1.0, and make sure positions are monotonic. |
79 | 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 | // The last one must be zero. |
86 | 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 | push_stages_pre: &dyn Fn(&mut RasterPipelineBuilder), |
110 | push_stages_post: &dyn Fn(&mut RasterPipelineBuilder), |
111 | ) -> bool { |
112 | p.push(pipeline::Stage::SeedShader); |
113 | |
114 | let ts = match self.transform.invert() { |
115 | Some(v) => v, |
116 | None => { |
117 | log::warn!("failed to invert a gradient transform. Nothing will be rendered" ); |
118 | return false; |
119 | } |
120 | }; |
121 | let ts = ts.post_concat(self.points_to_unit); |
122 | p.push_transform(ts); |
123 | |
124 | push_stages_pre(p); |
125 | |
126 | match self.tile_mode { |
127 | SpreadMode::Reflect => { |
128 | p.push(pipeline::Stage::ReflectX1); |
129 | } |
130 | SpreadMode::Repeat => { |
131 | p.push(pipeline::Stage::RepeatX1); |
132 | } |
133 | SpreadMode::Pad => { |
134 | if self.has_uniform_stops { |
135 | // We clamp only when the stops are evenly spaced. |
136 | // If not, there may be hard stops, and clamping ruins hard stops at 0 and/or 1. |
137 | // In that case, we must make sure we're using the general "gradient" stage, |
138 | // which is the only stage that will correctly handle unclamped t. |
139 | p.push(pipeline::Stage::PadX1); |
140 | } |
141 | } |
142 | } |
143 | |
144 | // The two-stop case with stops at 0 and 1. |
145 | if self.stops.len() == 2 { |
146 | debug_assert!(self.has_uniform_stops); |
147 | |
148 | let c0 = self.stops[0].color; |
149 | let c1 = self.stops[1].color; |
150 | |
151 | p.ctx.evenly_spaced_2_stop_gradient = EvenlySpaced2StopGradientCtx { |
152 | factor: GradientColor::new( |
153 | c1.red() - c0.red(), |
154 | c1.green() - c0.green(), |
155 | c1.blue() - c0.blue(), |
156 | c1.alpha() - c0.alpha(), |
157 | ), |
158 | bias: GradientColor::from(c0), |
159 | }; |
160 | |
161 | p.push(pipeline::Stage::EvenlySpaced2StopGradient); |
162 | } else { |
163 | // Unlike Skia, we do not support the `evenly_spaced_gradient` stage. |
164 | // In our case, there is no performance difference. |
165 | |
166 | let mut ctx = GradientCtx::default(); |
167 | |
168 | // Note: In order to handle clamps in search, the search assumes |
169 | // a stop conceptually placed at -inf. |
170 | // Therefore, the max number of stops is `self.points.len()+1`. |
171 | // |
172 | // We also need at least 16 values for lowp pipeline. |
173 | ctx.factors.reserve((self.stops.len() + 1).max(16)); |
174 | ctx.biases.reserve((self.stops.len() + 1).max(16)); |
175 | |
176 | ctx.t_values.reserve(self.stops.len() + 1); |
177 | |
178 | // Remove the dummy stops inserted by Gradient::new |
179 | // because they are naturally handled by the search method. |
180 | let (first_stop, last_stop) = if self.stops.len() > 2 { |
181 | let first = if self.stops[0].color != self.stops[1].color { |
182 | 0 |
183 | } else { |
184 | 1 |
185 | }; |
186 | |
187 | let len = self.stops.len(); |
188 | let last = if self.stops[len - 2].color != self.stops[len - 1].color { |
189 | len - 1 |
190 | } else { |
191 | len - 2 |
192 | }; |
193 | (first, last) |
194 | } else { |
195 | (0, 1) |
196 | }; |
197 | |
198 | let mut t_l = self.stops[first_stop].position.get(); |
199 | let mut c_l = GradientColor::from(self.stops[first_stop].color); |
200 | ctx.push_const_color(c_l); |
201 | ctx.t_values.push(NormalizedF32::ZERO); |
202 | // N.B. lastStop is the index of the last stop, not one after. |
203 | for i in first_stop..last_stop { |
204 | let t_r = self.stops[i + 1].position.get(); |
205 | let c_r = GradientColor::from(self.stops[i + 1].color); |
206 | debug_assert!(t_l <= t_r); |
207 | if t_l < t_r { |
208 | // For each stop we calculate a bias B and a scale factor F, such that |
209 | // for any t between stops n and n+1, the color we want is B[n] + F[n]*t. |
210 | let f = GradientColor::new( |
211 | (c_r.r - c_l.r) / (t_r - t_l), |
212 | (c_r.g - c_l.g) / (t_r - t_l), |
213 | (c_r.b - c_l.b) / (t_r - t_l), |
214 | (c_r.a - c_l.a) / (t_r - t_l), |
215 | ); |
216 | ctx.factors.push(f); |
217 | |
218 | ctx.biases.push(GradientColor::new( |
219 | c_l.r - f.r * t_l, |
220 | c_l.g - f.g * t_l, |
221 | c_l.b - f.b * t_l, |
222 | c_l.a - f.a * t_l, |
223 | )); |
224 | |
225 | ctx.t_values.push(NormalizedF32::new_clamped(t_l)); |
226 | } |
227 | |
228 | t_l = t_r; |
229 | c_l = c_r; |
230 | } |
231 | |
232 | ctx.push_const_color(c_l); |
233 | ctx.t_values.push(NormalizedF32::new_clamped(t_l)); |
234 | |
235 | ctx.len = ctx.factors.len(); |
236 | |
237 | // All lists must have the same length. |
238 | debug_assert_eq!(ctx.factors.len(), ctx.t_values.len()); |
239 | debug_assert_eq!(ctx.biases.len(), ctx.t_values.len()); |
240 | |
241 | // Will with zeros until we have enough data to fit into F32x16. |
242 | while ctx.factors.len() < 16 { |
243 | ctx.factors.push(GradientColor::default()); |
244 | ctx.biases.push(GradientColor::default()); |
245 | } |
246 | |
247 | p.push(pipeline::Stage::Gradient); |
248 | p.ctx.gradient = ctx; |
249 | } |
250 | |
251 | if !self.colors_are_opaque { |
252 | p.push(pipeline::Stage::Premultiply); |
253 | } |
254 | |
255 | push_stages_post(p); |
256 | |
257 | true |
258 | } |
259 | |
260 | pub fn apply_opacity(&mut self, opacity: f32) { |
261 | for stop in &mut self.stops { |
262 | stop.color.apply_opacity(opacity); |
263 | } |
264 | |
265 | self.colors_are_opaque = self.stops.iter().all(|p| p.color.is_opaque()); |
266 | } |
267 | } |
268 | |