Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make Take iterator for gradient inclusive of both end colors, add tests #158

Merged
merged 1 commit into from Dec 15, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Expand Up @@ -99,7 +99,7 @@ This results in the following three colors:

There is also a linear gradient type which makes it easy to interpolate between a series of colors. This gradient can be used in any color space and it can be used to make color sequence iterators.

The following example shows two gradients between the same two endpoints, but one is in RGB and the other in is HSV space.
The following example shows three gradients between the same two endpoints, but the top is in RGB space while the middle and bottom are in HSV space. The bottom gradient is an example of using the color sequence iterator.

```Rust
extern crate palette;
Expand All @@ -116,7 +116,7 @@ let grad2 = Gradient::new(vec![
]);
```

The RGB gradient goes through gray, while the HSV gradients changes only the hue:
The RGB gradient goes through gray, while the HSV gradients only change hue:

![Gradient Comparison](gfx/readme_gradients.png)

Expand Down
Binary file modified gfx/readme_gradients.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
22 changes: 21 additions & 1 deletion palette/examples/readme_examples.rs
Expand Up @@ -93,7 +93,7 @@ fn display_gradients<A: Mix<Scalar = f32> + Clone, B: Mix<Scalar = f32> + Clone>
LinSrgb: From<A>,
LinSrgb: From<B>,
{
let mut image = RgbImage::new(256, 64);
let mut image = RgbImage::new(256, 96);
{
let mut sub_image = image.sub_image(0, 0, 256, 32);
let (width, height) = sub_image.dimensions();
Expand Down Expand Up @@ -130,6 +130,26 @@ fn display_gradients<A: Mix<Scalar = f32> + Clone, B: Mix<Scalar = f32> + Clone>
}
}

{
let mut sub_image = image.sub_image(0, 64, 256, 32);
let swatch_size = 32;
let mut v1 = Vec::new();
for color in grad2.take(8) {
let pix: [u8; 3] = Srgb::from_linear(LinSrgb::from(color))
.into_format()
.into_raw();
v1.push(pix);
}
for (s, color) in v1.into_iter().enumerate() {
for x in (s * swatch_size)..((s + 1) * swatch_size) {
for y in 0..swatch_size {
let pixel = sub_image.get_pixel_mut(x as u32, y as u32);
*pixel = image::Rgb(color);
}
}
}
}

match image.save(filename) {
Ok(()) => println!("see '{}' for the result", filename),
Err(e) => println!("failed to write '{}': {}", filename, e),
Expand Down
77 changes: 69 additions & 8 deletions palette/src/gradient.rs
Expand Up @@ -92,7 +92,34 @@ impl<C: Mix + Clone> Gradient<C> {
min_color.mix(max_color, factor)
}

///Take `n` evenly spaced colors from the gradient, as an iterator.
///Take `n` evenly spaced colors from the gradient, as an iterator. The
///iterator includes both ends of the gradient, for `n > 1`, or just
///the lower end of the gradient for `n = 0`.
///
///For example, `take(5)` will include point 0.0 of the gradient, three
///intermediate colors, and point 1.0 spaced apart at 1/4 the distance
///between colors 0.0 and 1.0 on the gradient.
/// ```
/// #[macro_use] extern crate approx;
/// use palette::{Gradient, LinSrgb};
///
/// let gradient = Gradient::new(vec![
/// LinSrgb::new(1.0, 1.0, 0.0),
/// LinSrgb::new(0.0, 0.0, 1.0),
/// ]);
///
/// let taken_colors: Vec<_> = gradient.take(5).collect();
/// let colors = vec![
/// LinSrgb::new(1.0, 1.0, 0.0),
/// LinSrgb::new(0.75, 0.75, 0.25),
/// LinSrgb::new(0.5, 0.5, 0.5),
/// LinSrgb::new(0.25, 0.25, 0.75),
/// LinSrgb::new(0.0, 0.0, 1.0),
/// ];
/// for (c1, c2) in taken_colors.iter().zip(colors.iter()) {
/// assert_relative_eq!(c1, c2);
/// }
/// ```
pub fn take(&self, n: usize) -> Take<C> {
let (min, max) = self.domain();

Expand Down Expand Up @@ -142,9 +169,14 @@ impl<'a, C: Mix + Clone> Iterator for Take<'a, C> {

fn next(&mut self) -> Option<C> {
if self.from_head + self.from_end < self.len {
let i = self.from + (self.diff / cast(self.len)) * cast(self.from_head);
self.from_head += 1;
Some(self.gradient.get(i))
if self.len == 1 {
self.from_head += 1;
Some(self.gradient.get(self.from))
} else {
let i = self.from + (self.diff / cast(self.len - 1)) * cast(self.from_head);
self.from_head += 1;
Some(self.gradient.get(i))
}
} else {
None
}
Expand All @@ -160,9 +192,14 @@ impl<'a, C: Mix + Clone> ExactSizeIterator for Take<'a, C> {}
impl<'a, C: Mix + Clone> DoubleEndedIterator for Take<'a, C> {
fn next_back(&mut self) -> Option<Self::Item> {
if self.from_head + self.from_end < self.len {
let i = self.from + (self.diff / cast(self.len)) * cast(self.len - self.from_end - 1);
self.from_end += 1;
Some(self.gradient.get(i))
if self.len == 1 {
self.from_end += 1;
Some(self.gradient.get(self.from))
} else {
let i = self.from + (self.diff / cast(self.len - 1)) * cast(self.len - self.from_end - 1);
self.from_end += 1;
Some(self.gradient.get(i))
}
} else {
None
}
Expand Down Expand Up @@ -439,7 +476,7 @@ mod test {
]);
let g2 = g1.slice(..0.5);

let v1: Vec<_> = g1.take(10).take(5).collect();
let v1: Vec<_> = g1.take(9).take(5).collect();
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

9 items is 8 "jumps" and 5 items is 4 "jumps". 5 ends at the middle of 9. It's slightly mind bending (for my post work Friday brain), but it checks out.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I had to use pen and paper to reason about it. My thinking was the interval between each color step is 1/(n-1) for n > 1. So 9 is 1/8, 10 is 1/9, etc.

When you take 9, you're getting 7 intermediate colors that are 8 "jumps" apart. At first, this can be confusing when reasoning about what to take if you want a specific interval. I wanted to add a short but hopefully clear explanation of this in the docs so future people can avoid bending minds.

let v2: Vec<_> = g2.take(5).collect();
for (t1, t2) in v1.iter().zip(v2.iter()) {
assert_relative_eq!(t1, t2);
Expand All @@ -456,5 +493,29 @@ mod test {
let v1: Vec<_> = g.take(10).collect::<Vec<_>>().iter().rev().cloned().collect();
let v2: Vec<_> = g.take(10).rev().collect();
assert_eq!(v1, v2);

//make sure `take(1).rev()` doesn't produce NaN results
let v1: Vec<_> = g.take(1).collect::<Vec<_>>().iter().rev().cloned().collect();
let v2: Vec<_> = g.take(1).rev().collect();
assert_eq!(v1, v2);
}

#[test]
fn inclusive_take() {
let g = Gradient::new(vec![
LinSrgb::new(1.0, 1.0, 0.0),
LinSrgb::new(0.0, 0.0, 1.0),
]);

//take(0) returns None
let v1: Vec<_> = g.take(0).collect();
assert_eq!(v1.len(), 0);
//`Take` produces minimum gradient boundary for n=1
let v1: Vec<_> = g.take(1).collect();
assert_eq!(v1[0], LinSrgb::new(1.0, 1.0, 0.0));
//`Take` includes the maximum gradient color
let v1: Vec<_> = g.take(5).collect();
assert_eq!(v1[0], LinSrgb::new(1.0, 1.0, 0.0));
assert_eq!(v1[4], LinSrgb::new(0.0, 0.0, 1.0));
}
}