culors/samples.rs
1//! Evenly spaced ramp samples.
2//!
3//! Mirrors culori 4.0.2's `samples.js`:
4//!
5//! ```js
6//! const samples = (n = 2, γ = 1) => {
7//! let ease = gamma(γ);
8//! if (n < 2) {
9//! return n < 1 ? [] : [ease(0.5)];
10//! }
11//! let res = [];
12//! for (let i = 0; i < n; i++) {
13//! res.push(ease(i / (n - 1)));
14//! }
15//! return res;
16//! };
17//! ```
18//!
19//! [`samples`] keeps the linear (γ = 1) shorthand for backwards compatibility.
20//! [`samples_with_easing`] takes any `Fn(f64) -> f64`, letting callers plug in
21//! [`crate::easing_gamma`], smoothstep, or a custom curve.
22
23/// Returns `n` evenly spaced values in `[0, 1]` (linear, γ = 1).
24///
25/// Edge cases (matching culori exactly):
26/// - `n == 0` returns an empty vector.
27/// - `n == 1` returns `[0.5]`.
28/// - `n >= 2` returns `[0, 1/(n-1), 2/(n-1), …, 1]`.
29///
30/// Pair with [`crate::interpolate()`] to drive evenly spaced ramp generation:
31///
32/// ```rust
33/// use culors::{interpolate, parse, samples};
34/// let a = parse("oklch(70% 0.15 30deg)").unwrap();
35/// let b = parse("oklch(70% 0.15 200deg)").unwrap();
36/// let ramp = interpolate(&[a, b], "oklab");
37/// let stops: Vec<_> = samples(11).into_iter().map(ramp).collect();
38/// assert_eq!(stops.len(), 11);
39/// ```
40pub fn samples(n: usize) -> Vec<f64> {
41 samples_with_easing(n, |t| t)
42}
43
44/// `n` evenly spaced ramp positions, each transformed by `easing`.
45///
46/// Equivalent to culori's `samples(n, γ)` when `easing` is
47/// [`crate::easing_gamma(γ)`](crate::easing_gamma); the broader signature
48/// accepts any easing curve including [`crate::easing_smoothstep`].
49///
50/// ```rust
51/// use culors::{easing_gamma, samples_with_easing};
52/// // culori: samples(5, 2.2)
53/// let v = samples_with_easing(5, easing_gamma(2.2));
54/// assert!((v[2] - 0.217637640824031).abs() < 1e-12);
55/// ```
56pub fn samples_with_easing<F: Fn(f64) -> f64>(n: usize, easing: F) -> Vec<f64> {
57 if n < 2 {
58 return if n == 0 {
59 Vec::new()
60 } else {
61 vec![easing(0.5)]
62 };
63 }
64 let denom = (n - 1) as f64;
65 (0..n).map(|i| easing(i as f64 / denom)).collect()
66}