rasterize/
grad.rs

1use crate::{Color, LinColor, Paint, Point, Scalar, Transform, Units, utils::quadratic_solve};
2#[cfg(feature = "serde")]
3use serde::{Deserialize, Serialize, de};
4use std::cmp::Ordering;
5
6/// Gradient spread logic for the parameter smaller than 0 and greater than 1
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
8#[cfg_attr(
9    feature = "serde",
10    derive(Serialize, Deserialize),
11    serde(rename_all = "lowercase")
12)]
13pub enum GradSpread {
14    /// Use the same colors as the edge of the gradient
15    Pad,
16    /// Repeat gradient
17    Repeat,
18    /// Repeat gradient but alternate reflected and non reflected versions
19    Reflect,
20}
21
22impl GradSpread {
23    /// Map gradient parameter value to the range of [0, 1]
24    pub fn at(&self, t: Scalar) -> Scalar {
25        match self {
26            GradSpread::Pad => t,
27            GradSpread::Repeat => t.rem_euclid(1.0),
28            GradSpread::Reflect => ((t + 1.0).rem_euclid(2.0) - 1.0).abs(),
29        }
30    }
31}
32
33impl Default for GradSpread {
34    fn default() -> Self {
35        Self::Pad
36    }
37}
38
39/// Specifies color at a particular parameter offset of the gradient
40#[derive(Debug, Clone, Copy, PartialEq)]
41pub struct GradStop {
42    pub position: Scalar,
43    pub color: LinColor,
44}
45
46impl GradStop {
47    pub fn new(position: Scalar, color: LinColor) -> Self {
48        Self { position, color }
49    }
50}
51
52#[cfg(feature = "serde")]
53impl Serialize for GradStop {
54    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
55    where
56        S: serde::Serializer,
57    {
58        let color = self.color.to_string();
59        (self.position, color).serialize(serializer)
60    }
61}
62
63#[cfg(feature = "serde")]
64impl<'de> Deserialize<'de> for GradStop {
65    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
66    where
67        D: serde::Deserializer<'de>,
68    {
69        use std::borrow::Cow;
70        let (position, color): (Scalar, Cow<'de, str>) = Deserialize::deserialize(deserializer)?;
71        Ok(Self {
72            position,
73            color: color.parse::<LinColor>().map_err(de::Error::custom)?,
74        })
75    }
76}
77
78/// List of all `GradStop` in the gradient
79#[derive(Debug, Clone, PartialEq)]
80#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(transparent))]
81pub struct GradStops {
82    stops: Vec<GradStop>,
83}
84
85impl GradStops {
86    pub fn new(mut stops: Vec<GradStop>) -> Self {
87        stops.sort_by(|s0, s1| {
88            s0.position
89                .partial_cmp(&s1.position)
90                .unwrap_or(Ordering::Greater)
91        });
92        if stops.is_empty() {
93            stops.push(GradStop {
94                position: 0.0,
95                color: LinColor::new(0.0, 0.0, 0.0, 1.0),
96            });
97        }
98        Self { stops }
99    }
100
101    fn convert_to_srgb(&mut self) {
102        for stop in self.stops.iter_mut() {
103            stop.color = stop.color.into_srgb()
104        }
105    }
106
107    #[cfg(feature = "serde")]
108    fn convert_to_linear(&mut self) {
109        for stop in self.stops.iter_mut() {
110            stop.color = stop.color.into_linear()
111        }
112    }
113}
114
115impl GradStops {
116    fn at(&self, t: Scalar) -> LinColor {
117        let index = self.stops.binary_search_by(|stop| {
118            if stop.position < t {
119                Ordering::Less
120            } else {
121                Ordering::Greater
122            }
123        });
124        let index = match index {
125            Ok(index) => index,
126            Err(index) => index,
127        };
128        let size = self.stops.len();
129        if index == 0 {
130            self.stops[index].color
131        } else if index == size {
132            self.stops[size - 1].color
133        } else {
134            let p0 = &self.stops[index - 1];
135            let p1 = &self.stops[index];
136            let ratio = (t - p0.position) / (p1.position - p0.position);
137            p0.color.lerp(p1.color, ratio as f32)
138        }
139    }
140}
141
142impl From<Vec<GradStop>> for GradStops {
143    fn from(stops: Vec<GradStop>) -> Self {
144        Self::new(stops)
145    }
146}
147
148/// Linear Gradient
149#[derive(Debug, Clone, PartialEq)]
150pub struct GradLinear {
151    units: Units,
152    linear_colors: bool,
153    spread: GradSpread,
154    tr: Transform,
155    start: Point,
156    end: Point,
157    // precomputed value equal to `(end - start) / |end - start| ^ 2`
158    dir: Point,
159    stops: GradStops,
160}
161
162impl GradLinear {
163    pub const GRAD_TYPE: &'static str = "linear-gradient";
164
165    pub fn new(
166        stops: impl Into<GradStops>,
167        units: Units,
168        linear_colors: bool,
169        spread: GradSpread,
170        tr: Transform,
171        start: impl Into<Point>,
172        end: impl Into<Point>,
173    ) -> Self {
174        let start = start.into();
175        let end = end.into();
176        let mut stops = stops.into();
177        if !linear_colors {
178            stops.convert_to_srgb();
179        }
180        let dir = end - start;
181        Self {
182            stops,
183            units,
184            linear_colors,
185            spread,
186            tr,
187            start,
188            end,
189            dir: dir / dir.dot(dir),
190        }
191    }
192
193    /// Construct linear gradient from JSON value
194    #[cfg(feature = "serde")]
195    pub fn from_json(value: serde_json::Value) -> Result<Self, crate::SvgParserError> {
196        let value: GradLinearSerialize = serde_json::from_value(value)?;
197        Ok(value.into())
198    }
199}
200
201impl Paint for GradLinear {
202    fn at(&self, point: Point) -> LinColor {
203        // t = (point - start).dot(end - start) / |end - start| ^ 2
204        let t = (point - self.start).dot(self.dir);
205        let color = self.stops.at(self.spread.at(t));
206        if self.linear_colors {
207            color
208        } else {
209            color.into_linear()
210        }
211    }
212
213    fn transform(&self) -> Transform {
214        self.tr
215    }
216
217    fn units(&self) -> Option<Units> {
218        Some(self.units)
219    }
220
221    #[cfg(feature = "serde")]
222    fn to_json(&self) -> Result<serde_json::Value, crate::SvgParserError> {
223        let ser: GradLinearSerialize = self.clone().into();
224        Ok(serde_json::to_value(ser)?)
225    }
226}
227
228#[cfg(feature = "serde")]
229impl Serialize for GradLinear {
230    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
231    where
232        S: serde::Serializer,
233    {
234        GradLinearSerialize::from(self.clone()).serialize(serializer)
235    }
236}
237
238#[cfg(feature = "serde")]
239impl<'de> Deserialize<'de> for GradLinear {
240    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
241    where
242        D: serde::Deserializer<'de>,
243    {
244        Ok(GradLinearSerialize::deserialize(deserializer)?.into())
245    }
246}
247
248/// Intermediate type for linear gradient (de)serialization
249#[cfg(feature = "serde")]
250#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
251struct GradLinearSerialize {
252    #[serde(rename = "type")]
253    grad_type: String,
254    #[serde(default, skip_serializing_if = "crate::utils::is_default")]
255    units: Units,
256    #[serde(default, skip_serializing_if = "crate::utils::is_default")]
257    linear_colors: bool,
258    #[serde(default, skip_serializing_if = "crate::utils::is_default")]
259    spread: GradSpread,
260    #[serde(
261        with = "crate::utils::serde_from_str",
262        default,
263        skip_serializing_if = "crate::utils::is_default"
264    )]
265    tr: Transform,
266    start: Point,
267    end: Point,
268    stops: GradStops,
269}
270
271#[cfg(feature = "serde")]
272impl From<GradLinear> for GradLinearSerialize {
273    fn from(mut grad: GradLinear) -> Self {
274        if !grad.linear_colors {
275            grad.stops.convert_to_linear();
276        }
277        GradLinearSerialize {
278            grad_type: GradLinear::GRAD_TYPE.into(),
279            units: grad.units,
280            linear_colors: grad.linear_colors,
281            spread: grad.spread,
282            tr: grad.tr,
283            start: grad.start,
284            end: grad.end,
285            stops: grad.stops,
286        }
287    }
288}
289
290#[cfg(feature = "serde")]
291impl From<GradLinearSerialize> for GradLinear {
292    fn from(value: GradLinearSerialize) -> Self {
293        Self::new(
294            value.stops,
295            value.units,
296            value.linear_colors,
297            value.spread,
298            value.tr,
299            value.start,
300            value.end,
301        )
302    }
303}
304
305/// Radial Gradient
306#[derive(Debug, Clone, PartialEq)]
307pub struct GradRadial {
308    units: Units,
309    linear_colors: bool,
310    spread: GradSpread,
311    tr: Transform,
312    center: Point,
313    radius: Scalar,
314    fcenter: Point,
315    fradius: Scalar,
316    stops: GradStops,
317}
318
319impl GradRadial {
320    pub const GRAD_TYPE: &'static str = "radial-gradient";
321
322    #[allow(clippy::too_many_arguments)]
323    pub fn new(
324        stops: impl Into<GradStops>,
325        units: Units,
326        linear_colors: bool,
327        spread: GradSpread,
328        tr: Transform,
329        center: impl Into<Point>,
330        radius: Scalar,
331        fcenter: impl Into<Point>,
332        fradius: Scalar,
333    ) -> Self {
334        let center = center.into();
335        let fcenter = fcenter.into();
336        let mut stops = stops.into();
337        if !linear_colors {
338            stops.convert_to_srgb();
339        }
340        Self {
341            stops,
342            units,
343            linear_colors,
344            spread,
345            tr,
346            center,
347            radius,
348            fcenter,
349            fradius,
350        }
351    }
352
353    /// Construct radial gradient from JSON value
354    #[cfg(feature = "serde")]
355    pub fn from_json(value: serde_json::Value) -> Result<Self, crate::SvgParserError> {
356        let value: GradRadialSerialize = serde_json::from_value(value)?;
357        Ok(value.into())
358    }
359
360    /// Calculate gradient offset at a given point
361    fn offset(&self, point: Point) -> Option<Scalar> {
362        // Two circle gradient is an interpolation between two circles (fc, fr) and (c, r),
363        // with center `c(t) = (1 - t) * fc + t * c`, and radius `r(t) = (1 - t) * fr + t * r`.
364        // If we have a pixel with coordinates `p`, we should solve equation for it
365        // `|| c(t) - p || = r(t)` and pick solution corresponding to bigger radius.
366        //
367        // Solving this equation for `t`:
368        //```
369        //     || c(t) - p || = r(t)  -> At² - 2Bt + C = 0
370        // where:
371        //
372        //     cd = c - fc
373        //     pd = p - fc
374        //     rd = r - fr
375        //     A = cdx ^ 2 + cdy ^ 2 - rd ^ 2
376        //     B = pdx * cdx + pdy * cdy + fradius * rd
377        //     C = pdx ^2 + pdy ^ 2 - fradius ^ 2
378        // results in:
379        //     t = (B +/- (B ^ 2 - A * C).sqrt()) / A
380        //```
381        // [reference]: https://cgit.freedesktop.org/pixman/tree/pixman/pixman-radial-gradient.c
382
383        let cd = self.center - self.fcenter;
384        let pd = point - self.fcenter;
385        let rd = self.radius - self.fradius;
386
387        let a = cd.dot(cd) - rd * rd;
388        let b = -2.0 * (cd.dot(pd) + self.fradius * rd);
389        let c = pd.dot(pd) - self.fradius * self.fradius;
390
391        match quadratic_solve(a, b, c).into_array() {
392            [Some(t0), Some(t1)] => Some(t0.max(t1)),
393            [Some(t), None] | [None, Some(t)] => Some(t),
394            _ => None,
395        }
396    }
397}
398
399impl Paint for GradRadial {
400    fn at(&self, point: Point) -> LinColor {
401        let offset = match self.offset(point) {
402            None => return LinColor::new(0.0, 0.0, 0.0, 0.0),
403            Some(offset) => offset,
404        };
405        let color = self.stops.at(self.spread.at(offset));
406        if self.linear_colors {
407            color
408        } else {
409            color.into_linear()
410        }
411    }
412
413    fn transform(&self) -> Transform {
414        self.tr
415    }
416
417    fn units(&self) -> Option<Units> {
418        Some(self.units)
419    }
420
421    #[cfg(feature = "serde")]
422    fn to_json(&self) -> Result<serde_json::Value, crate::SvgParserError> {
423        let ser: GradRadialSerialize = self.clone().into();
424        Ok(serde_json::to_value(ser)?)
425    }
426}
427
428#[cfg(feature = "serde")]
429impl Serialize for GradRadial {
430    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
431    where
432        S: serde::Serializer,
433    {
434        GradRadialSerialize::from(self.clone()).serialize(serializer)
435    }
436}
437
438#[cfg(feature = "serde")]
439impl<'de> Deserialize<'de> for GradRadial {
440    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
441    where
442        D: serde::Deserializer<'de>,
443    {
444        Ok(GradRadialSerialize::deserialize(deserializer)?.into())
445    }
446}
447
448/// Intermediate type for radial gradient (de)serialization
449#[cfg(feature = "serde")]
450#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
451struct GradRadialSerialize {
452    #[serde(rename = "type")]
453    grad_type: String,
454    #[serde(default, skip_serializing_if = "crate::utils::is_default")]
455    units: Units,
456    #[serde(default, skip_serializing_if = "crate::utils::is_default")]
457    linear_colors: bool,
458    #[serde(default, skip_serializing_if = "crate::utils::is_default")]
459    spread: GradSpread,
460    #[serde(with = "crate::utils::serde_from_str")]
461    tr: Transform,
462    center: Point,
463    radius: Scalar,
464    #[serde(default, skip_serializing_if = "crate::utils::is_default")]
465    fcenter: Option<Point>,
466    #[serde(default, skip_serializing_if = "crate::utils::is_default")]
467    fradius: Scalar,
468    stops: GradStops,
469}
470
471#[cfg(feature = "serde")]
472impl From<GradRadial> for GradRadialSerialize {
473    fn from(mut grad: GradRadial) -> Self {
474        if !grad.linear_colors {
475            grad.stops.convert_to_linear();
476        }
477        Self {
478            grad_type: GradRadial::GRAD_TYPE.into(),
479            units: grad.units,
480            linear_colors: grad.linear_colors,
481            spread: grad.spread,
482            tr: grad.tr,
483            center: grad.center,
484            radius: grad.radius,
485            fcenter: (grad.center != grad.fcenter).then_some(grad.fcenter),
486            fradius: grad.fradius,
487            stops: grad.stops,
488        }
489    }
490}
491
492#[cfg(feature = "serde")]
493impl From<GradRadialSerialize> for GradRadial {
494    fn from(value: GradRadialSerialize) -> Self {
495        Self::new(
496            value.stops,
497            value.units,
498            value.linear_colors,
499            value.spread,
500            value.tr,
501            value.center,
502            value.radius,
503            value.fcenter.unwrap_or(value.center),
504            value.fradius,
505        )
506    }
507}
508
509#[cfg(test)]
510mod tests {
511    use super::*;
512    use crate::assert_approx_eq;
513
514    #[test]
515    fn test_spread() {
516        use GradSpread::*;
517        assert_approx_eq!(Reflect.at(0.3), 0.3, 1e-6);
518        assert_approx_eq!(Reflect.at(-0.3), 0.3, 1e-6);
519        assert_approx_eq!(Reflect.at(1.3), 0.7, 1e-6);
520        assert_approx_eq!(Reflect.at(-1.3), 0.7, 1e-6);
521
522        assert_approx_eq!(Repeat.at(0.3), 0.3);
523        assert_approx_eq!(Repeat.at(-0.3), 0.7);
524    }
525
526    #[test]
527    fn test_grad_stops() -> Result<(), Box<dyn std::error::Error>> {
528        let stops = GradStops::new(vec![
529            GradStop::new(0.0, LinColor::new(1.0, 0.0, 0.0, 1.0)),
530            GradStop::new(0.5, LinColor::new(0.0, 1.0, 0.0, 1.0)),
531            GradStop::new(1.0, LinColor::new(0.0, 0.0, 1.0, 1.0)),
532        ]);
533        assert_eq!(stops.at(-1.0), LinColor::new(1.0, 0.0, 0.0, 1.0));
534        assert_eq!(stops.at(0.25), LinColor::new(0.5, 0.5, 0.0, 1.0));
535        assert_eq!(stops.at(0.75), LinColor::new(0.0, 0.5, 0.5, 1.0));
536        assert_eq!(stops.at(2.0), LinColor::new(0.0, 0.0, 1.0, 1.0));
537
538        Ok(())
539    }
540
541    #[test]
542    fn test_radial_grad() -> Result<(), Box<dyn std::error::Error>> {
543        let fcenter = Point::new(0.25, 0.0);
544        let center = Point::new(0.5, 0.0);
545        let grad = GradRadial::new(
546            vec![],
547            Units::BoundingBox,
548            true,
549            GradSpread::Pad,
550            Transform::identity(),
551            center,
552            0.5,
553            fcenter,
554            0.1,
555        );
556        assert!(grad.offset(fcenter).unwrap() < 0.0);
557        assert_approx_eq!(grad.offset(Point::new(0.675, 0.0)).unwrap(), 0.5);
558        assert_approx_eq!(grad.offset(Point::new(1.0, 0.0)).unwrap(), 1.0);
559
560        #[cfg(feature = "serde")]
561        {
562            let grad_str = serde_json::to_string(&grad)?;
563            assert_eq!(grad, serde_json::from_str(grad_str.as_str())?);
564        }
565
566        Ok(())
567    }
568
569    #[test]
570    fn test_ling_grad() -> Result<(), Box<dyn std::error::Error>> {
571        let c0 = "#89155180".parse()?;
572        let c1 = "#ff272d80".parse()?;
573        let c2 = "#ff272d00".parse()?;
574        let grad = GradLinear::new(
575            vec![
576                GradStop::new(0.0, c0),
577                GradStop::new(0.5, c1),
578                GradStop::new(1.0, c2),
579            ],
580            Units::UserSpaceOnUse,
581            true,
582            GradSpread::default(),
583            Transform::identity(),
584            (0.0, 0.0),
585            (1.0, 1.0),
586        );
587
588        assert_eq!(grad.at(Point::new(-0.5, -0.5)), c0);
589        assert_eq!(grad.at(Point::new(0.0, 0.0)), c0);
590        assert_eq!(grad.at(Point::new(1.0, 0.0)), c1);
591        assert_eq!(grad.at(Point::new(0.0, 1.0)), c1);
592        assert_eq!(grad.at(Point::new(1.0, 1.0)), c2);
593        assert_eq!(grad.at(Point::new(1.5, 1.5)), c2);
594
595        #[cfg(feature = "serde")]
596        {
597            let grad_str = serde_json::to_string(&grad)?;
598            assert_eq!(grad, serde_json::from_str(grad_str.as_str())?);
599        }
600
601        Ok(())
602    }
603}