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 enum GradientKind {
145 Linear {
147 start: Point,
149 end: Point,
151 },
152 Radial {
154 start_center: Point,
156 start_radius: f32,
158 end_center: Point,
160 end_radius: f32,
162 },
163 Sweep {
166 center: Point,
168 start_angle: f32,
170 end_angle: f32,
172 },
173}
174
175#[derive(Clone, PartialEq, Debug)]
177#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
178pub struct Gradient {
179 pub kind: GradientKind,
181 pub extend: Extend,
183 pub interpolation_cs: ColorSpaceTag,
189 pub hue_direction: HueDirection,
195 pub stops: ColorStops,
197}
198
199impl Default for Gradient {
200 fn default() -> Self {
201 Self {
202 kind: GradientKind::Linear {
203 start: Point::default(),
204 end: Point::default(),
205 },
206 extend: Extend::default(),
207 interpolation_cs: DEFAULT_GRADIENT_COLOR_SPACE,
208 hue_direction: HueDirection::default(),
209 stops: ColorStops::default(),
210 }
211 }
212}
213
214impl Gradient {
215 pub fn new_linear(start: impl Into<Point>, end: impl Into<Point>) -> Self {
217 Self {
218 kind: GradientKind::Linear {
219 start: start.into(),
220 end: end.into(),
221 },
222 extend: Extend::default(),
223 interpolation_cs: DEFAULT_GRADIENT_COLOR_SPACE,
224 hue_direction: HueDirection::default(),
225 stops: ColorStops::default(),
226 }
227 }
228
229 pub fn new_radial(center: impl Into<Point>, radius: f32) -> Self {
231 let center = center.into();
232 Self {
233 kind: GradientKind::Radial {
234 start_center: center,
235 start_radius: 0.0,
236 end_center: center,
237 end_radius: radius,
238 },
239 extend: Extend::default(),
240 interpolation_cs: DEFAULT_GRADIENT_COLOR_SPACE,
241 hue_direction: HueDirection::default(),
242 stops: ColorStops::default(),
243 }
244 }
245
246 pub fn new_two_point_radial(
248 start_center: impl Into<Point>,
249 start_radius: f32,
250 end_center: impl Into<Point>,
251 end_radius: f32,
252 ) -> Self {
253 Self {
254 kind: GradientKind::Radial {
255 start_center: start_center.into(),
256 start_radius,
257 end_center: end_center.into(),
258 end_radius,
259 },
260 extend: Extend::default(),
261 interpolation_cs: DEFAULT_GRADIENT_COLOR_SPACE,
262 hue_direction: HueDirection::default(),
263 stops: ColorStops::default(),
264 }
265 }
266
267 pub fn new_sweep(center: impl Into<Point>, start_angle: f32, end_angle: f32) -> Self {
270 Self {
271 kind: GradientKind::Sweep {
272 center: center.into(),
273 start_angle,
274 end_angle,
275 },
276 extend: Extend::default(),
277 interpolation_cs: DEFAULT_GRADIENT_COLOR_SPACE,
278 hue_direction: HueDirection::default(),
279 stops: ColorStops::default(),
280 }
281 }
282
283 #[must_use]
285 pub const fn with_extend(mut self, mode: Extend) -> Self {
286 self.extend = mode;
287 self
288 }
289
290 #[must_use]
292 pub const fn with_interpolation_cs(mut self, interpolation_cs: ColorSpaceTag) -> Self {
293 self.interpolation_cs = interpolation_cs;
294 self
295 }
296
297 #[must_use]
299 pub const fn with_hue_direction(mut self, hue_direction: HueDirection) -> Self {
300 self.hue_direction = hue_direction;
301 self
302 }
303
304 #[must_use]
306 pub fn with_stops(mut self, stops: impl ColorStopsSource) -> Self {
307 self.stops.clear();
308 stops.collect_stops(&mut self.stops);
309 self
310 }
311
312 #[must_use]
314 pub fn with_alpha(mut self, alpha: f32) -> Self {
315 self.stops
316 .iter_mut()
317 .for_each(|stop| *stop = stop.with_alpha(alpha));
318 self
319 }
320
321 #[must_use]
324 pub fn multiply_alpha(mut self, alpha: f32) -> Self {
325 self.stops
326 .iter_mut()
327 .for_each(|stop| *stop = stop.multiply_alpha(alpha));
328 self
329 }
330}
331
332pub trait ColorStopsSource {
334 fn collect_stops(self, stops: &mut ColorStops);
336}
337
338impl<T> ColorStopsSource for &'_ [T]
339where
340 T: Into<ColorStop> + Copy,
341{
342 fn collect_stops(self, stops: &mut ColorStops) {
343 for &stop in self {
344 stops.push(stop.into());
345 }
346 }
347}
348
349impl<T, const N: usize> ColorStopsSource for [T; N]
350where
351 T: Into<ColorStop>,
352{
353 fn collect_stops(self, stops: &mut ColorStops) {
354 for stop in self.into_iter() {
355 stops.push(stop.into());
356 }
357 }
358}
359
360impl<CS: ColorSpace> ColorStopsSource for &'_ [AlphaColor<CS>] {
361 fn collect_stops(self, stops: &mut ColorStops) {
362 if !self.is_empty() {
363 let denom = (self.len() - 1).max(1) as f32;
364 stops.extend(self.iter().enumerate().map(|(i, c)| ColorStop {
365 offset: (i as f32) / denom,
366 color: DynamicColor::from_alpha_color(*c),
367 }));
368 }
369 }
370}
371
372impl ColorStopsSource for &'_ [DynamicColor] {
373 fn collect_stops(self, stops: &mut ColorStops) {
374 if !self.is_empty() {
375 let denom = (self.len() - 1).max(1) as f32;
376 stops.extend(self.iter().enumerate().map(|(i, c)| ColorStop {
377 offset: (i as f32) / denom,
378 color: (*c),
379 }));
380 }
381 }
382}
383
384impl<CS: ColorSpace> ColorStopsSource for &'_ [OpaqueColor<CS>] {
385 fn collect_stops(self, stops: &mut ColorStops) {
386 if !self.is_empty() {
387 let denom = (self.len() - 1).max(1) as f32;
388 stops.extend(self.iter().enumerate().map(|(i, c)| ColorStop {
389 offset: (i as f32) / denom,
390 color: DynamicColor::from_alpha_color((*c).with_alpha(1.)),
391 }));
392 }
393 }
394}
395
396impl<const N: usize, CS: ColorSpace> ColorStopsSource for [AlphaColor<CS>; N] {
397 fn collect_stops(self, stops: &mut ColorStops) {
398 (&self[..]).collect_stops(stops);
399 }
400}
401impl<const N: usize> ColorStopsSource for [DynamicColor; N] {
402 fn collect_stops(self, stops: &mut ColorStops) {
403 (&self[..]).collect_stops(stops);
404 }
405}
406impl<const N: usize, CS: ColorSpace> ColorStopsSource for [OpaqueColor<CS>; N] {
407 fn collect_stops(self, stops: &mut ColorStops) {
408 (&self[..]).collect_stops(stops);
409 }
410}
411
412#[cfg(test)]
413mod tests {
414 extern crate alloc;
415 extern crate std;
416 use super::Gradient;
417 use alloc::vec;
418 use color::{cache_key::CacheKey, palette, parse_color};
419 use std::collections::HashSet;
420
421 #[test]
422 fn color_stops_cache() {
423 let mut set = HashSet::new();
424 let stops = Gradient::default()
425 .with_stops([palette::css::RED, palette::css::LIME, palette::css::BLUE])
426 .stops;
427 let stops_clone = stops.clone();
428 let parsed_gradient = Gradient::default().with_stops(
429 vec![
430 parse_color("red").unwrap(),
431 parse_color("lime").unwrap(),
432 parse_color("blue").unwrap(),
433 ]
434 .as_slice(),
435 );
436 let parsed_stops = parsed_gradient.stops.clone();
437 set.insert(CacheKey(stops));
438 assert!(set.contains(&CacheKey(stops_clone)));
440 set.insert(CacheKey(parsed_stops));
441 let new_grad = parsed_gradient.clone();
442 assert!(set.contains(&CacheKey(new_grad.stops)));
443 }
444}