1use super::Extend;
5
6use color::{
7 cache_key::{BitEq, BitHash},
8 AlphaColor, ColorSpace, ColorSpaceTag, DynamicColor, HueDirection, OpaqueColor,
9};
10use kurbo::Point;
11use smallvec::SmallVec;
12
13use core::{
14 hash::Hasher,
15 ops::{Deref, DerefMut},
16};
17
18const DEFAULT_GRADIENT_COLOR_SPACE: ColorSpaceTag = ColorSpaceTag::Srgb;
22
23#[derive(Copy, Clone, Debug, PartialEq)]
27#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
28pub struct ColorStop {
29 pub offset: f32,
31 pub color: DynamicColor,
33}
34
35impl BitHash for ColorStop {
36 fn bit_hash<H: Hasher>(&self, state: &mut H) {
37 self.offset.bit_hash(state);
38 self.color.bit_hash(state);
39 }
40}
41
42impl BitEq for ColorStop {
43 fn bit_eq(&self, other: &Self) -> bool {
44 self.offset.bit_eq(&other.offset) && self.color.bit_eq(&other.color)
45 }
46}
47
48impl ColorStop {
49 #[must_use]
51 pub const fn with_alpha(self, alpha: f32) -> Self {
52 Self {
53 offset: self.offset,
54 color: self.color.with_alpha(alpha),
55 }
56 }
57
58 #[must_use]
63 pub const fn multiply_alpha(self, alpha: f32) -> Self {
64 Self {
65 offset: self.offset,
66 color: self.color.multiply_alpha(alpha),
67 }
68 }
69}
70
71impl<CS: ColorSpace> From<(f32, AlphaColor<CS>)> for ColorStop {
72 fn from(pair: (f32, AlphaColor<CS>)) -> Self {
73 Self {
74 offset: pair.0,
75 color: DynamicColor::from_alpha_color(pair.1),
76 }
77 }
78}
79
80impl From<(f32, DynamicColor)> for ColorStop {
81 fn from(pair: (f32, DynamicColor)) -> Self {
82 Self {
83 offset: pair.0,
84 color: pair.1,
85 }
86 }
87}
88
89impl<CS: ColorSpace> From<(f32, OpaqueColor<CS>)> for ColorStop {
90 fn from(pair: (f32, OpaqueColor<CS>)) -> Self {
91 Self {
92 offset: pair.0,
93 color: DynamicColor::from_alpha_color(pair.1.with_alpha(1.)),
94 }
95 }
96}
97
98#[derive(Clone, PartialEq, Debug, Default)]
100#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
101pub struct ColorStops(pub SmallVec<[ColorStop; 4]>);
102
103impl Deref for ColorStops {
104 type Target = SmallVec<[ColorStop; 4]>;
105 fn deref(&self) -> &Self::Target {
106 &self.0
107 }
108}
109
110impl DerefMut for ColorStops {
111 fn deref_mut(&mut self) -> &mut Self::Target {
112 &mut self.0
113 }
114}
115
116impl ColorStops {
117 pub fn new() -> Self {
119 Self::default()
120 }
121}
122
123impl BitEq for ColorStops {
124 fn bit_eq(&self, other: &Self) -> bool {
125 self.as_slice().bit_eq(other.as_slice())
126 }
127}
128
129impl BitHash for ColorStops {
130 fn bit_hash<H: Hasher>(&self, state: &mut H) {
131 self.as_slice().bit_hash(state);
132 }
133}
134
135impl From<&[ColorStop]> for ColorStops {
136 fn from(slice: &[ColorStop]) -> Self {
137 Self(slice.into())
138 }
139}
140
141#[derive(Copy, Clone, PartialEq, Debug)]
143#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
144pub struct LinearGradientPosition {
145 pub start: Point,
147 pub end: Point,
149}
150
151impl LinearGradientPosition {
152 pub fn new(start: impl Into<Point>, end: impl Into<Point>) -> Self {
154 Self {
155 start: start.into(),
156 end: end.into(),
157 }
158 }
159}
160
161#[derive(Copy, Clone, PartialEq, Debug)]
163#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
164pub struct RadialGradientPosition {
165 pub start_center: Point,
167 pub start_radius: f32,
169 pub end_center: Point,
171 pub end_radius: f32,
173}
174
175impl RadialGradientPosition {
176 pub fn new(center: impl Into<Point>, radius: f32) -> Self {
178 let center = center.into();
179 Self {
180 start_center: center,
181 start_radius: 0.0,
182 end_center: center,
183 end_radius: radius,
184 }
185 }
186 pub fn new_two_point(
188 start_center: impl Into<Point>,
189 start_radius: f32,
190 end_center: impl Into<Point>,
191 end_radius: f32,
192 ) -> Self {
193 Self {
194 start_center: start_center.into(),
195 start_radius,
196 end_center: end_center.into(),
197 end_radius,
198 }
199 }
200}
201
202#[derive(Copy, Clone, PartialEq, Debug)]
208#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
209pub struct SweepGradientPosition {
210 pub center: Point,
212 pub start_angle: f32,
216 pub end_angle: f32,
220}
221
222impl SweepGradientPosition {
223 pub fn new(center: impl Into<Point>, start_angle: f32, end_angle: f32) -> Self {
225 Self {
226 center: center.into(),
227 start_angle,
228 end_angle,
229 }
230 }
231}
232
233#[derive(Clone, Copy, Default, Debug, PartialEq)]
236#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
237pub enum InterpolationAlphaSpace {
238 #[default]
251 Premultiplied = 0,
252 Unpremultiplied = 1,
264}
265
266#[derive(Copy, Clone, PartialEq, Debug)]
268#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
269pub enum GradientKind {
270 Linear(LinearGradientPosition),
272 Radial(RadialGradientPosition),
274 Sweep(SweepGradientPosition),
277}
278
279impl From<LinearGradientPosition> for GradientKind {
280 #[inline(always)]
281 fn from(value: LinearGradientPosition) -> Self {
282 Self::Linear(value)
283 }
284}
285impl From<RadialGradientPosition> for GradientKind {
286 #[inline(always)]
287 fn from(value: RadialGradientPosition) -> Self {
288 Self::Radial(value)
289 }
290}
291impl From<SweepGradientPosition> for GradientKind {
292 #[inline(always)]
293 fn from(value: SweepGradientPosition) -> Self {
294 Self::Sweep(value)
295 }
296}
297
298#[derive(Clone, PartialEq, Debug)]
300#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
301pub struct Gradient {
302 pub kind: GradientKind,
304 pub extend: Extend,
306 pub interpolation_cs: ColorSpaceTag,
313 pub hue_direction: HueDirection,
319 pub interpolation_alpha_space: InterpolationAlphaSpace,
321 pub stops: ColorStops,
323}
324
325impl Default for Gradient {
326 fn default() -> Self {
327 Self {
328 kind: LinearGradientPosition {
329 start: Point::default(),
330 end: Point::default(),
331 }
332 .into(),
333 extend: Extend::default(),
334 interpolation_cs: DEFAULT_GRADIENT_COLOR_SPACE,
335 hue_direction: HueDirection::default(),
336 interpolation_alpha_space: InterpolationAlphaSpace::default(),
337 stops: ColorStops::default(),
338 }
339 }
340}
341
342impl Gradient {
343 pub fn new_linear(start: impl Into<Point>, end: impl Into<Point>) -> Self {
345 Self {
346 kind: LinearGradientPosition::new(start, end).into(),
347 extend: Extend::default(),
348 interpolation_cs: DEFAULT_GRADIENT_COLOR_SPACE,
349 hue_direction: HueDirection::default(),
350 interpolation_alpha_space: InterpolationAlphaSpace::default(),
351 stops: ColorStops::default(),
352 }
353 }
354
355 pub fn new_radial(center: impl Into<Point>, radius: f32) -> Self {
357 let center = center.into();
358 Self {
359 kind: RadialGradientPosition::new(center, radius).into(),
360 extend: Extend::default(),
361 interpolation_cs: DEFAULT_GRADIENT_COLOR_SPACE,
362 hue_direction: HueDirection::default(),
363 interpolation_alpha_space: InterpolationAlphaSpace::default(),
364 stops: ColorStops::default(),
365 }
366 }
367
368 pub fn new_two_point_radial(
370 start_center: impl Into<Point>,
371 start_radius: f32,
372 end_center: impl Into<Point>,
373 end_radius: f32,
374 ) -> Self {
375 Self {
376 kind: RadialGradientPosition::new_two_point(
377 start_center,
378 start_radius,
379 end_center,
380 end_radius,
381 )
382 .into(),
383 extend: Extend::default(),
384 interpolation_cs: DEFAULT_GRADIENT_COLOR_SPACE,
385 hue_direction: HueDirection::default(),
386 interpolation_alpha_space: InterpolationAlphaSpace::default(),
387 stops: ColorStops::default(),
388 }
389 }
390
391 pub fn new_sweep(center: impl Into<Point>, start_angle: f32, end_angle: f32) -> Self {
394 Self {
395 kind: SweepGradientPosition::new(center, start_angle, end_angle).into(),
396 extend: Extend::default(),
397 interpolation_cs: DEFAULT_GRADIENT_COLOR_SPACE,
398 hue_direction: HueDirection::default(),
399 interpolation_alpha_space: InterpolationAlphaSpace::default(),
400 stops: ColorStops::default(),
401 }
402 }
403
404 #[must_use]
406 pub const fn with_extend(mut self, mode: Extend) -> Self {
407 self.extend = mode;
408 self
409 }
410
411 #[must_use]
413 pub const fn with_interpolation_cs(mut self, interpolation_cs: ColorSpaceTag) -> Self {
414 self.interpolation_cs = interpolation_cs;
415 self
416 }
417
418 #[must_use]
420 pub const fn with_interpolation_alpha_space(
421 mut self,
422 interpolation_alpha_space: InterpolationAlphaSpace,
423 ) -> Self {
424 self.interpolation_alpha_space = interpolation_alpha_space;
425 self
426 }
427
428 #[must_use]
430 pub const fn with_hue_direction(mut self, hue_direction: HueDirection) -> Self {
431 self.hue_direction = hue_direction;
432 self
433 }
434
435 #[must_use]
437 pub fn with_stops(mut self, stops: impl ColorStopsSource) -> Self {
438 self.stops.clear();
439 stops.collect_stops(&mut self.stops);
440 self
441 }
442
443 #[must_use]
445 pub fn with_alpha(mut self, alpha: f32) -> Self {
446 self.stops
447 .iter_mut()
448 .for_each(|stop| *stop = stop.with_alpha(alpha));
449 self
450 }
451
452 #[must_use]
455 pub fn multiply_alpha(mut self, alpha: f32) -> Self {
456 self.stops
457 .iter_mut()
458 .for_each(|stop| *stop = stop.multiply_alpha(alpha));
459 self
460 }
461}
462
463pub trait ColorStopsSource {
465 fn collect_stops(self, stops: &mut ColorStops);
467}
468
469impl<T> ColorStopsSource for &'_ [T]
470where
471 T: Into<ColorStop> + Copy,
472{
473 fn collect_stops(self, stops: &mut ColorStops) {
474 for &stop in self {
475 stops.push(stop.into());
476 }
477 }
478}
479
480impl<T, const N: usize> ColorStopsSource for [T; N]
481where
482 T: Into<ColorStop>,
483{
484 fn collect_stops(self, stops: &mut ColorStops) {
485 for stop in self.into_iter() {
486 stops.push(stop.into());
487 }
488 }
489}
490
491impl<CS: ColorSpace> ColorStopsSource for &'_ [AlphaColor<CS>] {
492 fn collect_stops(self, stops: &mut ColorStops) {
493 if !self.is_empty() {
494 let denom = (self.len() - 1).max(1) as f32;
495 stops.extend(self.iter().enumerate().map(|(i, c)| ColorStop {
496 offset: (i as f32) / denom,
497 color: DynamicColor::from_alpha_color(*c),
498 }));
499 }
500 }
501}
502
503impl ColorStopsSource for &'_ [DynamicColor] {
504 fn collect_stops(self, stops: &mut ColorStops) {
505 if !self.is_empty() {
506 let denom = (self.len() - 1).max(1) as f32;
507 stops.extend(self.iter().enumerate().map(|(i, c)| ColorStop {
508 offset: (i as f32) / denom,
509 color: (*c),
510 }));
511 }
512 }
513}
514
515impl<CS: ColorSpace> ColorStopsSource for &'_ [OpaqueColor<CS>] {
516 fn collect_stops(self, stops: &mut ColorStops) {
517 if !self.is_empty() {
518 let denom = (self.len() - 1).max(1) as f32;
519 stops.extend(self.iter().enumerate().map(|(i, c)| ColorStop {
520 offset: (i as f32) / denom,
521 color: DynamicColor::from_alpha_color((*c).with_alpha(1.)),
522 }));
523 }
524 }
525}
526
527impl<const N: usize, CS: ColorSpace> ColorStopsSource for [AlphaColor<CS>; N] {
528 fn collect_stops(self, stops: &mut ColorStops) {
529 (&self[..]).collect_stops(stops);
530 }
531}
532impl<const N: usize> ColorStopsSource for [DynamicColor; N] {
533 fn collect_stops(self, stops: &mut ColorStops) {
534 (&self[..]).collect_stops(stops);
535 }
536}
537impl<const N: usize, CS: ColorSpace> ColorStopsSource for [OpaqueColor<CS>; N] {
538 fn collect_stops(self, stops: &mut ColorStops) {
539 (&self[..]).collect_stops(stops);
540 }
541}
542
543#[cfg(test)]
544mod tests {
545 extern crate alloc;
546 extern crate std;
547 use super::Gradient;
548 use alloc::vec;
549 use color::{cache_key::CacheKey, palette, parse_color};
550 use std::collections::HashSet;
551
552 #[test]
553 fn color_stops_cache() {
554 let mut set = HashSet::new();
555 let stops = Gradient::default()
556 .with_stops([palette::css::RED, palette::css::LIME, palette::css::BLUE])
557 .stops;
558 let stops_clone = stops.clone();
559 let parsed_gradient = Gradient::default().with_stops(
560 vec![
561 parse_color("red").unwrap(),
562 parse_color("lime").unwrap(),
563 parse_color("blue").unwrap(),
564 ]
565 .as_slice(),
566 );
567 let parsed_stops = parsed_gradient.stops.clone();
568 set.insert(CacheKey(stops));
569 assert!(set.contains(&CacheKey(stops_clone)));
571 set.insert(CacheKey(parsed_stops));
572 let new_grad = parsed_gradient.clone();
573 assert!(set.contains(&CacheKey(new_grad.stops)));
574 }
575}