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#[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 Pad,
16 Repeat,
18 Reflect,
20}
21
22impl GradSpread {
23 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#[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#[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#[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 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 #[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 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#[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#[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 #[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 fn offset(&self, point: Point) -> Option<Scalar> {
362 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#[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}