Skip to content

Commit

Permalink
Merge #205
Browse files Browse the repository at this point in the history
205: Generalizing gradients and add constant gradients r=Ogeon a=NicolasKlenert

This pull request contains two commits:

### 1. Generalization of  `Gradient` 

This allows creation of gradients from arrays. The inner collection type only has to be `ArrayLike`. For backward compatibility the default is still vector. This allows constant gradients and should also allow gradients to be supported for the next `#[no-std]` build. Other options to enable `#[no-std]` for gradients were briefly disscused in #156.

### 2. Adding some constant gradients

This commit adds constant gradients. To be precise, it adds all 4 new matplotlib color gradients, which are perfectly perceptually-uniform, both in regular form and also when converted to black-and-white and therefore one of the most useful gradients. These are build the same way the named colors are built. A new feature-flag `named_gradients` is added to toggle said constants. This closes #62.

### Alterantives

- The generalization of gradients can be achieved in a multiple of ways. Using a trait is just one of them and may be the wrong way to go. 
   - However I think because this pull request doesn't have any breaking changes and gradients should be supporting arrays in future versions of the crate, it doesn't seem like this update of  `Gradient` will cause more breaking API changes in the future than otherwise. Also constant gradients may be the only interaction with gradients a user needs, such that introducing them could reduce the number of users which actually relies on the generation of `Gradient` itself.
- At the moment the 4 constant gradients are using linear interpolation but in nature these gradients are Spline-Interpolations of exaclty two points (and 2 controlpoints). If `Gradient` will support Spline-Inerpolation in the future and the exact controlpoints of these gradients can be found (I only found the colormaps), the gradients could be implemented more memory efficient.

### Remark

These commits depend on const generics, which is a feature in beta but is planned to be stable on 2021-03-25 onwords (see [#79135](rust-lang/rust#79135)).

Co-authored-by: Nicolas Klenert <klenert.nicolas@gmail.com>
Co-authored-by: NicolasKlenert <Nicolas_Klenert@web.de>
Co-authored-by: NicolasKlenert <klenert.nicolas@gmail.com>
  • Loading branch information
3 people committed Mar 30, 2021
2 parents ba00c41 + 23dd0b3 commit 00f8e6a
Show file tree
Hide file tree
Showing 5 changed files with 1,231 additions and 66 deletions.
3 changes: 2 additions & 1 deletion palette/Cargo.toml
Expand Up @@ -15,9 +15,10 @@ categories = ["graphics", "multimedia::images", "no-std"]
build = "build/main.rs"

[features]
default = ["named_from_str", "std"]
default = ["named_from_str", "named_gradients", "std"]
named_from_str = ["named", "phf", "phf_codegen", "std"]
named = []
named_gradients = ["std"]
random = ["rand"]
serializing = ["serde", "std"]

Expand Down
70 changes: 65 additions & 5 deletions palette/build/named.rs
@@ -1,16 +1,23 @@
use std::fs::File;

#[cfg(feature = "named")]
pub fn build() {
use std::io::{BufRead, BufReader, Write};
use std::path::Path;

let out_dir = ::std::env::var("OUT_DIR").unwrap();
let dest_path = Path::new(&out_dir).join("named.rs");
let mut writer = File::create(dest_path).expect("couldn't create named.rs");
build_colors(&mut writer);
let dest_path = Path::new(&out_dir).join("named_gradients.rs");
let mut writer = File::create(dest_path).expect("couldn't create named_gradients.rs");
build_gradients(&mut writer);
}

#[cfg(feature = "named")]
pub fn build_colors(writer: &mut File) {
use std::io::{BufRead, BufReader, Write};

let reader =
BufReader::new(File::open("build/svg_colors.txt").expect("could not open svg_colors.txt"));
let mut writer = File::create(dest_path).expect("couldn't create named.rs");
let mut entries = vec![];

for line in reader.lines() {
Expand Down Expand Up @@ -40,7 +47,56 @@ pub fn build() {
entries.push((name.to_owned(), name.to_uppercase()));
}

gen_from_str(&mut writer, &entries)
gen_from_str(writer, &entries)
}

#[cfg(feature = "named_gradients")]
pub fn build_gradients(writer: &mut File) {
use std::io::{BufRead, BufReader, Write};

let reader =
BufReader::new(File::open("build/svg_gradients_mpl.txt").expect("could not open svg_gradients_mpl.txt"));

let mut line_iter = reader.lines();
while let Some(Ok(line)) = line_iter.next(){
//empty lines are allowed
if line.is_empty() {continue;}
let mut parts = line.split_whitespace();
//every line should have the same info: name type number_of_colors [\n red green blue]^number_of_colors
let name = parts.next().expect("couldn't get the color name");
let color_type = parts.next().expect("couldn't get the type of the colors");
//we assume that color_type is a rgb type
let color_type = format!("crate::rgb::{}", color_type);
let number_of_colors : usize = parts.next().expect("couldn't get the number of colors")
.parse().unwrap_or_else(|_| panic!("couldn't parse the number of colors for color {}", name));
writeln!(writer, "/// New matplotlib colormap by Nathaniel J. Smith, Stefan van der Walt, and (in the case of viridis) Eric Firing.").unwrap();
writeln!(writer, "/// This gradient is perfectly perceptually-uniform, both in regular form and also when converted to black-and-white.").unwrap();
writeln!(writer, "/// The colormap is released under the CC0 license public domain dedication.").unwrap();
write!(writer,
"pub const {0}: crate::gradient::Gradient<{1}, [(f32,{1});{2}]> = crate::gradient::Gradient([",
name.to_uppercase(), color_type, number_of_colors).unwrap();
for i in 0..number_of_colors {
let color = line_iter
.next()
.unwrap_or_else(|| panic!("less lines than stated colors in gradient {}", name))
.unwrap_or_else(|_| panic!("couldn't read the {}th line of color {}", i, name));
let mut rgb = color.split(",");
let red: f32 = rgb
.next()
.and_then(|r| r.trim().parse().ok())
.unwrap_or_else(|| panic!("couldn't get the {}th red-value for {}", i, name));
let green: f32 = rgb
.next()
.and_then(|r| r.trim().parse().ok())
.unwrap_or_else(|| panic!("couldn't get the {}th green-value for {}", i, name));
let blue: f32 = rgb
.next()
.and_then(|r| r.trim().parse().ok())
.unwrap_or_else(|| panic!("couldn't get the {}th blue-value for {}", i, name));
write!(writer, "({:.10},{}{{red: {}, green: {}, blue: {}, standard: ::core::marker::PhantomData}}),", (i as f32/number_of_colors as f32), color_type, red, green, blue).unwrap();
}
write!(writer, "], ::core::marker::PhantomData);\n").unwrap();
}
}

#[cfg(feature = "named_from_str")]
Expand All @@ -60,8 +116,12 @@ fn gen_from_str(writer: &mut File, entries: &[(String, String)]) {
}

#[cfg(not(feature = "named"))]
pub fn build() {}
pub fn build_colors(_writer: &mut File) {}

#[allow(unused)]
#[cfg(not(feature = "named_from_str"))]
fn gen_from_str(_writer: &mut File, _entries: &[(String, String)]) {}

#[allow(unused)]
#[cfg(not(feature = "named_gradients"))]
pub fn build_gradients(_writer: &mut File) {}

0 comments on commit 00f8e6a

Please sign in to comment.