Skip to content

Commit 196acff

Browse files
Generalize Gradient over collection with trait ArrayLike
1 parent 481d20d commit 196acff

File tree

1 file changed

+154
-58
lines changed

1 file changed

+154
-58
lines changed

palette/src/gradient.rs renamed to palette/src/gradient/mod.rs

Lines changed: 154 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
//! default).
55
66
use std::cmp::max;
7+
use std::ops::Index;
78

89
use approx::{AbsDiffEq, RelativeEq, UlpsEq};
910
use num_traits::{One, Zero};
@@ -12,6 +13,57 @@ use crate::float::Float;
1213
use crate::Mix;
1314
use crate::{from_f64, FromF64};
1415

16+
#[cfg(feature = "named_gradients")]
17+
pub mod named;
18+
19+
/// Types which represent an ordered collection of known size
20+
pub trait ArrayLike : Index<usize>{
21+
/// Returns the number of elements in the collection, also referred to as its 'length'.
22+
fn len(&self) -> usize;
23+
/// Returns a reference to the element at the position i or None if out of bounds.
24+
fn get(&self, i: usize) -> Option<&Self::Output>;
25+
/// Returns a pointer to the first element of the collection, or None if it is empty.
26+
fn first(&self) -> Option<&Self::Output> {
27+
self.get(0)
28+
}
29+
/// Returns a pointer to the last element of the collection, or None if it is empty.
30+
fn last(&self) -> Option<&Self::Output> {
31+
self.get(self.len() - 1)
32+
}
33+
/// Returns true if the collection contains no elements.
34+
fn is_empty(&self) -> bool {
35+
self.len() == 0
36+
}
37+
}
38+
39+
impl<T> ArrayLike for Vec<T> {
40+
fn len(&self) -> usize {
41+
self.len()
42+
}
43+
fn get(&self, i: usize) -> Option<&T> {
44+
self.as_slice().get(i)
45+
}
46+
}
47+
48+
impl<T, const N: usize> ArrayLike for [T;N] {
49+
fn len(&self) -> usize {
50+
<[T]>::len(self)
51+
}
52+
fn get(&self, i: usize) -> Option<&T> {
53+
<[T]>::get(self,i)
54+
}
55+
}
56+
57+
impl<C,T> From<T> for Gradient<C,T>
58+
where
59+
C: Mix + Clone,
60+
T: ArrayLike<Output = (C::Scalar, C)>
61+
{
62+
fn from(col: T) -> Self {
63+
Gradient(col)
64+
}
65+
}
66+
1567
/// A linear interpolation between colors.
1668
///
1769
/// It's used to smoothly transition between a series of colors, that can be
@@ -21,36 +73,16 @@ use crate::{from_f64, FromF64};
2173
/// the domain of the gradient will have the same color as the closest control
2274
/// point.
2375
#[derive(Clone, Debug)]
24-
pub struct Gradient<C: Mix + Clone>(Vec<(C::Scalar, C)>);
25-
26-
impl<C: Mix + Clone> Gradient<C> {
27-
/// Create a gradient of evenly spaced colors with the domain [0.0, 1.0].
28-
/// There must be at least one color.
29-
pub fn new<I: IntoIterator<Item = C>>(colors: I) -> Gradient<C>
30-
where
31-
C::Scalar: FromF64,
32-
{
33-
let mut points: Vec<_> = colors.into_iter().map(|c| (C::Scalar::zero(), c)).collect();
34-
assert!(!points.is_empty());
35-
let step_size = C::Scalar::one() / from_f64(max(points.len() - 1, 1) as f64);
36-
37-
for (i, &mut (ref mut p, _)) in points.iter_mut().enumerate() {
38-
*p = from_f64::<C::Scalar>(i as f64) * step_size;
39-
}
40-
41-
Gradient(points)
42-
}
43-
44-
/// Create a gradient of colors with custom spacing and domain. There must
45-
/// be at least one color and they are expected to be ordered by their
46-
/// position value.
47-
pub fn with_domain(colors: Vec<(C::Scalar, C)>) -> Gradient<C> {
48-
assert!(!colors.is_empty());
49-
50-
//Maybe sort the colors?
51-
Gradient(colors)
52-
}
76+
pub struct Gradient<C, T = Vec<(<C as Mix>::Scalar, C)>>(T)
77+
where
78+
C: Mix + Clone,
79+
T: ArrayLike<Output = (C::Scalar, C)>;
5380

81+
impl<C,T> Gradient<C,T>
82+
where
83+
C: Mix + Clone,
84+
T: ArrayLike<Output = (C::Scalar, C)>
85+
{
5486
/// Get a color from the gradient. The color of the closest control point
5587
/// will be returned if `i` is outside the domain.
5688
pub fn get(&self, i: C::Scalar) -> C {
@@ -97,6 +129,16 @@ impl<C: Mix + Clone> Gradient<C> {
97129
min_color.mix(max_color, factor)
98130
}
99131

132+
/// Create a gradient of colors with custom spacing and domain. There must
133+
/// be at least one color and they are expected to be ordered by their
134+
/// position value.
135+
pub fn with_domain(colors: T) -> Gradient<C, T> {
136+
assert!(!colors.is_empty());
137+
138+
//Maybe sort the colors?
139+
Gradient(colors)
140+
}
141+
100142
/// Take `n` evenly spaced colors from the gradient, as an iterator. The
101143
/// iterator includes both ends of the gradient, for `n > 1`, or just
102144
/// the lower end of the gradient for `n = 0`.
@@ -125,7 +167,7 @@ impl<C: Mix + Clone> Gradient<C> {
125167
/// assert_relative_eq!(c1, c2);
126168
/// }
127169
/// ```
128-
pub fn take(&self, n: usize) -> Take<C> {
170+
pub fn take(&self, n: usize) -> Take<C,T> {
129171
let (min, max) = self.domain();
130172

131173
Take {
@@ -139,7 +181,7 @@ impl<C: Mix + Clone> Gradient<C> {
139181
}
140182

141183
/// Slice this gradient to limit its domain.
142-
pub fn slice<R: Into<Range<C::Scalar>>>(&self, range: R) -> Slice<C> {
184+
pub fn slice<R: Into<Range<C::Scalar>>>(&self, range: R) -> Slice<C,T> {
143185
Slice {
144186
gradient: self,
145187
range: range.into(),
@@ -160,20 +202,45 @@ impl<C: Mix + Clone> Gradient<C> {
160202
}
161203
}
162204

205+
impl<C: Mix + Clone> Gradient<C> {
206+
/// Create a gradient of evenly spaced colors with the domain [0.0, 1.0].
207+
/// There must be at least one color.
208+
pub fn new<I: IntoIterator<Item = C>>(colors: I) -> Gradient<C>
209+
where
210+
C::Scalar: FromF64,
211+
{
212+
let mut points: Vec<_> = colors.into_iter().map(|c| (C::Scalar::zero(), c)).collect();
213+
assert!(!points.is_empty());
214+
let step_size = C::Scalar::one() / from_f64(max(points.len() - 1, 1) as f64);
215+
216+
for (i, &mut (ref mut p, _)) in points.iter_mut().enumerate() {
217+
*p = from_f64::<C::Scalar>(i as f64) * step_size;
218+
}
219+
220+
Gradient(points)
221+
}
222+
}
223+
163224
/// An iterator over interpolated colors.
164225
#[derive(Clone)]
165-
pub struct Take<'a, C: Mix + Clone + 'a> {
166-
gradient: MaybeSlice<'a, C>,
226+
pub struct Take<'a, C, T = Vec<(<C as Mix>::Scalar, C)>>
227+
where
228+
C: Mix + Clone + 'a,
229+
T: ArrayLike<Output = (C::Scalar, C)>
230+
{
231+
gradient: MaybeSlice<'a, C, T>,
167232
from: C::Scalar,
168233
diff: C::Scalar,
169234
len: usize,
170235
from_head: usize,
171236
from_end: usize,
172237
}
173238

174-
impl<'a, C: Mix + Clone> Iterator for Take<'a, C>
239+
impl<'a, C, T> Iterator for Take<'a, C, T>
175240
where
176241
C::Scalar: FromF64,
242+
C: Mix + Clone,
243+
T: ArrayLike<Output = (C::Scalar, C)>
177244
{
178245
type Item = C;
179246

@@ -202,11 +269,18 @@ where
202269
}
203270
}
204271

205-
impl<'a, C: Mix + Clone> ExactSizeIterator for Take<'a, C> where C::Scalar: FromF64 {}
272+
impl<'a, C, T> ExactSizeIterator for Take<'a, C, T>
273+
where
274+
C::Scalar: FromF64,
275+
C: Mix + Clone,
276+
T: ArrayLike<Output = (C::Scalar, C)>
277+
{}
206278

207-
impl<'a, C: Mix + Clone> DoubleEndedIterator for Take<'a, C>
279+
impl<'a, C, T> DoubleEndedIterator for Take<'a, C, T>
208280
where
209281
C::Scalar: FromF64,
282+
C: Mix + Clone,
283+
T: ArrayLike<Output = (C::Scalar, C)>
210284
{
211285
fn next_back(&mut self) -> Option<Self::Item> {
212286
if self.from_head + self.from_end < self.len {
@@ -228,35 +302,29 @@ where
228302

229303
/// A slice of a Gradient that limits its domain.
230304
#[derive(Clone, Debug)]
231-
pub struct Slice<'a, C: Mix + Clone + 'a> {
232-
gradient: &'a Gradient<C>,
305+
pub struct Slice<'a, C,T = Vec<(<C as Mix>::Scalar, C)>>
306+
where
307+
C: Mix + Clone + 'a,
308+
T: ArrayLike<Output = (C::Scalar, C)>
309+
{
310+
gradient: &'a Gradient<C,T>,
233311
range: Range<C::Scalar>,
234312
}
235313

236-
impl<'a, C: Mix + Clone> Slice<'a, C> {
314+
impl<'a, C, T> Slice<'a, C, T>
315+
where
316+
C: Mix + Clone + 'a,
317+
T: ArrayLike<Output = (C::Scalar, C)>
318+
{
237319
/// Get a color from the gradient slice. The color of the closest domain
238320
/// limit will be returned if `i` is outside the domain.
239321
pub fn get(&self, i: C::Scalar) -> C {
240322
self.gradient.get(self.range.clamp(i))
241323
}
242324

243-
/// Take `n` evenly spaced colors from the gradient slice, as an iterator.
244-
pub fn take(&self, n: usize) -> Take<C> {
245-
let (min, max) = self.domain();
246-
247-
Take {
248-
gradient: MaybeSlice::Slice(self.clone()),
249-
from: min,
250-
diff: max - min,
251-
len: n,
252-
from_head: 0,
253-
from_end: 0,
254-
}
255-
}
256-
257325
/// Slice this gradient slice to further limit its domain. Ranges outside
258326
/// the domain will be clamped to the nearest domain limit.
259-
pub fn slice<R: Into<Range<C::Scalar>>>(&self, range: R) -> Slice<C> {
327+
pub fn slice<R: Into<Range<C::Scalar>>>(&self, range: R) -> Slice<C,T> {
260328
Slice {
261329
gradient: self.gradient,
262330
range: self.range.constrain(&range.into()),
@@ -278,6 +346,26 @@ impl<'a, C: Mix + Clone> Slice<'a, C> {
278346
}
279347
}
280348

349+
impl<'a, C, T> Slice<'a, C, T>
350+
where
351+
C: Mix + Clone + 'a,
352+
T: ArrayLike<Output = (C::Scalar, C)> + Clone
353+
{
354+
/// Take `n` evenly spaced colors from the gradient slice, as an iterator.
355+
pub fn take(&self, n: usize) -> Take<C, T> {
356+
let (min, max) = self.domain();
357+
358+
Take {
359+
gradient: MaybeSlice::Slice(self.clone()),
360+
from: min,
361+
diff: max - min,
362+
len: n,
363+
from_head: 0,
364+
from_end: 0,
365+
}
366+
}
367+
}
368+
281369
/// A domain range for gradient slices.
282370
#[derive(Clone, Debug, PartialEq)]
283371
pub struct Range<T: Float> {
@@ -449,12 +537,20 @@ where
449537
}
450538

451539
#[derive(Clone)]
452-
enum MaybeSlice<'a, C: Mix + Clone + 'a> {
453-
NotSlice(&'a Gradient<C>),
454-
Slice(Slice<'a, C>),
540+
enum MaybeSlice<'a, C, T = Vec<(<C as Mix>::Scalar, C)>>
541+
where
542+
C: Mix + Clone + 'a,
543+
T: ArrayLike<Output = (C::Scalar, C)>
544+
{
545+
NotSlice(&'a Gradient<C, T>),
546+
Slice(Slice<'a, C, T>),
455547
}
456548

457-
impl<'a, C: Mix + Clone> MaybeSlice<'a, C> {
549+
impl<'a, C, T> MaybeSlice<'a, C, T>
550+
where
551+
C: Mix + Clone + 'a,
552+
T: ArrayLike<Output = (C::Scalar, C)>
553+
{
458554
fn get(&self, i: C::Scalar) -> C {
459555
match *self {
460556
MaybeSlice::NotSlice(g) => g.get(i),

0 commit comments

Comments
 (0)