color_brewery/lib.rs
1//! Color schemes and gradients.
2//!
3//! This create provides the [color schemes by Cynthia
4//! Brewer](http://colorbrewer2.org/) and Matplotlib colormaps as well
5//! as gradients build from them (or from your own colors). The
6//! default representation of colors is the one given by the [rgb
7//! crate][] but any type can be used: it suffices it implements the
8//! [`RGBColor`] trait.
9//!
10//! [rgb crate]: https://crates.io/crates/rgb
11
12use std::f64::consts::PI;
13use std::marker::PhantomData;
14use rgb::{RGBA, RGB8, RGB16, RGBA8, RGBA16};
15
16mod palettes;
17use palettes::ty::PaletteData;
18pub use palettes::ty::{PaletteType, Trivalent};
19
20/// A “continuous” range of colors parametrized by reals in \[0, 1\].
21pub trait ColorRange<Color> {
22 /// Returns the color corresponding to `t` ∈ \[0., 1.\].
23 fn rgb(&self, t: f64) -> Color;
24
25 /// Return an iterator yielding uniform sampling of `n` points
26 /// between `a` and `b` (with the bounds `a` and `b` included in
27 /// the list of points) together with colors. It is not required
28 /// that `a <= b`.
29 fn range(self, mut a: f64, mut b: f64, n: usize) -> Range<Self, Color>
30 where Self: Sized {
31 if a == f64::INFINITY { a = f64::MAX; }
32 else if a == f64::NEG_INFINITY { a = f64::MIN };
33 if b == f64::NEG_INFINITY { b = f64::MIN; }
34 else if b == f64::INFINITY { b = f64::MAX };
35 // `a` or `b` NaN will give an iterator yielding NaN.
36 if n == 0 {
37 Range { range: self, color: PhantomData,
38 a, b, flast: 0., last: 0,
39 i: 1, j: 0 } // Empty iterator
40 } else {
41 Range { range: self, color: PhantomData,
42 a, b, flast: (n - 1) as f64,
43 last: n - 1, i: 0, j: n - 1 }
44 }
45 }
46}
47
48/// An iterator yielding `f64` in a given range together with colors.
49pub struct Range<R, Color> {
50 range: R,
51 color: PhantomData<Color>,
52 a: f64, // finite or NaN
53 b: f64, // finite or NaN
54 flast: f64, // `last` as a floating-point number
55 last: usize,
56 i: usize, // first position to be consumed (i ≤ j)
57 j: usize, // last position to be consumed
58}
59
60impl<R, Color> Range<R, Color> where R: ColorRange<Color> {
61 /// Return the float and RGB color of the position `k` (assuming
62 /// it is in the range `0 ..= self.last`).
63 fn rgb(&self, k: usize) -> (f64, Color) {
64 if k == 0 {
65 (self.a, R::rgb(&self.range, 0.))
66 } else if k == self.last {
67 (self.b, R::rgb(&self.range, 1.))
68 } else {
69 let alpha = (self.last - k) as f64;
70 let beta = k as f64;
71 let t = beta / self.flast;
72 let mut x = (alpha * self.a + beta * self.b) / self.flast;
73 if x.is_infinite() {
74 x = (1. - t) * self.a + t * self.b;
75 }
76 (x, R::rgb(&self.range, t))
77 }
78
79 }
80}
81
82impl<R, Color> Iterator for Range<R, Color>
83where R: ColorRange<Color> {
84 type Item = (f64, Color);
85
86 fn next(&mut self) -> Option<Self::Item> {
87 if self.i <= self.j {
88 let item = self.rgb(self.i);
89 self.i += 1;
90 Some(item)
91 } else {
92 None
93 }
94 }
95
96 fn size_hint(&self) -> (usize, Option<usize>) {
97 let len = self.last - self.i + 1;
98 (len, Some(len))
99 }
100}
101
102impl<R, Color> ExactSizeIterator for Range<R, Color>
103where R: ColorRange<Color> {
104 fn len(&self) -> usize { self.last - self.i + 1 }
105}
106
107impl<R, Color> DoubleEndedIterator for Range<R, Color>
108where R: ColorRange<Color> {
109 fn next_back(&mut self) -> Option<Self::Item> {
110 if self.i <= self.j {
111 let item = self.rgb(self.j);
112 if self.j == 0 {
113 self.i = 1
114 } else {
115 self.j -= 1;
116 }
117 Some(item)
118 } else {
119 None
120 }
121 }
122}
123
124/// Specifies the methods a RGB color encoding must provide.
125pub trait RGBColor: Sized {
126 /// Return the red, green, blue and alpha components of the color
127 /// (in \[0, 255\]).
128 fn to_rgba(&self) -> RGBA<f64>;
129
130 /// Create a color from its RGBA components (in \[0, 255\]).
131 fn from_rgba(rgba: RGBA<f64>) -> Self;
132
133 /// Return the color corresponding to the hue `h` ∈ \[0., 1.\].
134 ///
135 /// # Example
136 ///
137 /// ```
138 /// use rgb::RGB8;
139 /// use color_brewery::{RGBColor, ColorRange};
140 /// let rgb = RGB8::HUE.rgb(0.5);
141 /// ```
142 const HUE: Hue<Self> = Hue { color: PhantomData };
143
144 /// Return a gradient from color `c0` to color `c1`.
145 ///
146 /// # Example
147 ///
148 /// ```
149 /// use rgb::RGB8;
150 /// use color_brewery::{RGBColor, ColorRange};
151 /// let red = RGB8::new(255,0, 0);
152 /// let blue = RGB8::new(0, 0, 255);
153 /// let grad = red.gradient(&blue);
154 /// let rgb = grad.rgb(0.5);
155 /// ```
156 fn gradient(&self, c1: &Self) -> Gradient<Self> {
157 let lch0 = Lch::from_rgb(Self::to_rgba(self));
158 let lch1 = Lch::from_rgb(Self::to_rgba(c1));
159 let h0 = lch0.h;
160 let h1 = lch1.h;
161 let dh = {
162 if h1 > h0 && h1 - h0 > PI { h1 - (h0 + TWO_PI) }
163 else if h1 < h0 && h0 - h1 > PI { h1 + TWO_PI - h0 }
164 else { h1 - h0 } };
165 Gradient { c0: lch0,
166 dc: Lch { l: lch1.l - lch0.l, c: lch1.c - lch0.c,
167 h: dh, a: lch1.a - lch0.a },
168 color: PhantomData }
169 }
170
171 /// Find palettes matching certain criteria.
172 fn palettes(len: usize) -> PaletteFind<Self> {
173 PaletteFind {
174 len,
175 typ: vec![],
176 blind: Trivalent::No, // "no" means "not necessarily want"
177 print: Trivalent::No,
178 photocopy: Trivalent::No,
179 lcd: Trivalent::No,
180 color: PhantomData,
181 }
182 }
183
184
185 /// Matplotlib magma color scheme.
186 ///
187 /// <img src="https://raw.githubusercontent.com/d3/d3-scale-chromatic/master/img/magma.png" width="100%" height="40" alt="magma">
188 #[inline]
189 fn magma() -> Palette<Self> { Palette::new(&palettes::MAGMA) }
190
191 /// Matplotlib inferno color scheme.
192 ///
193 /// <img src="https://raw.githubusercontent.com/d3/d3-scale-chromatic/master/img/inferno.png" width="100%" height="40" alt="inferno">
194 #[inline]
195 fn inferno() -> Palette<Self> { Palette::new(&palettes::INFERNO) }
196
197 /// Matplotlib plasma color scheme.
198 ///
199 /// <img src="https://raw.githubusercontent.com/d3/d3-scale-chromatic/master/img/plasma.png" width="100%" height="40" alt="plasma">
200 #[inline]
201 fn plasma() -> Palette<Self> { Palette::new(&palettes::PLASMA) }
202
203 /// Matplotlib viridis color scheme.
204 ///
205 /// <img src="https://raw.githubusercontent.com/d3/d3-scale-chromatic/master/img/viridis.png" width="100%" height="40" alt="viridis">
206 #[inline]
207 fn viridis() -> Palette<Self> { Palette::new(&palettes::VIRIDIS) }
208
209 /// Matplotlib cividis color scheme.
210 ///
211 /// <img src="https://raw.githubusercontent.com/d3/d3-scale-chromatic/master/img/cividis.png" width="100%" height="40" alt="cividis">
212 #[inline]
213 fn cividis() -> Palette<Self> { Palette::new(&palettes::CIVIDIS) }
214
215 /// Matplotlib twilight color scheme.
216 ///
217 #[inline]
218 fn twilight() -> Palette<Self> { Palette::new(&palettes::TWILIGHT) }
219
220 /// Matplotlib turbo color scheme.
221 ///
222 /// <img src="https://raw.githubusercontent.com/d3/d3-scale-chromatic/master/img/turbo.png" width="100%" height="40" alt="turbo">
223 #[inline]
224 fn turbo() -> Palette<Self> { Palette::new(&palettes::TURBO) }
225
226 /// Brewer "Light yellow to dark green" sequential scheme.
227 ///
228 /// <img src="https://raw.githubusercontent.com/d3/d3-scale-chromatic/master/img/YlGn.png" width="100%" height="40" alt="ylgn">
229 #[inline]
230 fn ylgn() -> PaletteIter<Self> { PaletteIter::new(&palettes::YLGN) }
231
232 /// Brewer "Light yellow to green to dark blue" sequential scheme.
233 ///
234 /// <img src="https://raw.githubusercontent.com/d3/d3-scale-chromatic/master/img/YlGnBu.png" width="100%" height="40" alt="ylgnbu">
235 #[inline]
236 fn ylgnbu() -> PaletteIter<Self> { PaletteIter::new(&palettes::YLGNBU) }
237
238 /// Brewer "Light green to dark blue" sequential scheme.
239 ///
240 /// <img src="https://raw.githubusercontent.com/d3/d3-scale-chromatic/master/img/GnBu.png" width="100%" height="40" alt="gnbu">
241 #[inline]
242 fn gnbu() -> PaletteIter<Self> { PaletteIter::new(&palettes::GNBU) }
243
244 /// Brewer "Light blue to dark green" sequential scheme.
245 ///
246 /// <img src="https://raw.githubusercontent.com/d3/d3-scale-chromatic/master/img/BuGn.png" width="100%" height="40" alt="bugn">
247 #[inline]
248 fn bugn() -> PaletteIter<Self> { PaletteIter::new(&palettes::BUGN) }
249
250 /// Brewer "Light purple to blue to dark green" sequential scheme.
251 ///
252 /// <img src="https://raw.githubusercontent.com/d3/d3-scale-chromatic/master/img/PuBuGn.png" width="100%" height="40" alt="pubugn">
253 #[inline]
254 fn pubugn() -> PaletteIter<Self> { PaletteIter::new(&palettes::PUBUGN) }
255
256 /// Brewer "Light purple to dark blue" sequential scheme.
257 ///
258 /// <img src="https://raw.githubusercontent.com/d3/d3-scale-chromatic/master/img/PuBu.png" width="100%" height="40" alt="pubu">
259 #[inline]
260 fn pubu() -> PaletteIter<Self> { PaletteIter::new(&palettes::PUBU) }
261
262 /// Brewer "Light blue to dark purple" sequential scheme.
263 ///
264 /// <img src="https://raw.githubusercontent.com/d3/d3-scale-chromatic/master/img/BuPu.png" width="100%" height="40" alt="bupu">
265 #[inline]
266 fn bupu() -> PaletteIter<Self> { PaletteIter::new(&palettes::BUPU) }
267
268 /// Brewer "Light red to dark purple" sequential scheme.
269 ///
270 /// <img src="https://raw.githubusercontent.com/d3/d3-scale-chromatic/master/img/RdPu.png" width="100%" height="40" alt="rdpu">
271 #[inline]
272 fn rdpu() -> PaletteIter<Self> { PaletteIter::new(&palettes::RDPU) }
273
274 /// Brewer "Light purple to dark red" sequential scheme.
275 ///
276 /// <img src="https://raw.githubusercontent.com/d3/d3-scale-chromatic/master/img/PuRd.png" width="100%" height="40" alt="purd">
277 #[inline]
278 fn purd() -> PaletteIter<Self> { PaletteIter::new(&palettes::PURD) }
279
280 /// Brewer "Light orange to dark red" sequential scheme.
281 ///
282 /// <img src="https://raw.githubusercontent.com/d3/d3-scale-chromatic/master/img/OrRd.png" width="100%" height="40" alt="orrd">
283 #[inline]
284 fn orrd() -> PaletteIter<Self> { PaletteIter::new(&palettes::ORRD) }
285
286 /// Brewer "Light yellow to orange to dark red" sequential scheme.
287 ///
288 /// <img src="https://raw.githubusercontent.com/d3/d3-scale-chromatic/master/img/YlOrRd.png" width="100%" height="40" alt="ylorrd">
289 #[inline]
290 fn ylorrd() -> PaletteIter<Self> { PaletteIter::new(&palettes::YLORRD) }
291
292 /// Brewer "Light yellow to orange to dark brown" sequential scheme.
293 ///
294 /// <img src="https://raw.githubusercontent.com/d3/d3-scale-chromatic/master/img/YlOrBr.png" width="100%" height="40" alt="ylorbr">
295 #[inline]
296 fn ylorbr() -> PaletteIter<Self> { PaletteIter::new(&palettes::YLORBR) }
297
298
299 /// Brewer "Light to dark purple" sequential scheme.
300 ///
301 /// <img src="https://raw.githubusercontent.com/d3/d3-scale-chromatic/master/img/Purples.png" width="100%" height="40" alt="purples">
302 #[inline]
303 fn purples() -> PaletteIter<Self> { PaletteIter::new(&palettes::PURPLES) }
304
305 /// Brewer "Light to dark blue" sequential scheme.
306 ///
307 /// <img src="https://raw.githubusercontent.com/d3/d3-scale-chromatic/master/img/Blues.png" width="100%" height="40" alt="blues">
308 #[inline]
309 fn blues() -> PaletteIter<Self> { PaletteIter::new(&palettes::BLUES) }
310
311 /// Brewer "Light to dark green" sequential scheme.
312 ///
313 /// <img src="https://raw.githubusercontent.com/d3/d3-scale-chromatic/master/img/Greens.png" width="100%" height="40" alt="greens">
314 #[inline]
315 fn greens() -> PaletteIter<Self> { PaletteIter::new(&palettes::GREENS) }
316
317 /// Brewer "Light to dark orange" sequential scheme.
318 ///
319 /// <img src="https://raw.githubusercontent.com/d3/d3-scale-chromatic/master/img/Oranges.png" width="100%" height="40" alt="oranges">
320 #[inline]
321 fn oranges() -> PaletteIter<Self> { PaletteIter::new(&palettes::ORANGES) }
322
323 /// Brewer "Light to dark red" sequential scheme.
324 ///
325 /// <img src="https://raw.githubusercontent.com/d3/d3-scale-chromatic/master/img/Reds.png" width="100%" height="40" alt="reds">
326 #[inline]
327 fn reds() -> PaletteIter<Self> { PaletteIter::new(&palettes::REDS) }
328
329 /// Brewer "Light to dark gray" sequential scheme.
330 ///
331 /// <img src="https://raw.githubusercontent.com/d3/d3-scale-chromatic/master/img/Greys.png" width="100%" height="40" alt="greys">
332 #[inline]
333 fn greys() -> PaletteIter<Self> { PaletteIter::new(&palettes::GREYS) }
334
335
336 /// Brewer "Dark orange to light to dark purple" diverging scheme
337 ///
338 /// <img src="https://raw.githubusercontent.com/d3/d3-scale-chromatic/master/img/PuOr.png" width="100%" height="40" alt="puor">
339 #[inline]
340 fn puor() -> PaletteIter<Self> { PaletteIter::new(&palettes::PUOR) }
341
342 /// Brewer "Dark brown to light to dark blue-green" diverging scheme
343 ///
344 /// <img src="https://raw.githubusercontent.com/d3/d3-scale-chromatic/master/img/BrBG.png" width="100%" height="40" alt="brbg">
345 #[inline]
346 fn brbg() -> PaletteIter<Self> { PaletteIter::new(&palettes::BRBG) }
347
348 /// Brewer "Dark reddish-purple to light to dark green" diverging scheme
349 ///
350 /// <img src="https://raw.githubusercontent.com/d3/d3-scale-chromatic/master/img/PRGn.png" width="100%" height="40" alt="prgn">
351 #[inline]
352 fn prgn() -> PaletteIter<Self> { PaletteIter::new(&palettes::PRGN) }
353
354 /// Brewer "Dark magenta to light to dark yellow-green" diverging scheme
355 ///
356 /// <img src="https://raw.githubusercontent.com/d3/d3-scale-chromatic/master/img/PiYG.png" width="100%" height="40" alt="piyg">
357 #[inline]
358 fn piyg() -> PaletteIter<Self> { PaletteIter::new(&palettes::PIYG) }
359
360 /// Brewer "Dark red to light to dark blue" diverging scheme.
361 ///
362 /// <img src="https://raw.githubusercontent.com/d3/d3-scale-chromatic/master/img/RdBu.png" width="100%" height="40" alt="rdbu">
363 #[inline]
364 fn rdbu() -> PaletteIter<Self> { PaletteIter::new(&palettes::RDBU) }
365
366 /// Brewer "Dark red to light to dark grey" diverging scheme.
367 ///
368 /// <img src="https://raw.githubusercontent.com/d3/d3-scale-chromatic/master/img/RdGy.png" width="100%" height="40" alt="rdgy">
369 #[inline]
370 fn rdgy() -> PaletteIter<Self> { PaletteIter::new(&palettes::RDGY) }
371
372 /// Brewer "Dark red to light yelow to dark blue" diverging scheme.
373 ///
374 /// <img src="https://raw.githubusercontent.com/d3/d3-scale-chromatic/master/img/RdYlBu.png" width="100%" height="40" alt="rdylbu">
375 #[inline]
376 fn rdylbu() -> PaletteIter<Self> { PaletteIter::new(&palettes::RDYLBU) }
377
378 /// Brewer "Dark red, orange, light yellow, green, dark blue"
379 /// diverging scheme.
380 ///
381 /// <img src="https://raw.githubusercontent.com/d3/d3-scale-chromatic/master/img/Spectral.png" width="100%" height="40" alt="spectral">
382 #[inline]
383 fn spectral() -> PaletteIter<Self> { PaletteIter::new(&palettes::SPECTRAL) }
384
385 /// Brewer "Dark red, orange, light yellow, yellow-green, dark green"
386 /// diverging scheme.
387 ///
388 /// <img src="https://raw.githubusercontent.com/d3/d3-scale-chromatic/master/img/RdYlGn.png" width="100%" height="40" alt="rdylgn">
389 #[inline]
390 fn rdylgn() -> PaletteIter<Self> { PaletteIter::new(&palettes::RDYLGN) }
391
392
393 /// Brewer qualitative scheme: includes bold, readily named, basic
394 /// colors (such as red, green, blue).
395 ///
396 /// <img src="https://raw.githubusercontent.com/d3/d3-scale-chromatic/master/img/Set1.png" width="100%" height="40" alt="set1">
397 #[inline]
398 fn set1() -> PaletteIter<Self> { PaletteIter::new(&palettes::SET1) }
399
400 /// Brewer qualitative scheme: Lighter version of [`Self::set1`].
401 ///
402 /// <img src="https://raw.githubusercontent.com/d3/d3-scale-chromatic/master/img/Pastel1.png" width="100%" height="40" alt="pastel1">
403 #[inline]
404 fn pastel1() -> PaletteIter<Self> { PaletteIter::new(&palettes::PASTEL1) }
405
406 /// Brewer qualitative scheme: Includes mostly a mixture colors
407 /// (such as blue-green, red-orange).
408 ///
409 /// <img src="https://raw.githubusercontent.com/d3/d3-scale-chromatic/master/img/Set2.png" width="100%" height="40" alt="set2">
410 #[inline]
411 fn set2() -> PaletteIter<Self> { PaletteIter::new(&palettes::SET2) }
412
413 /// Brewer qualitative scheme: Lighter version of [`Self::set2`].
414 ///
415 /// <img src="https://raw.githubusercontent.com/d3/d3-scale-chromatic/master/img/Pastel2.png" width="100%" height="40" alt="pastel2">
416 #[inline]
417 fn pastel2() -> PaletteIter<Self> { PaletteIter::new(&palettes::PASTEL2) }
418
419 /// Brewer qualitative scheme: Darker version of [`Self::set2`].
420 ///
421 /// <img src="https://raw.githubusercontent.com/d3/d3-scale-chromatic/master/img/Dark2.png" width="100%" height="40" alt="dark2">
422 #[inline]
423 fn dark2() -> PaletteIter<Self> { PaletteIter::new(&palettes::DARK2) }
424
425 /// Brewer qualitative scheme: Medium saturation set with more
426 /// lightness variation and more classes than [`Self::set1`] and
427 /// [`Self::set2`].
428 ///
429 /// <img src="https://raw.githubusercontent.com/d3/d3-scale-chromatic/master/img/Set3.png" width="100%" height="40" alt="set3">
430 #[inline]
431 fn set3() -> PaletteIter<Self> { PaletteIter::new(&palettes::SET3) }
432
433 /// Brewer qualitative scheme: Light/dark paris for namable hues.
434 ///
435 /// <img src="https://raw.githubusercontent.com/d3/d3-scale-chromatic/master/img/Paired.png" width="100%" height="40" alt="paired">
436 #[inline]
437 fn paired() -> PaletteIter<Self> { PaletteIter::new(&palettes::PAIRED) }
438
439 /// Brewer qualitative scheme: Include lightness and saturation
440 /// extremes to accent small or important areas.
441 ///
442 /// <img src="https://raw.githubusercontent.com/d3/d3-scale-chromatic/master/img/Accent.png" width="100%" height="40" alt="accent">
443 #[inline]
444 fn accent() -> PaletteIter<Self> { PaletteIter::new(&palettes::ACCENT) }
445
446 /// Convert the color to grayscale.
447 fn to_gray(&self) -> Self {
448 let RGBA{ r, g, b, a } = Self::to_rgba(self);
449 let x = 0.299 * r + 0.587 * g + 0.114 * b;
450 Self::from_rgba(RGBA{ r: x, g: x, b: x, a })
451 }
452}
453
454impl RGBColor for RGBA<f64> {
455 #[inline]
456 fn to_rgba(&self) -> RGBA<f64> { *self }
457
458 #[inline]
459 fn from_rgba(c: RGBA<f64>) -> Self { c }
460}
461
462impl RGBColor for RGB8 {
463 #[inline]
464 fn to_rgba(&self) -> RGBA<f64> {
465 RGBA{ r: self.r as f64, g: self.g as f64, b: self.b as f64, a: 255. }
466 }
467
468 #[inline]
469 fn from_rgba(c: RGBA<f64>) -> Self {
470 RGB8 { r: c.r as u8, g: c.g as u8, b: c.b as u8 }
471 }
472}
473
474impl RGBColor for RGB16 {
475 #[inline]
476 fn to_rgba(&self) -> RGBA<f64> {
477 RGBA{ r: self.r as f64, g: self.g as f64, b: self.b as f64, a: 255. }
478 }
479
480 #[inline]
481 fn from_rgba(c: RGBA<f64>) -> Self {
482 RGB16 { r: c.r as u16, g: c.g as u16, b: c.b as u16 }
483 }
484}
485
486impl RGBColor for RGBA8 {
487 #[inline]
488 fn to_rgba(&self) -> RGBA<f64> {
489 RGBA{ r: self.r as f64, g: self.g as f64, b: self.b as f64,
490 a: self.a as f64 }
491 }
492
493 #[inline]
494 fn from_rgba(c: RGBA<f64>) -> Self {
495 RGBA8 { r: c.r as u8, g: c.g as u8, b: c.b as u8, a: c.a as u8 }
496 }
497}
498
499impl RGBColor for RGBA16 {
500 #[inline]
501 fn to_rgba(&self) -> RGBA<f64> {
502 RGBA{ r: self.r as f64, g: self.g as f64, b: self.b as f64,
503 a: self.a as f64 }
504 }
505
506 #[inline]
507 fn from_rgba(c: RGBA<f64>) -> Self {
508 RGBA16 { r: c.r as u16, g: c.g as u16, b: c.b as u16, a: c.a as u16 }
509 }
510}
511
512/// The type for colors in the CIE L*C*h*_ab color space with a D50
513/// reference white point and an alpha component. This color space is
514/// CIE L*a*b* with polar coordinates.
515#[derive(Clone, Copy)]
516struct Lch {
517 /// The lightness in the range 0. to 100.
518 l: f64,
519 /// The chroma, in the range 0. to 181.02, but less in practice.
520 c: f64,
521 /// The hue in degrees in the range 0. to 2π.
522 h: f64,
523 /// Alpha component
524 a: f64,
525}
526
527const EPS0: f64 = 6. / 29.;
528const EPS: f64 = EPS0 * EPS0 * EPS0 ;
529const TWO_PI: f64 = 2. * PI;
530
531impl Lch {
532 fn from_rgb(c: RGBA<f64>) -> Lch {
533 // See https://github.com/dbuenzli/gg/blob/b8704687d669d139bb4ac7a54115afc7e5caaa55/src/gg.ml#L2926
534 const C0: f64 = 1. / 3.;
535 const C1: f64 = 841. / 108.;
536 const C2: f64 = 4. / 29.;
537 let xr = 0.4522795 * c.r + 0.3993744 * c.g + 0.1483460 * c.b;
538 let yr = 0.2225105 * c.r + 0.7168863 * c.g + 0.0606032 * c.b;
539 let zr = 0.0168820 * c.r + 0.1176865 * c.g + 0.8654315 * c.b;
540 let fx = if xr > EPS { xr.powf(C0) } else { C1 * xr + C2 };
541 let fy = if yr > EPS { yr.powf(C0) } else { C1 * yr + C2 };
542 let fz = if zr > EPS { zr.powf(C0) } else { C1 * zr + C2 };
543 let l = 116. * fy - 16.;
544 let a = 500. * (fx - fy);
545 let b = 200. * (fy - fz);
546 let h = { let h = b.atan2(a);
547 if h < 0. { h + TWO_PI } else { h } };
548 Lch { l, c: a.hypot(b), h, a: c.a }
549 }
550
551 fn to_rgb(&self) -> RGBA<f64> {
552 const C0: f64 = 108. / 841.;
553 const C1: f64 = 4. / 29.;
554 let a = self.c * self.h.cos();
555 let b = self.c * self.h.sin();
556 let fy = (self.l + 16.) / 116.;
557 let fx = a / 500. + fy;
558 let fz = fy - b / 200.;
559 let fx1 = if fx > EPS0 { fx * fx * fx } else { C0 * (fx - C1) };
560 let fy1 = if fy > EPS0 { fy * fy * fy } else { C0 * (fy - C1) };
561 let fz1 = if fz > EPS0 { fz * fz * fz } else { C0 * (fz - C1) };
562 let r = 3.0215932 * fx1 - 1.6168777 * fy1 - 0.4047152 * fz1;
563 let g = -0.9437222 * fx1 + 1.9161365 * fy1 + 0.0275856 * fz1;
564 let b = 0.0693906 * fx1 - 0.2290271 * fy1 + 1.1596365 * fz1;
565 RGBA { r, g, b, a: self.a }
566 }
567}
568
569/// Hue
570///
571pub struct Hue<Color> { color: PhantomData<Color> }
572
573impl<Color: RGBColor> ColorRange<Color> for Hue<Color> {
574 fn rgb(&self, t: f64) -> Color {
575 let t = 6. * t;
576 let f = 255. * t.fract();
577 let ti = t.trunc().rem_euclid(6.);
578 let rgba = {
579 if ti == 0. { RGBA{ r: 255., g: f, b: 0., a: 255.} }
580 else if ti == 1. { RGBA{ r: 255. - f, g: 255., b: 0., a: 255.} }
581 else if ti == 2. { RGBA{ r: 0., g: 255., b: f, a: 255.} }
582 else if ti == 3. { RGBA{ r: 0., g: 255. - f, b: 255., a: 255.} }
583 else if ti == 4. { RGBA{ r: f, g: 0., b: 255., a: 255.} }
584 else { RGBA{ r: 255., g: 0., b: 255. - f, a: 255.} }
585 };
586 Color::from_rgba(rgba)
587 }
588}
589
590
591/// Gradient between two colors.
592///
593/// Created by [`RGBColor::gradient`]. See the [`ColorRange`] trait
594/// for methods.
595pub struct Gradient<Color> {
596 c0: Lch, // first color
597 dc: Lch, // last - fist color
598 color: PhantomData<Color>,
599}
600
601impl<Color> Gradient<Color>
602where Color: RGBColor {
603 /// Returns the color corresponding to `t` ∈ \[0., 1.\] but does
604 /// not check the later condition.
605 #[inline]
606 fn rgb_unsafe(&self, t: f64) -> Color {
607 let lhc = Lch { l: self.c0.l + t * self.dc.l,
608 c: self.c0.c + t * self.dc.c,
609 h: self.c0.h + t * self.dc.h,
610 a: self.c0.a + t * self.dc.a };
611 Color::from_rgba(lhc.to_rgb())
612 }
613}
614
615impl<Color> ColorRange<Color> for Gradient<Color>
616where Color: RGBColor {
617 /// Returns the color corresponding to `t` ∈ \[0., 1.\], where
618 /// `t == 0.` returns the first color provided in the gradient and
619 /// `t == 1.` the second.
620 fn rgb(&self, t: f64) -> Color { self.rgb_unsafe(t.clamp(0., 1.)) }
621}
622
623
624#[derive(Clone, Copy)]
625pub struct Palette<Color> {
626 palette: &'static PaletteData,
627 color: PhantomData<Color>,
628}
629
630impl<Color: RGBColor> Palette<Color> {
631 fn new(palette: &'static PaletteData) -> Self {
632 Self { palette, color: PhantomData }
633 }
634}
635
636/// # Color palettes (aka colormaps)
637///
638/// A Colormap with certain characteristics.
639impl<Color> Palette<Color>
640where Color: RGBColor {
641 /// Returns the number of colors in the palette.
642 ///
643 /// Palettes countains at least 2 colors.
644 pub fn len(&self) -> usize { self.palette.rgb.len() }
645
646 /// Says whether the palette is `Seq`uential, `Div`ergent or
647 /// `Qual`itative.
648 pub fn typ(&self) -> PaletteType { self.palette.typ }
649
650 /// Says whether the palette is colorblind safe (if the palette
651 /// specifies it).
652 pub fn blind(&self) -> Trivalent { self.palette.blind }
653
654 /// Says whether the palette is suitable for desktop color
655 /// printing.
656 pub fn print(&self) -> Trivalent { self.palette.print }
657
658 /// Says whether the palette will withstand black and white
659 /// photocopying.
660 pub fn photocopy(&self) -> Trivalent { self.palette.photocopy }
661
662 /// Says whether the palette is friendly for LCD screens (which
663 /// tend to wash-out colors).
664 pub fn lcd(&self) -> Trivalent { self.palette.lcd }
665
666 /// Returns the RGB colors of the palette.
667 pub fn colors(&self) -> Vec<Color> {
668 self.palette.rgb.iter().map(|&c| Color::from_rgba(c)).collect()
669 }
670
671 /// Returns a gradient constructed from the palette.
672 /// It only makes sense for sequential and some diverging palettes.
673 pub fn gradient(&self) -> PaletteGradient<Color> {
674 PaletteGradient {
675 gradients: self.palette.rgb.windows(2)
676 .map(|c| { let c0 = Color::from_rgba(c[0]);
677 let c1 = Color::from_rgba(c[1]);
678 c0.gradient(&c1) })
679 .collect() }
680 }
681}
682
683/// A gradient based on a [`Palette`].
684pub struct PaletteGradient<Color> {
685 gradients: Vec<Gradient<Color>>,
686}
687
688impl<Color> ColorRange<Color> for PaletteGradient<Color>
689where Color: RGBColor {
690 fn rgb(&self, t: f64) -> Color {
691 let n = self.gradients.len();
692 let tn = t.clamp(0., 1.) * n as f64;
693 let i = tn.trunc() as usize;
694 if i < n { self.gradients[i].rgb_unsafe(tn.fract()) }
695 else { self.gradients[n-1].rgb_unsafe(1.) }
696 }
697}
698
699/// An exact size iterator over [`Palette`]s.
700#[derive(Clone, Copy)]
701pub struct PaletteIter<Color> {
702 palettes: &'static Vec<PaletteData>,
703 i: usize, // first position to be consumed (i < j)
704 j: usize, // position after the last one to be consumed
705 color: PhantomData<Color>,
706}
707
708impl<Color: RGBColor> PaletteIter<Color> {
709 fn new(palettes: &'static Vec<PaletteData>) -> Self {
710 Self { palettes, i: 0, j: palettes.len(),
711 color: PhantomData }
712 }
713}
714
715impl<Color> Iterator for PaletteIter<Color>
716where Color: RGBColor {
717 type Item = Palette<Color>;
718
719 fn next(&mut self) -> Option<Self::Item> {
720 if self.i >= self.j { return None }
721 let x = Palette::new(&self.palettes[self.i]);
722 self.i += 1;
723 Some(x)
724 }
725
726 fn size_hint(&self) -> (usize, Option<usize>) {
727 let len = self.j - self.i;
728 (len, Some(len))
729 }
730}
731
732impl<Color: RGBColor> ExactSizeIterator for PaletteIter<Color> {}
733
734impl<Color: RGBColor> DoubleEndedIterator for PaletteIter<Color>{
735 fn next_back(&mut self) -> Option<Self::Item> {
736 if self.i >= self.j { return None }
737 self.j -= 1;
738 Some(Palette::new(&self.palettes[self.j]))
739 }
740}
741
742
743/// Set criteria to find matching palettes.
744///
745/// Created by [`RGBColor::palettes`].
746#[derive(Clone)]
747pub struct PaletteFind<Color> {
748 len: usize,
749 typ: Vec<PaletteType>,
750 blind: Trivalent,
751 print: Trivalent,
752 photocopy: Trivalent,
753 lcd: Trivalent,
754 color: PhantomData<Color>,
755}
756
757fn satisfy(prop: Trivalent, specified: Trivalent) -> bool {
758 use Trivalent::*;
759 match specified {
760 Yes => matches!(prop, Yes),
761 No => true,
762 Maybe => matches!(prop, Yes | Maybe),
763 }
764}
765
766impl<Color> PaletteFind<Color>
767where Color: RGBColor {
768 /// Find [`Palette`]s with this type. Use several times to
769 /// specify more than one [`PaletteType`].
770 pub fn typ(mut self, t: PaletteType) -> Self {
771 self.typ.push(t);
772 self
773 }
774
775 /// Search palettes possibly ([`Trivalent::Maybe`]) or definitely
776 /// ([`Trivalent::Yes`]) suitable for color blind people.
777 pub fn blind(mut self, at_least: Trivalent) -> Self {
778 self.blind = at_least;
779 self
780 }
781
782 /// Search palettes possibly ([`Trivalent::Maybe`]) or definitely
783 /// ([`Trivalent::Yes`]) suitable for desktop color printing.
784 pub fn print(mut self, at_least: Trivalent) -> Self {
785 self.print = at_least;
786 self
787 }
788
789 /// Search palettes possibly ([`Trivalent::Maybe`]) or definitely
790 /// ([`Trivalent::Yes`]) suitable for black and white photocopying.
791 pub fn photocopy(mut self, at_least: Trivalent) -> Self {
792 self.photocopy = at_least;
793 self
794 }
795
796 /// Search palettes possibly ([`Trivalent::Maybe`]) or definitely
797 /// ([`Trivalent::Yes`]) suitable for LCD screens.
798 pub fn lcd(mut self, at_least: Trivalent) -> Self {
799 self.lcd = at_least;
800 self
801 }
802
803 /// Return an iterator on all known palettes.
804 fn all() -> impl Iterator<Item = Palette<Color>> {
805 palettes::ALL_MATPLOTLIB_PALETTES.iter().copied()
806 .chain(palettes::ALL_BREWER_PALETTES.iter()
807 .flat_map(|v| v.iter()))
808 .map(|c| Palette::new(c))
809 }
810
811 /// Return the list of palettes of length at least `len` (and
812 /// satisfying the criteria set with other methods).
813 pub fn find(self) -> impl Iterator<Item = Palette<Color>> {
814 use PaletteType::*;
815 let typ = { if self.typ.is_empty() { vec![Seq, Div, Qual] }
816 else { self.typ } };
817 Self::all().filter(move |p| {
818 p.len() >= self.len
819 && typ.contains(&p.palette.typ)
820 && satisfy(p.palette.blind, self.blind)
821 && satisfy(p.palette.print, self.print)
822 && satisfy(p.palette.photocopy, self.photocopy)
823 && satisfy(p.palette.lcd, self.lcd)
824 })
825 }
826}
827
828
829
830// color Blindness
831// http://vision.psychol.cam.ac.uk/jdmollon/papers/colourmaps.pdf
832// https://www.mapbox.com/blog/colorblind-simulation/
833// http://colororacle.org/ — https://github.com/nvkelso/colora-oracle-java
834
835
836
837#[cfg(test)]
838mod tests {
839 use super::*;
840
841 #[test]
842 fn hue_range() {
843 for (i, (x, c)) in RGB8::HUE.range(0., 1., 11).enumerate() {
844 assert!((x - 0.1 * i as f64).abs() <= 1e-15,
845 "{} ≉ {}", x, 0.1 * i as f64);
846 assert_eq!(RGB8::HUE.rgb(x), c);
847 }
848 }
849}