prismatica 1.0.0

308 scientific colormaps as compile-time Rust constants
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
<div align="center">

<h1>prismatica</h1>

[![Sponsor](https://img.shields.io/badge/Sponsor-resonant--jovian-ea4aaa?style=for-the-badge&logo=githubsponsors&logoColor=white)](https://github.com/sponsors/resonant-jovian)
[![Support on thanks.dev](https://img.shields.io/badge/thanks.dev-Support-green?style=for-the-badge&logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCI+PHBhdGggZmlsbD0id2hpdGUiIGQ9Ik0xMiAyMWMtNS41IDAtMTAtMy41LTEwLTkgMC00IDItNy41IDYtMTAgMS41IDIuNSAzIDQuNSA0IDQuNSAxLTEuNSAyLjUtMy41IDQtNC41IDQuNSAyLjUgNiA2IDYgMTAgMCA1LjUtNC41IDktMTAgOXoiLz48L3N2Zz4=)](https://thanks.dev/u/gh/resonant-jovian)

[![Crates.io](https://img.shields.io/crates/v/prismatica?style=for-the-badge&logo=rust&logoColor=white&label=crates.io)](https://crates.io/crates/prismatica)
[![docs.rs](https://img.shields.io/docsrs/prismatica?style=for-the-badge&logo=docsdotrs&logoColor=white&label=docs.rs)](https://docs.rs/prismatica)
[![Downloads](https://img.shields.io/crates/d/prismatica?style=for-the-badge&logo=rust&logoColor=white&color=e6761b)](https://crates.io/crates/prismatica)

[![License: GPL-3.0](https://img.shields.io/badge/License-GPL--3.0-3366cc?style=for-the-badge&logo=gnu&logoColor=white)](https://www.gnu.org/licenses/gpl-3.0)
[![MSRV](https://img.shields.io/badge/MSRV-1.85-3366cc?style=for-the-badge&logo=rust&logoColor=white)](https://releases.rs/docs/1.85.0/)
[![Edition](https://img.shields.io/badge/Edition-2024-3366cc?style=for-the-badge&logo=rust&logoColor=white)](https://doc.rust-lang.org/edition-guide/)

[![CI](https://img.shields.io/github/actions/workflow/status/resonant-jovian/prismatica/ci.yml?style=for-the-badge&logo=githubactions&logoColor=white&label=ci)](https://github.com/resonant-jovian/prismatica/actions/workflows/ci.yml)

</div>

**The universal compile-time scientific colormap library for Rust**

> [!TIP]
> The API is stable. Prismatica follows [Semantic Versioning]https://semver.org/ -- breaking changes require a major version bump.

---

## Status

| Component | Status | Count |
|---|---|---|
| Core types (`Color`, `Colormap`, `ColormapMeta`, `DiscretePalette`) | Done | |
| Registry (discovery, filtering, palette lookup) | Done | |
| Prelude module | Done | |
| matplotlib | Done | 8 maps |
| Crameri | Done | 40 maps |
| CET | Done | 59 maps |
| CMOcean | Done | 22 maps |
| Moreland | Done | 6 maps |
| CMasher | Done | 53 maps |
| ColorBrewer | Done | 35 maps + 35 palettes |
| CartoColors | Done | 34 maps + 34 palettes |
| NCAR NCL | Done | 44 maps |
| d3 | Done | 7 maps + 1 palette |
| Framework integrations (19 crates) | Done | |
| **Total** | | **308 colormaps + 70 palettes** |

---

## Highlights

- **308 scientific colormaps** from 10 established collections
- **70 discrete palettes** for categorical data (ColorBrewer, CartoColors, d3)
- **Compile-time constants** -- zero runtime file I/O, zero parsing
- **`#![no_std]`** -- core functionality works without allocation
- **Continuous sampling** -- linear interpolation between 256-step LUT entries for any `t` in `[0, 1]`
- **Rich metadata** -- every colormap carries its kind, perceptual uniformity flag, CVD safety, and citation
- **Feature-gated collections** -- include only the maps you need; ~1 KB per colormap
- **Framework integrations** -- bidirectional conversion for 19 crates including plotters, egui, image, bevy, ratatui, iced, palette, and more
- **Sibling project** -- [chromata]https://github.com/resonant-jovian/chromata provides editor color themes with the same Color type and integration pattern

---

## For Everyone

### What are scientific colormaps?

A colormap is a function that converts a scalar value (like temperature, elevation, or density) into a color. When you see a weather map where blue means cold and red means hot, that is a colormap at work.

**Perceptual uniformity** means that equal steps in data produce equal steps in perceived color difference. Without it, your visualization can create false features -- bright bands or phantom boundaries that exist in the colormap but not in the data. The classic "rainbow" and "jet" colormaps are notorious for this.

Prismatica provides every established scientific colormap collection as compile-time Rust data. A researcher writes `BATLOW.eval(0.5)` and gets a perceptually uniform, colorblind-safe color. No files to load, no dependencies to install.

### Quick start

Add prismatica to your project:

```toml
[dependencies]
prismatica = "1.0.0"
```

Use a colormap:

```rust
use prismatica::crameri::BATLOW;

let color = BATLOW.eval(0.5);
println!("RGB: ({}, {}, {})", color.r, color.g, color.b);
```

### Supported collections

| Collection | Author / Organization | Maps | Palettes | Type |
|---|---|---|---|---|
| Matplotlib | van der Walt, Smith, Firing | 8 | | Sequential, perceptually uniform |
| Crameri | Fabio Crameri | 40 | | Sequential, diverging, multi-sequential, cyclic |
| CET | Peter Kovesi | 59 | | Sequential, diverging, cyclic, rainbow, isoluminant |
| CMOcean | Kristen Thyng | 22 | | Oceanographic sequential, diverging |
| ColorBrewer | Cynthia Brewer | 35 | 35 | Sequential, diverging, qualitative |
| CMasher | Ellert van der Velden | 53 | | Sequential, diverging (astrophysics) |
| NCAR NCL | NCAR | 44 | | Geoscience sequential, diverging |
| CartoColors | CARTO | 34 | 34 | Cartographic sequential, diverging, qualitative |
| Moreland | Kenneth Moreland | 6 | | Cool-warm diverging, black body, Kindlmann |
| d3 | Mike Bostock | 7 | 1 | Turbo, Rainbow, Sinebow, Cubehelix, Tableau10 |
| **Total** | | **308** | **70** | |

### Choosing the right colormap

| Data type | Recommended maps | Why |
|---|---|---|
| **Sequential** (temperature, elevation) | `batlow`, `viridis`, `oslo`, `thermal` | Monotonic luminance, perceptually uniform |
| **Diverging** (anomalies, residuals) | `berlin`, `vik`, `balance`, `smooth-cool-warm` | Neutral center, symmetric extremes |
| **Cyclic** (phase, direction, time-of-day) | `romaO`, `phase`, `twilight` | End color equals start color |
| **Categorical** (labels, classes) | `SET2`, `DARK2`, `PAIRED`, `Tableau10` | Maximally distinct, non-interpolated |

### What prismatica is not

- Not a color manipulation library (use [`palette`]https://crates.io/crates/palette for that)
- Not a gradient builder (use [`colorgrad`]https://crates.io/crates/colorgrad for custom gradients)
- Not a rendering engine
- Not a data visualization framework

Prismatica is the data layer. It answers one question: *given a colormap name and a scalar `t` in `[0, 1]`, what RGB color should I use?*

---

## For Researchers

### The Color type

```rust
pub struct Color {
    pub r: u8,
    pub g: u8,
    pub b: u8,
}
```

Methods: `new(r, g, b)`, `from_hex(0xFF8800)`, `from_css_hex("#ff8800")` (also 3-digit: `"#FFF"`), `from_f32(r, g, b)`, `to_hex()`, `to_css_hex()`, `to_f32()`, `lerp(other, t)`, `luminance()`, `contrast_ratio(other)`. Implements `Display` (CSS hex), `Default` (black), `Ord` (lexicographic r,g,b), `FromStr` (`"#ff8800".parse()`), `From<u32>`, `From<[u8; 3]>`, and `From<(u8, u8, u8)>`. `from_css_hex` is `const fn`.

### Colormap sampling

```rust
use prismatica::crameri::BATLOW;

// Continuous: sample at any float in [0, 1]
let color = BATLOW.eval(0.5);

// Rational: the 30th of 100 evenly-spaced samples
let color = BATLOW.eval_rational(30, 100);

// Reversed direction (zero allocation)
let rev = BATLOW.reversed();
let color = rev.eval(0.2); // equivalent to BATLOW.eval(0.8)

// Extract N discrete colors
let legend_colors = BATLOW.colors(10);
```

Values outside `[0, 1]` are clamped. Interpolation is linear in sRGB space, matching matplotlib, ParaView, and most scientific tools.

### Colormap discovery

```rust
use prismatica::{ColormapKind, all_colormaps, find_by_name, filter_by_collection};

// Find all perceptually uniform diverging colormaps
let diverging: Vec<_> = all_colormaps()
    .iter()
    .filter(|cm| {
        cm.meta.kind == ColormapKind::Diverging
            && cm.meta.perceptually_uniform
    })
    .collect();

// Look up by name
let viridis = find_by_name("viridis").expect("viridis should exist");

// Filter by collection
let crameri_maps = filter_by_collection("crameri");
```

### Discrete palettes

ColorBrewer, CartoColors, and d3 provide discrete palettes alongside continuous colormaps:

```rust
use prismatica::colorbrewer::SET2_PALETTE;

for i in 0..SET2_PALETTE.len() {
    let c = SET2_PALETTE.get(i);
    println!("Category {}: #{:02x}{:02x}{:02x}", i, c.r, c.g, c.b);
}

// Also available via the registry
let palette = prismatica::find_palette_by_name("Blues").expect("Blues should exist");
```

### Feature flags

| Feature | Maps | Description |
|---|---|---|
| `core` (default) | 48 | matplotlib + Crameri -- the maps journals recommend |
| `matplotlib` | 8 | viridis, inferno, magma, plasma, cividis, twilight, mako, rocket |
| `crameri` | 40 | batlow, berlin, roma, oslo, tokyo, hawaii, and more |
| `cet` | 59 | CET-L*, CET-D*, CET-C*, CET-R* perceptually uniform maps |
| `cmocean` | 22 | thermal, haline, solar, ice, deep, and 17 more |
| `colorbrewer` | 35 + 35 palettes | Blues, RdBu, Set2, Spectral, and more |
| `cmasher` | 53 | ember, ocean, gothic, fusion, wildfire, and more |
| `ncar` | 44 | NCAR NCL geoscience colour tables |
| `cartocolors` | 34 + 34 palettes | CARTO cartographic colour schemes |
| `moreland` | 6 | cool-warm, black body, Kindlmann, extended variants |
| `d3` | 7 + 1 palette | Turbo, Rainbow, Sinebow, Cubehelix, Tableau10 |
| `all` | 308 + 70 palettes | All collections |

### Framework integrations

All integrations provide bidirectional conversion via `From`/`Into`. Enum-based types use `TryFrom` for the reverse direction. Enable `all-integrations` to get everything.

| Feature flag | Framework | Forward | Reverse |
|---|---|---|---|
| `egui-integration` | egui | `Color` -> `Color32` | `Color32` -> `Color` |
| `plotters-integration` | plotters | `Color` -> `RGBColor` | `RGBColor` -> `Color` |
| `image-integration` | image | `Color` -> `Rgb<u8>` | `Rgb<u8>` -> `Color` |
| `palette-integration` | palette | `Color` -> `Srgb<u8>` | `Srgb<u8>` -> `Color` |
| `bevy-color-integration` | bevy_color | `Color` -> `Srgba` | `Srgba` -> `Color` |
| `iced-integration` | iced | `Color` -> `iced::Color` | `iced::Color` -> `Color` |
| `macroquad-integration` | macroquad | `Color` -> `mq::Color` | `mq::Color` -> `Color` |
| `tiny-skia-integration` | tiny-skia | `Color` -> `ts::Color` | `ts::Color` -> `Color` |
| `wgpu-integration` | wgpu | `Color` -> `wgpu::Color` | `wgpu::Color` -> `Color` |
| `slint-integration` | slint | `Color` -> `slint::Color` | `slint::Color` -> `Color` |
| `ratatui-integration` | ratatui | `Color` -> `Color::Rgb` | `TryFrom` |
| `crossterm-integration` | crossterm | `Color` -> `Color::Rgb` | `TryFrom` |
| `colored-integration` | colored | `Color` -> `TrueColor` | `TryFrom` |
| `owo-colors-integration` | owo-colors | `Color` -> `Rgb` | `Rgb` -> `Color` |
| `termion-integration` | termion | `Color` -> `Rgb` | `Rgb` -> `Color` |
| `cursive-integration` | cursive | `Color` -> `Color::Rgb` | `TryFrom` |
| `comfy-table-integration` | comfy-table | `Color` -> `Color::Rgb` | `TryFrom` |
| `syntect-integration` | syntect | `Color` -> `hl::Color` | `hl::Color` -> `Color` |
| `serde-support` | serde | Serialize/Deserialize for meta types | |
| `all-integrations` | all of the above | | |

### Binary size

Each colormap is a 256x3 = 768-byte LUT plus ~200 bytes of metadata. Approximately **1 KB per colormap**.

| Feature | Maps | Size |
|---|---|---|
| `core` (default) | 48 | ~48 KB |
| `all` | 308 | ~308 KB |

Even with all 308 colormaps enabled, the total is smaller than a single PNG image.

### Metadata

Every colormap carries structured metadata for programmatic filtering:

```rust
pub struct ColormapMeta {
    pub name: &'static str,              // "batlow"
    pub collection: &'static str,        // "crameri"
    pub author: &'static str,            // "Fabio Crameri"
    pub kind: ColormapKind,              // Sequential, Diverging, Cyclic, ...
    pub perceptually_uniform: bool,      // true
    pub cvd_friendly: bool,              // true (colorblind safe)
    pub grayscale_safe: bool,            // true
    pub lut_size: usize,                 // 256
    pub citation: &'static str,          // DOI / reference string
}
```

### Citation

If you use prismatica in academic work, please cite the upstream colormap authors. Each colormap's `meta.citation` field contains the appropriate reference. Key citations:

- **Crameri:** Crameri, F. (2018). Scientific colour maps. Zenodo. doi:10.5281/zenodo.1243862
- **CET:** Kovesi, P. (2015). Good Colour Maps: How to Design Them. arXiv:1509.03700
- **CMOcean:** Thyng, K. M. et al. (2016). True colors of oceanography. Oceanography, 29(3), 10.
- **CMasher:** van der Velden, E. (2020). CMasher: Scientific colormaps for making accessible, informative and 'cmashing' plots. JOSS, 5(46), 2004.

---

## For Developers

### Architecture

Prismatica's core data model is simple:

1. A **lookup table (LUT)** of 256 evenly-spaced RGB values, stored as `static [[u8; 3]; 256]`
2. A **metadata struct** describing the colormap's properties
3. A **sampling function** that interpolates between LUT entries for any input `t` in `[0, 1]`

All colormap data is compiled into the binary as `const`/`static` arrays. There is no runtime I/O, no parsing, and no allocation for basic operations.

### Module structure

```
src/
├── lib.rs              # Crate-level docs, re-exports, feature gates
├── types.rs            # Color, Colormap, ColormapMeta, ColormapKind, DiscretePalette
├── traits.rs           # Framework conversion traits (feature-gated)
├── registry.rs         # all_colormaps(), find_by_name(), filter functions, palette functions
├── prelude.rs          # Convenience re-exports
├── integration/        # Feature-gated framework conversions (19 crates)
│
├── matplotlib/         # Feature: "matplotlib" — 8 maps
├── crameri/            # Feature: "crameri" — 40 maps
├── cet/                # Feature: "cet" — 59 maps
├── cmocean/            # Feature: "cmocean" — 22 maps
├── colorbrewer/        # Feature: "colorbrewer" — 35 maps + 35 palettes
├── cmasher/            # Feature: "cmasher" — 53 maps
├── ncar/               # Feature: "ncar" — 44 maps
├── cartocolors/        # Feature: "cartocolors" — 34 maps + 34 palettes
├── moreland/           # Feature: "moreland" — 6 maps
└── d3/                 # Feature: "d3" — 7 maps + 1 palette
```

### Code generation pipeline

Colormaps are not written by hand. A two-stage pipeline generates all colormap source code:

1. **Fetch** (`cargo xtask fetch [collection]`): Downloads upstream data and normalizes to `data/{collection}/{name}.csv` (256 rows of `R,G,B` as uint8) plus `data/{collection}/{name}.json` (metadata).

2. **Generate** (`cargo xtask generate [collection]`): Reads normalized CSV + JSON and emits Rust source files with `const` colormap definitions and `static` LUT arrays.

```
Upstream data -> cargo xtask fetch -> data/*.csv + data/*.json -> cargo xtask generate -> src/{collection}/*.rs
```

### Data sources

| Collection | Source | Format | License |
|---|---|---|---|
| Crameri | [Zenodo]https://zenodo.org/records/8409685 | 256x3 CSV (floats 0-1) | MIT |
| CET | [colorcet.com]https://colorcet.com/ | CSV (floats 0-1) | CC-BY |
| CMOcean | [GitHub]https://github.com/matplotlib/cmocean | Space-separated float .txt | MIT |
| ColorBrewer | [colorbrewer2.org]https://colorbrewer2.org/ | JSON (rgb() CSS strings) | Apache-2.0 |
| Matplotlib | [GitHub]https://github.com/matplotlib/matplotlib | Python 256x3 float arrays | CC0 |
| CMasher | [GitHub]https://github.com/1313e/CMasher | `.txt` LUT files (int/float) | BSD-3 |
| Moreland | [kennethmoreland.com]https://www.kennethmoreland.com/color-advice/ | CSV (int 0-255) | Public domain / BSD |
| NCAR NCL | [GitHub]https://github.com/NCAR/ncl | `.rgb` integer tables | Apache-2.0 |
| CartoColors | [GitHub]https://github.com/CartoDB/CartoColor | TypeScript hex arrays | CC-BY-3.0 |
| d3 | computed locally | Cubehelix/sinebow math | ISC |

### Adding new colormaps

To add a new collection:

1. Add a fetch module in `xtask/src/fetch/{collection}.rs`
2. Register it in `xtask/src/fetch/mod.rs`
3. Run `cargo xtask fetch {collection}` to populate `data/{collection}/`
4. Add generate module (or use the generic path) in `xtask/src/generate/`
5. Run `cargo xtask generate {collection}` to emit `src/{collection}/*.rs`
6. Add a feature flag in `Cargo.toml` and add to the `all` feature
7. Add `#[cfg(feature = "...")]` module declaration in `lib.rs`
8. Add the collection to `for_each_colormap()` in `registry.rs`

### Testing

```bash
cargo test                                         # All tests (unit + integration)
cargo test --all-features                          # With all collections enabled
cargo check --no-default-features --features core  # Verify no_std compatibility
cargo clippy --all-targets --all-features -- -D warnings  # Lint
cargo test --test property --features all          # Property-based tests (proptest)
cargo test --test snapshots --features all         # Snapshot tests (insta)
```

Snapshot tests use [insta](https://insta.rs/) to detect codegen changes. After modifying the code generation pipeline, run `cargo insta review` to inspect and accept updated snapshots.

### Code quality

```rust
#![no_std]
#![forbid(unsafe_code)]
#![deny(clippy::unwrap_used)]
#![warn(missing_docs)]
#![warn(unreachable_pub)]
```

---

## Competitive positioning

| Crate | Colormaps | LUT-based | Metadata | Collections | `no_std` |
|---|---|---|---|---|---|
| colorous | ~40 | Partial | None | d3 only | Yes |
| colorgrad | ~40 | No (interpolated) | None | d3 + custom | No |
| scarlet | ~5 | No (computed) | None | Basic only | No |
| **prismatica** | **308** | **Yes (256-step const)** | **Full** | **10 collections** | **Yes** |

---

## Minimum supported Rust version

Rust edition 2024, targeting **stable Rust 1.85+**.

## Support

If prismatica is useful to your projects, consider supporting development via [thanks.dev](https://thanks.dev/u/gh/resonant-jovian).

## License

This project is licensed under the [GNU General Public License v3.0](LICENSE).

Prismatica bundles colormap data from multiple upstream sources, each with its own license. All upstream licenses are permissive (MIT, Apache-2.0, CC-BY, CC0, BSD-3, public domain) and compatible with GPL-3.0. See individual collection module docs for source URLs and license details.