11 releases
0.4.5 | Aug 2, 2024 |
---|---|
0.4.4 | Jan 29, 2024 |
0.4.3 | Jun 21, 2023 |
0.4.1 | May 30, 2023 |
0.1.0 | Apr 3, 2023 |
#448 in Algorithms
573 downloads per month
Used in ratio-dsm
64KB
2K
SLoC
Ratio Color
Color palette library.
Changelog
This repository keeps a CHANGELOG.md according to the recommendations by Keep a Changelog.
Contributions and license
To get contributing, feel free to fork, pick up an issue or file your own and get going for your first merge! We'll be more than happy to help.
You might have noticed that this crate follows the "MIT-OR-Apache-2.0" license and some of our other crates follow the "GPL-3.0-or-later" license. This is intentional. Some crates, we consider as "just utilities" that we would like to offer to the entire Rust community without any question asked or guarantees given. This is such a utility crate.
lib.rs
:
A library to make palette management for plotting projects easier. It is built on the crate
palette
that allows for creating, mixing and generally working with colors and pixels.
A short categorical example utilizing the Bold preset:
use ratio_color::{Categorical, Container, Palette, key_to_order};
use palette::Srgba;
let keys = ["foo", "bar", "baz", "quux"];
let categorical: Palette<&str, Srgba<u8>> = Palette::with_preset(
&Categorical::Bold,
key_to_order(keys)
);
assert_eq!(categorical.get("foo").expect("a color"), &Srgba::new(57, 105, 172, 255));
A short numerical example utilizing several numerical presets:
use ratio_color::{key_to_order, ColorAtFraction, Container, LinearGradient, Numerical, Palette};
use palette::Srgba;
let keys = ["foo", "bar", "baz", "quux"];
let numerical: Palette<&str, LinearGradient> = Palette::with_presets(
&[Numerical::SeqMagma, Numerical::SeqViridis, Numerical::CycEdge, Numerical::DivSpectral],
key_to_order(keys)
);
let gradient: &LinearGradient = numerical.get("bar").expect("a gradient");
assert_eq!(gradient, &LinearGradient::from(Numerical::SeqMagma));
let color: Srgba<u8> = gradient.color_at(0.5);
assert_eq!(color, Srgba::<u8>::new(183, 56, 120, 255));
The key struct of this crate is the Palette
for which the Container
trait has been
implemented. A container is a mix of a slice of values, usually a vector, and a BTreeMap
of
keys to access them in a number of ways using the Selector
enum to obtain either a suitable
index for the slice of values or an override value.
In terms of palettes this corresponds to a palette having a fixed number of colors or gradients
to choose from (the values). When binding colors to keys, the default behavior would be to use
the key's order in the mapping to determine the index of the palette's color to use using
Selector::KeyOrder
. However, you might want to use a specific color from the given palette
for a given key using Selector::Index
or even a complete overridden value not present in the
usual palette Selector::Value
.
The Palette
is a generic struct with an implementation of Container
for any orderable
key and any value.
Categorical palette
For our example palette, we will use red, green, and blue as our default colors, or values. Pink will be used as a special case later on. Next we create a set of keys for which we want to have colors available on demand. Just as an example, we will use "sun", "trees", "sky", "ground", "ditto", and "zzz". Note that we have more keys than default colors! For some, we will assign a fixed color by using an index. Others receive a color by their key's order with respect to the other keys. And finally, an exception will be made for ditto.
use std::collections::BTreeMap;
use ratio_color::{Container, Palette, Selector};
use palette::Srgba;
// Create some categorical palette colors.
let red: Srgba<u8> = Srgba::new(255, 0, 0, 100);
let green: Srgba<u8> = Srgba::new(0, 255, 0, 200);
let blue: Srgba<u8> = Srgba::new(0, 0, 255, 255);
// Bundle them together in a vec to supply to the categorical palette later on.
let values = vec![red.clone(), green.clone(), blue.clone()];
// Initialize a BTreeMap to map from key to a selector.
let mut selectors = BTreeMap::new();
selectors.insert("sun", Selector::Index(0)); // red
selectors.insert("trees", Selector::Index(1)); // green
selectors.insert("sky", Selector::Index(2)); // blue
selectors.insert("ground", Selector::KeyOrder); // what will this be?
selectors.insert("zzz", Selector::KeyOrder); // I'm probably last.
// Ditto's are always pink, so they get a special value.
let pink: Srgba<u8> = Srgba::new(255, 125, 200, 255);
selectors.insert("ditto", Selector::Value(pink.clone()));
// Create the palette.
let palette = Palette::new(values, selectors);
// Let's check the contents!
assert_eq!(
palette.get("sun").expect("a color"),
&red,
"our sun is red"
);
assert_eq!(
palette.get("ditto").expect("a color"),
&pink,
"a ditto is pink"
);
assert_eq!(
palette.get("ground").expect("a color"),
&green,
"the ground is green, because it's key is the second one by order (ditto, *ground*, sky, sun, trees, zzz)"
);
assert_eq!(
palette.get("zzz").expect("a color"),
&blue,
"even though I'm sixth, I'll be 2 (blue) instead (index=5, per modulo 3 makes 2)"
)
So now we can safely get our colors for our given keys. The Palette
's generic nature, means
we can use any orderable key, and also any value. Adding or removing a key later on is done by
mutating the Palette::selectors
property or whatever property you decide to hook up to the
Container::selectors
trait function.
In order to not necessarily clog your error stack, we rely on the basic behavior of maps an
vectors by returning values as an Option
rather than throwing errors.
Numerical palette
Numerical palettes are completely similar to categorical palettes as far as storage and access
go. The palette
crate no longer provides us with continuous color scales directly. As a
replacement, we introduce the ColorAtFraction
trait and LinearGradient
struct, that uses
the enterpolation
crate to interpolate between linearized colors instead.
use palette::Srgba;
use ratio_color::{key_to_order, ColorAtFraction, LinearGradient, Palette, Container};
// Create basic colors.
let transparent: Srgba<u8> = Srgba::new(0, 0, 0, 0);
let red: Srgba<u8> = Srgba::new(255, 0, 0, 255);
let green: Srgba<u8> = Srgba::new(0, 255, 0, 255);
let blue: Srgba<u8> = Srgba::new(0, 0, 255, 255);
// Create three gradients.
let to_red =
LinearGradient::new([transparent.clone(), red.clone()], None).expect("a gradient");
let to_green =
LinearGradient::new([transparent.clone(), green.clone()], None).expect("a gradient");
let to_blue =
LinearGradient::new([transparent.clone(), blue.clone()], None).expect("a gradient");
// Get a color from a linear gradient and convert it back into a regular Srgba.
// Note that blueness interpolation in the linear color space does not equal regular "mean"
// taking, but the alpha channel does.
let halfway_blue = Srgba::<u8>::from_linear(to_blue.color_at(0.5));
assert_eq!(halfway_blue, Srgba::<u8>::new(0, 0, 188, 128));
// Let's assert the endpoints for completeness' sake.
let start: Srgba<u8> = to_blue.color_at(0.0);
let end: Srgba<u8> = to_blue.color_at(1.0);
assert_eq!(&start, &transparent);
assert_eq!(&end, &blue);
// Store these in an palette for these keys.
let keys = ["foo", "bar", "baz", "quux"];
// Let keys resolve to their order in the BTreeMap.
// Actual palette creation.
let palette = Palette::new(
[to_red.clone(), to_green.clone(), to_blue.clone()],
key_to_order(keys)
);
// Test accessing and using a gradient.
let full_red: Srgba<u8> = palette.get("quux").expect("a gradient").color_at(1.0);
assert_eq!(&full_red, &red);
assert_eq!(
<LinearGradient as ColorAtFraction<Srgba<u8>>>::color_at(
palette.get("quux").expect("a gradient"),
0.66
),
Srgba::<u8>::new(212, 0, 0, 168),
"Access quux at %66 using the fully qualified path to access the method."
);
Dependencies
~3MB
~61K SLoC