From 6839872bda1f2ced7f7dc32bce787318c6a3e0b1 Mon Sep 17 00:00:00 2001 From: okaneco <47607823+okaneco@users.noreply.github.com> Date: Thu, 12 Dec 2019 01:33:38 -0500 Subject: [PATCH] Make `Take` iterator inclusive Update simple slice test for new gradient take behavior Update documentation to note take is inclusive, add doc test Update README.md and readme_examples.rs to include stepped gradient Update /gfx/readme_gradients.png with take gradient --- README.md | 4 +- gfx/readme_gradients.png | Bin 475 -> 598 bytes palette/examples/readme_examples.rs | 22 +++++++- palette/src/gradient.rs | 77 +++++++++++++++++++++++++--- 4 files changed, 92 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 5fdaac094..5a0c9b5bb 100644 --- a/README.md +++ b/README.md @@ -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; @@ -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) diff --git a/gfx/readme_gradients.png b/gfx/readme_gradients.png index 5c24eb8b995d3ce97d326b870842d9d3e8f9b027..d6ee8ac2d8e8a7bd75813b8624e643a9a4045415 100644 GIT binary patch literal 598 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K55;&NEWbI_@`#_2%-O<;Pfnj4m_n$;oAYUQb zBgmJ5p-PQ`p`nF=;TKS-;RORjsR0ASs{{rHs~HRo;stYd1=;{5*pj^6T^Rm@;DWu& zCj*T?@9E+gQgQ3;9s8hW2Z{EF_a{YE^IL24KjeI4vuv&VM;W&b6D$s-P5iO`!gI;A z_P+@}CY9=8nuqVk?#k0pUCI+XJ!snPpth3*cl6esyZ*WD`$$gpPxOxB~$lhag=%f{Pq5Rak+OlSj6WoH_wsad(Qv+)x3XKuI{^4)>!Yuz`?-6 zz{J4Vz~I23z#u>@_ksD_$xB|JcHQKEQ}y?g+O_@fuG|WJuHJv?-n5@fa-ySezx2$u zxY-x4IR9hH#<$_^wbi>f?VG=3)|cMg+C3r9ySLi$eg0}`+4H`!`zQB*|G0lDN7Whr zu`-eB4v3@wOsbtEb4FVdQ I&MBb@0O~5$jsO4v literal 475 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K595|SOWR<<$OQ66aPZ!6K3dXmWJ@XD5@URAW zzEv&$pS{J8eQOijvCz#Y)c6%T^1HS%O6$u{_daDgwdn7g*d5a*?K0rsZTHq^`DcTr zo6k=1`}W#t$Np!VTfctkJ89DI_x$P8Y4&Z~7pF1wM9e=NDngVhYeL({|Q-!bd-kNakt*0cS-XBhTs^Oow`M8P*&qQ=S3j3^ HP6 + Clone, B: Mix + Clone> LinSrgb: From, LinSrgb: From, { - 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(); @@ -130,6 +130,26 @@ fn display_gradients + Clone, B: Mix + 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), diff --git a/palette/src/gradient.rs b/palette/src/gradient.rs index f78ffa0ac..4dfd3b5a1 100644 --- a/palette/src/gradient.rs +++ b/palette/src/gradient.rs @@ -92,7 +92,34 @@ impl Gradient { 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 { let (min, max) = self.domain(); @@ -142,9 +169,14 @@ impl<'a, C: Mix + Clone> Iterator for Take<'a, C> { fn next(&mut self) -> Option { 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 } @@ -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 { 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 } @@ -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(); let v2: Vec<_> = g2.take(5).collect(); for (t1, t2) in v1.iter().zip(v2.iter()) { assert_relative_eq!(t1, t2); @@ -456,5 +493,29 @@ mod test { let v1: Vec<_> = g.take(10).collect::>().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::>().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)); } }