tiny_skia/shaders/
radial_gradient.rs1use alloc::vec::Vec;
8
9use tiny_skia_path::Scalar;
10
11use crate::{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)]
22struct FocalData {
23 r1: f32, }
25
26impl FocalData {
27 fn is_focal_on_circle(&self) -> bool {
34 (1.0 - self.r1).is_nearly_zero()
35 }
36
37 fn is_well_behaved(&self) -> bool {
38 !self.is_focal_on_circle() && self.r1 > 1.0
39 }
40}
41
42#[derive(Clone, PartialEq, Debug)]
47pub struct RadialGradient {
48 pub(crate) base: Gradient,
49 focal_data: Option<FocalData>,
50}
51
52impl RadialGradient {
53 #[allow(clippy::new_ret_no_self)]
64 pub fn new(
65 start: Point,
66 end: Point,
67 radius: f32,
68 stops: Vec<GradientStop>,
69 mode: SpreadMode,
70 transform: Transform,
71 ) -> Option<Shader<'static>> {
72 if radius < 0.0 || radius.is_nearly_zero() {
75 return None;
76 }
77
78 if stops.is_empty() {
79 return None;
80 }
81
82 if stops.len() == 1 {
83 return Some(Shader::SolidColor(stops[0].color));
84 }
85
86 transform.invert()?;
87
88 let length = (end - start).length();
89 if !length.is_finite() {
90 return None;
91 }
92
93 if length.is_nearly_zero_within_tolerance(DEGENERATE_THRESHOLD) {
94 let inv = radius.invert();
100 let mut ts = Transform::from_translate(-start.x, -start.y);
101 ts = ts.post_scale(inv, inv);
102
103 Some(Shader::RadialGradient(RadialGradient {
106 base: Gradient::new(stops, mode, transform, ts),
107 focal_data: None,
108 }))
109 } else {
110 let mut ts = ts_from_poly_to_poly(
112 start,
113 end,
114 Point::from_xy(0.0, 0.0),
115 Point::from_xy(1.0, 0.0),
116 )?;
117
118 let d_center = (start - end).length();
119 let r1 = radius / d_center;
120 let focal_data = FocalData { r1 };
121
122 if focal_data.is_focal_on_circle() {
125 ts = ts.post_scale(0.5, 0.5);
126 } else {
127 ts = ts.post_scale(r1 / (r1 * r1 - 1.0), 1.0 / ((r1 * r1 - 1.0).abs()).sqrt());
128 }
129
130 Some(Shader::RadialGradient(RadialGradient {
131 base: Gradient::new(stops, mode, transform, ts),
132 focal_data: Some(focal_data),
133 }))
134 }
135 }
136
137 pub(crate) fn push_stages(&self, p: &mut RasterPipelineBuilder) -> bool {
138 let p0 = if let Some(focal_data) = self.focal_data {
139 1.0 / focal_data.r1
140 } else {
141 1.0
142 };
143
144 p.ctx.two_point_conical_gradient = pipeline::TwoPointConicalGradientCtx {
145 mask: u32x8::default(),
146 p0,
147 };
148
149 self.base.push_stages(
150 p,
151 &|p| {
152 if let Some(focal_data) = self.focal_data {
153 if focal_data.is_focal_on_circle() {
156 p.push(pipeline::Stage::XYTo2PtConicalFocalOnCircle);
157 } else if focal_data.is_well_behaved() {
158 p.push(pipeline::Stage::XYTo2PtConicalWellBehaved);
159 } else {
160 p.push(pipeline::Stage::XYTo2PtConicalGreater);
161 }
162
163 if !focal_data.is_well_behaved() {
164 p.push(pipeline::Stage::Mask2PtConicalDegenerates);
165 }
166 } else {
167 p.push(pipeline::Stage::XYToRadius);
168 }
169 },
170 &|p| {
171 if let Some(focal_data) = self.focal_data {
172 if !focal_data.is_well_behaved() {
173 p.push(pipeline::Stage::ApplyVectorMask);
174 }
175 }
176 },
177 )
178 }
179}
180
181fn ts_from_poly_to_poly(src1: Point, src2: Point, dst1: Point, dst2: Point) -> Option<Transform> {
182 let tmp = from_poly2(src1, src2);
183 let res = tmp.invert()?;
184 let tmp = from_poly2(dst1, dst2);
185 Some(tmp.pre_concat(res))
186}
187
188fn from_poly2(p0: Point, p1: Point) -> Transform {
189 Transform::from_row(
190 p1.y - p0.y,
191 p0.x - p1.x,
192 p1.x - p0.x,
193 p1.y - p0.y,
194 p0.x,
195 p0.y,
196 )
197}