Skip to content

Commit 3bab284

Browse files
committed
Align more with original tiny skia RadialGradient implementation
- Notably, transform inversion happens after checking the color stops.
1 parent 080ab6d commit 3bab284

2 files changed

Lines changed: 65 additions & 48 deletions

File tree

src/shaders/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ pub enum Shader<'a> {
5050
SolidColor(Color),
5151
/// A linear gradient shader.
5252
LinearGradient(LinearGradient),
53-
/// A conical radial gradient shader.
53+
/// A radial gradient shader.
5454
RadialGradient(RadialGradient),
5555
/// A pattern shader.
5656
Pattern(Pattern<'a>),

src/shaders/radial_gradient.rs

Lines changed: 64 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -82,24 +82,37 @@ impl FocalData {
8282

8383
#[derive(Clone, PartialEq, Debug)]
8484
enum GradientType {
85-
Radial,
86-
Strip,
85+
Radial { radius1: f32, radius2: f32 },
86+
Strip {
87+
/// Radius of the first circle scaled by the distance between centers (r0 / d_center)
88+
scaled_r0: f32,
89+
},
8790
Focal(FocalData),
8891
}
8992

9093
/// A 2-point conical gradient shader.
9194
#[derive(Clone, PartialEq, Debug)]
9295
pub struct RadialGradient {
9396
pub(crate) base: Gradient,
94-
center1: Point,
95-
center2: Point,
96-
radius1: f32,
97-
radius2: f32,
9897
gradient_type: GradientType,
9998
}
10099

101100
impl RadialGradient {
102-
/// Creates a new 2-point conical gradient shader.
101+
/// Creates a new two-point conical gradient shader.
102+
///
103+
/// A two-point conical gradient (also known as a radial gradient)
104+
/// interpolates colors between two circles defined by their center points
105+
/// and radii.
106+
///
107+
/// Returns `Shader::SolidColor` when:
108+
/// - `stops.len()` == 1
109+
///
110+
/// Returns `None` when:
111+
/// - `stops` is empty
112+
/// - `start_radius` < 0 or `end_radius` < 0
113+
/// - `transform` is not invertible
114+
/// - The gradient is degenerate (both radii and centers are equal, except
115+
/// in specific pad mode cases)
103116
#[allow(clippy::new_ret_no_self)]
104117
pub fn new(
105118
start_point: Point,
@@ -114,14 +127,14 @@ impl RadialGradient {
114127
return None;
115128
}
116129

117-
transform.invert()?;
118-
119130
match stops.as_slice() {
120131
[] => return None,
121132
[stop] => return Some(Shader::SolidColor(stop.color)),
122133
_ => {}
123134
}
124135

136+
transform.invert()?;
137+
125138
let length = (start_point - end_point).length();
126139
if !length.is_finite() {
127140
return None;
@@ -135,21 +148,18 @@ impl RadialGradient {
135148
// The interpolation region becomes an infinitely thin ring at the radius, so the
136149
// final gradient will be the first color repeated from p=0 to 1, and then a hard
137150
// stop switching to the last color at p=1.
138-
let front = stops.first()?.clone();
139-
let back = stops.last()?.clone();
151+
let start_color = stops.first()?.clone().color;
152+
let end_color = stops.last()?.clone().color;
140153
let mut new_stops = stops; // Reuse allocation from stops.
141154
new_stops.clear();
142155
new_stops.extend_from_slice(&[
143-
GradientStop::new(0.0, front.color),
144-
GradientStop::new(1.0, front.color),
145-
GradientStop::new(1.0, back.color),
156+
GradientStop::new(0.0, start_color),
157+
GradientStop::new(1.0, start_color),
158+
GradientStop::new(1.0, end_color),
146159
]);
147160
// If the center positions are the same, then the gradient is the radial variant
148161
// of a 2 pt conical gradient, an actual radial gradient (startRadius == 0), or
149162
// it is fully degenerate (startRadius == endRadius).
150-
let inv = end_radius.invert();
151-
let ts = Transform::from_translate(-start_point.x, -start_point.y)
152-
.post_scale(inv, inv);
153163
// We can treat this gradient as a simple radial, which is faster. If we got
154164
// here, we know that endRadius is not equal to 0, so this produces a meaningful
155165
// gradient
@@ -159,7 +169,6 @@ impl RadialGradient {
159169
new_stops,
160170
mode,
161171
transform,
162-
ts,
163172
);
164173
}
165174
// TODO: Consider making a degenerate gradient
@@ -171,10 +180,6 @@ impl RadialGradient {
171180
// is the radial variant of a 2 pt conical gradient,
172181
// an actual radial gradient (startRadius == 0),
173182
// or it is fully degenerate (startRadius == endRadius).
174-
let inv = end_radius.invert();
175-
let ts =
176-
Transform::from_translate(-start_point.x, -start_point.y).post_scale(inv, inv);
177-
178183
// We can treat this gradient as a simple radial, which is faster. If we got here,
179184
// we know that endRadius is not equal to 0, so this produces a meaningful gradient.
180185
return Self::new_radial_unchecked(
@@ -183,7 +188,6 @@ impl RadialGradient {
183188
stops,
184189
mode,
185190
transform,
186-
ts,
187191
);
188192
}
189193
}
@@ -199,40 +203,52 @@ impl RadialGradient {
199203
)
200204
}
201205

202-
/// Create a simple radial shader.
206+
/// Creates a simple radial gradient shader without validation.
207+
///
208+
/// This is an optimized path for creating radial gradients when the start radius is 0
209+
/// and the gradient is known to be valid. The function computes the points-to-unit
210+
/// transformation internally based on the center point and radius.
211+
///
212+
/// # Parameters
213+
/// - `center`: The center point of the radial gradient
214+
/// - `radius`: The radius of the gradient (assumed to be > 0)
215+
/// - `stops`: Color stops for the gradient (assumed to have length >= 2)
216+
/// - `mode`: How the gradient extends beyond its bounds
217+
/// - `transform`: The gradient's transformation matrix (assumed to be invertible)
203218
fn new_radial_unchecked(
204-
point: Point,
219+
center: Point,
205220
radius: f32,
206221
stops: Vec<GradientStop>,
207222
mode: SpreadMode,
208223
transform: Transform,
209-
points_to_unit: Transform,
210224
) -> Option<Shader<'static>> {
225+
let inv = radius.invert();
226+
let points_to_unit =
227+
Transform::from_translate(-center.x, -center.y).post_scale(inv, inv);
228+
211229
Some(Shader::RadialGradient(RadialGradient {
212230
base: Gradient::new(stops, mode, transform, points_to_unit),
213-
center1: point,
214-
center2: point,
215-
radius1: 0.0,
216-
radius2: radius,
217-
gradient_type: GradientType::Radial,
231+
gradient_type: GradientType::Radial {
232+
radius1: 0.0,
233+
radius2: radius,
234+
},
218235
}))
219236
}
220237

221238
pub(crate) fn push_stages(&self, cs: ColorSpace, p: &mut RasterPipelineBuilder) -> bool {
222239
let (p0, p1) = match self.gradient_type {
223-
GradientType::Radial => {
224-
if self.radius1 == 0.0 {
240+
GradientType::Radial { radius1, radius2 } => {
241+
if radius1 == 0.0 {
225242
(1.0, 0.0)
226243
} else {
227-
let d_radius = self.radius2 - self.radius1;
244+
let d_radius = radius2 - radius1;
228245
// For concentric gradients: t = t * scale + bias
229-
let p0 = self.radius1.max(self.radius2) / d_radius;
230-
let p1 = -self.radius1 / d_radius;
246+
let p0 = radius1.max(radius2) / d_radius;
247+
let p1 = -radius1 / d_radius;
231248
(p0, p1)
232249
}
233250
}
234-
GradientType::Strip => {
235-
let scaled_r0 = self.radius1 / (self.center1 - self.center2).length();
251+
GradientType::Strip { scaled_r0 } => {
236252
(scaled_r0 * scaled_r0, 0.0 /*unused*/)
237253
}
238254
GradientType::Focal(fd) => (1.0 / fd.r1, fd.focal_x),
@@ -249,15 +265,15 @@ impl RadialGradient {
249265
cs,
250266
&|p| {
251267
match self.gradient_type {
252-
GradientType::Radial => {
268+
GradientType::Radial { .. } => {
253269
p.push(pipeline::Stage::XYToRadius);
254270
// Apply scale/bias to map t from [0, 1] based on r_max to proper t where
255271
// t=0 at r0 and t=1 at r1
256272
if (p0, p1) != (1.0, 0.0) {
257273
p.push(pipeline::Stage::ApplyConcentricScaleBias);
258274
}
259275
}
260-
GradientType::Strip => {
276+
GradientType::Strip { .. } => {
261277
p.push(pipeline::Stage::XYTo2PtConicalStrip);
262278
p.push(pipeline::Stage::Mask2PtConicalNan);
263279
}
@@ -291,7 +307,7 @@ impl RadialGradient {
291307
}
292308
},
293309
&|p| match self.gradient_type {
294-
GradientType::Strip => p.push(pipeline::Stage::ApplyVectorMask),
310+
GradientType::Strip { .. } => p.push(pipeline::Stage::ApplyVectorMask),
295311
GradientType::Focal(fd) if !fd.is_well_behaved() => {
296312
p.push(pipeline::Stage::ApplyVectorMask)
297313
}
@@ -323,11 +339,16 @@ fn create(
323339
// Concentric case: we can pretend we're radial (with a tiny twist).
324340
let scale = 1.0 / r0.max(r1);
325341
gradient_matrix = Transform::from_translate(-c1.x, -c1.y).post_scale(scale, scale);
326-
gradient_type = GradientType::Radial;
342+
gradient_type = GradientType::Radial {
343+
radius1: r0,
344+
radius2: r1,
345+
};
327346
} else {
328347
gradient_matrix = map_to_unit_x(c0, c1)?;
348+
let d_center = (c0 - c1).length();
329349
gradient_type = if (r0 - r1).is_nearly_zero() {
330-
GradientType::Strip
350+
let scaled_r0 = r0 / d_center;
351+
GradientType::Strip { scaled_r0 }
331352
} else {
332353
GradientType::Focal(FocalData::default())
333354
};
@@ -341,10 +362,6 @@ fn create(
341362

342363
Some(Shader::RadialGradient(RadialGradient {
343364
base: Gradient::new(stops, mode, transform, gradient_matrix),
344-
center1: c0,
345-
center2: c1,
346-
radius1: r0,
347-
radius2: r1,
348365
gradient_type,
349366
}))
350367
}

0 commit comments

Comments
 (0)