1use alloc::vec::Vec;
8
9use tiny_skia_path::Scalar;
10
11use crate::{ColorSpace, GradientStop, Point, Shader, SpreadMode, Transform};
12
13use super::gradient::{Gradient, DEGENERATE_THRESHOLD};
14use crate::pipeline;
15use crate::pipeline::RasterPipelineBuilder;
16use crate::wide::u32x8;
17
18#[cfg(all(not(feature = "std"), feature = "no-std-float"))]
19use tiny_skia_path::NoStdFloat;
20
21#[derive(Copy, Clone, PartialEq, Debug, Default)]
22struct FocalData {
23 r1: f32, focal_x: f32, is_swapped: bool,
26}
27
28impl FocalData {
29 fn set(&mut self, mut r0: f32, mut r1: f32, matrix: &mut Transform) -> bool {
30 self.is_swapped = false;
31 self.focal_x = r0 / (r0 - r1);
32
33 if (self.focal_x - 1.0).is_nearly_zero() {
34 *matrix = matrix.post_translate(-1.0, 0.0).post_scale(-1.0, 1.0);
36 core::mem::swap(&mut r0, &mut r1);
37
38 self.focal_x = 0.0; self.is_swapped = true;
40 }
41
42 let from = [Point::from_xy(self.focal_x, 0.0), Point::from_xy(1.0, 0.0)];
44 let to = [Point::from_xy(0.0, 0.0), Point::from_xy(1.0, 0.0)];
45
46 let focal_matrix = match ts_from_poly_to_poly(from[0], from[1], to[0], to[1]) {
47 Some(m) => m,
48 None => return false,
49 };
50
51 *matrix = matrix.post_concat(focal_matrix);
52 self.r1 = r1 / (1.0 - self.focal_x).abs(); if self.is_focal_on_circle() {
57 *matrix = matrix.post_scale(0.5, 0.5);
58 } else {
59 *matrix = matrix.post_scale(
60 self.r1 / (self.r1 * self.r1 - 1.0),
61 1.0 / (self.r1 * self.r1 - 1.0).abs().sqrt(),
62 );
63 }
64
65 *matrix = matrix.post_scale((1.0 - self.focal_x).abs(), (1.0 - self.focal_x).abs()); true
68 }
69
70 fn is_focal_on_circle(&self) -> bool {
71 (1.0 - self.r1).is_nearly_zero()
72 }
73
74 fn is_well_behaved(&self) -> bool {
75 !self.is_focal_on_circle() && self.r1 > 1.0
76 }
77
78 fn is_natively_focal(&self) -> bool {
79 self.focal_x.is_nearly_zero()
80 }
81}
82
83#[derive(Clone, PartialEq, Debug)]
84enum GradientType {
85 Radial {
86 radius1: f32,
87 radius2: f32,
88 },
89 Strip {
90 scaled_r0: f32,
92 },
93 Focal(FocalData),
94}
95
96#[derive(Clone, PartialEq, Debug)]
98pub struct RadialGradient {
99 pub(crate) base: Gradient,
100 gradient_type: GradientType,
101}
102
103impl RadialGradient {
104 #[allow(clippy::new_ret_no_self)]
120 pub fn new(
121 start_point: Point,
122 start_radius: f32,
123 end_point: Point,
124 end_radius: f32,
125 stops: Vec<GradientStop>,
126 mode: SpreadMode,
127 transform: Transform,
128 ) -> Option<Shader<'static>> {
129 if start_radius < 0.0 || end_radius < 0.0 {
130 return None;
131 }
132
133 match stops.as_slice() {
134 [] => return None,
135 [stop] => return Some(Shader::SolidColor(stop.color)),
136 _ => {}
137 }
138
139 transform.invert()?;
140
141 let length = (start_point - end_point).length();
142 if !length.is_finite() {
143 return None;
144 }
145 if length.is_nearly_zero_within_tolerance(DEGENERATE_THRESHOLD) {
146 if start_radius.is_nearly_equal_within_tolerance(end_radius, DEGENERATE_THRESHOLD) {
147 if mode == SpreadMode::Pad && end_radius > DEGENERATE_THRESHOLD {
151 let start_color = stops.first()?.clone().color;
155 let end_color = stops.last()?.clone().color;
156 let mut new_stops = stops; new_stops.clear();
158 new_stops.extend_from_slice(&[
159 GradientStop::new(0.0, start_color),
160 GradientStop::new(1.0, start_color),
161 GradientStop::new(1.0, end_color),
162 ]);
163 return Self::new_radial_unchecked(
170 start_point,
171 end_radius,
172 new_stops,
173 mode,
174 transform,
175 );
176 }
177 return None;
179 }
180
181 if start_radius.is_nearly_zero_within_tolerance(DEGENERATE_THRESHOLD) {
182 return Self::new_radial_unchecked(start_point, end_radius, stops, mode, transform);
189 }
190 }
191
192 create(
193 start_point,
194 start_radius,
195 end_point,
196 end_radius,
197 stops,
198 mode,
199 transform,
200 )
201 }
202
203 fn new_radial_unchecked(
216 center: Point,
217 radius: f32,
218 stops: Vec<GradientStop>,
219 mode: SpreadMode,
220 transform: Transform,
221 ) -> Option<Shader<'static>> {
222 let inv = radius.invert();
223 let points_to_unit = Transform::from_translate(-center.x, -center.y).post_scale(inv, inv);
224
225 Some(Shader::RadialGradient(RadialGradient {
226 base: Gradient::new(stops, mode, transform, points_to_unit),
227 gradient_type: GradientType::Radial {
228 radius1: 0.0,
229 radius2: radius,
230 },
231 }))
232 }
233
234 pub(crate) fn push_stages(&self, cs: ColorSpace, p: &mut RasterPipelineBuilder) -> bool {
235 let (p0, p1) = match self.gradient_type {
236 GradientType::Radial { radius1, radius2 } => {
237 if radius1 == 0.0 {
238 (1.0, 0.0)
239 } else {
240 let d_radius = radius2 - radius1;
241 let p0 = radius1.max(radius2) / d_radius;
243 let p1 = -radius1 / d_radius;
244 (p0, p1)
245 }
246 }
247 GradientType::Strip { scaled_r0 } => {
248 (scaled_r0 * scaled_r0, 0.0 )
249 }
250 GradientType::Focal(fd) => (1.0 / fd.r1, fd.focal_x),
251 };
252
253 p.ctx.two_point_conical_gradient = pipeline::TwoPointConicalGradientCtx {
254 mask: u32x8::default(),
255 p0,
256 p1,
257 };
258
259 self.base.push_stages(
260 p,
261 cs,
262 &|p| {
263 match self.gradient_type {
264 GradientType::Radial { .. } => {
265 p.push(pipeline::Stage::XYToRadius);
266 if (p0, p1) != (1.0, 0.0) {
269 p.push(pipeline::Stage::ApplyConcentricScaleBias);
270 }
271 }
272 GradientType::Strip { .. } => {
273 p.push(pipeline::Stage::XYTo2PtConicalStrip);
274 p.push(pipeline::Stage::Mask2PtConicalNan);
275 }
276 GradientType::Focal(fd) => {
277 if fd.is_focal_on_circle() {
278 p.push(pipeline::Stage::XYTo2PtConicalFocalOnCircle);
279 } else if fd.is_well_behaved() {
280 p.push(pipeline::Stage::XYTo2PtConicalWellBehaved);
281 } else if fd.is_swapped || (1.0 - fd.focal_x) < 0.0 {
282 p.push(pipeline::Stage::XYTo2PtConicalSmaller);
283 } else {
284 p.push(pipeline::Stage::XYTo2PtConicalGreater);
285 }
286
287 if !fd.is_well_behaved() {
288 p.push(pipeline::Stage::Mask2PtConicalDegenerates);
289 }
290
291 if (1.0 - fd.focal_x) < 0.0 {
292 p.push(pipeline::Stage::NegateX);
293 }
294
295 if !fd.is_natively_focal() {
296 p.push(pipeline::Stage::Alter2PtConicalCompensateFocal);
297 }
298
299 if fd.is_swapped {
300 p.push(pipeline::Stage::Alter2PtConicalUnswap);
301 }
302 }
303 }
304 },
305 &|p| match self.gradient_type {
306 GradientType::Strip { .. } => p.push(pipeline::Stage::ApplyVectorMask),
307 GradientType::Focal(fd) if !fd.is_well_behaved() => {
308 p.push(pipeline::Stage::ApplyVectorMask)
309 }
310 _ => {}
311 },
312 )
313 }
314}
315
316fn create(
317 c0: Point,
318 r0: f32,
319 c1: Point,
320 r1: f32,
321 stops: Vec<GradientStop>,
322 mode: SpreadMode,
323 transform: Transform,
324) -> Option<Shader<'static>> {
325 let mut gradient_type;
326 let mut gradient_matrix;
327
328 if (c0 - c1).length().is_nearly_zero() {
329 if r0.max(r1).is_nearly_zero() || r0.is_nearly_equal(r1) {
330 return None;
333 }
334
335 let scale = 1.0 / r0.max(r1);
337 gradient_matrix = Transform::from_translate(-c1.x, -c1.y).post_scale(scale, scale);
338 gradient_type = GradientType::Radial {
339 radius1: r0,
340 radius2: r1,
341 };
342 } else {
343 gradient_matrix = map_to_unit_x(c0, c1)?;
344 let d_center = (c0 - c1).length();
345 gradient_type = if (r0 - r1).is_nearly_zero() {
346 let scaled_r0 = r0 / d_center;
347 GradientType::Strip { scaled_r0 }
348 } else {
349 GradientType::Focal(FocalData::default())
350 };
351 }
352 if let GradientType::Focal(ref mut focal_data) = &mut gradient_type {
353 let d_center = (c0 - c1).length();
354 if !focal_data.set(r0 / d_center, r1 / d_center, &mut gradient_matrix) {
355 return None;
356 }
357 }
358
359 Some(Shader::RadialGradient(RadialGradient {
360 base: Gradient::new(stops, mode, transform, gradient_matrix),
361 gradient_type,
362 }))
363}
364
365fn map_to_unit_x(origin: Point, x_is_one: Point) -> Option<Transform> {
366 ts_from_poly_to_poly(
367 origin,
368 x_is_one,
369 Point::from_xy(0.0, 0.0),
370 Point::from_xy(1.0, 0.0),
371 )
372}
373
374fn ts_from_poly_to_poly(src1: Point, src2: Point, dst1: Point, dst2: Point) -> Option<Transform> {
375 let tmp = from_poly2(src1, src2);
376 let res = tmp.invert()?;
377 let tmp = from_poly2(dst1, dst2);
378 Some(tmp.pre_concat(res))
379}
380
381fn from_poly2(p0: Point, p1: Point) -> Transform {
382 Transform::from_row(
383 p1.y - p0.y,
384 p0.x - p1.x,
385 p1.x - p0.x,
386 p1.y - p0.y,
387 p0.x,
388 p0.y,
389 )
390}