use core::convert::TryFrom;
use alloc::vec::Vec;
use crate::{convert_colors, BlendMode, Color, Gradient, GradientBuilder, GradientBuilderError};
#[inline]
fn basis(t1: f32, v0: f32, v1: f32, v2: f32, v3: f32) -> f32 {
let t2 = t1 * t1;
let t3 = t2 * t1;
((1.0 - 3.0 * t1 + 3.0 * t2 - t3) * v0
+ (4.0 - 6.0 * t2 + 3.0 * t3) * v1
+ (1.0 + 3.0 * t1 + 3.0 * t2 - 3.0 * t3) * v2
+ t3 * v3)
/ 6.0
}
#[cfg_attr(
feature = "named-colors",
doc = r##"
```
# fn main() -> Result<(), Box<dyn std::error::Error>> {
# use colorgrad::{GradientBuilder, BasisGradient};
use colorgrad::Gradient;
let grad = GradientBuilder::new()
.html_colors(&["deeppink", "gold", "seagreen"])
.build::<BasisGradient>()?;
# Ok(())
# }
```
"##
)]
#[derive(Debug, Clone)]
pub struct BasisGradient {
values: Vec<[f32; 4]>,
positions: Vec<f32>,
domain: (f32, f32),
mode: BlendMode,
first_color: Color,
last_color: Color,
}
impl BasisGradient {
pub(crate) fn new(colors: &[Color], positions: Vec<f32>, mode: BlendMode) -> Self {
let dmin = positions[0];
let dmax = positions[positions.len() - 1];
let first_color = colors[0].clone();
let last_color = colors[colors.len() - 1].clone();
Self {
values: convert_colors(colors, mode).collect(),
positions,
domain: (dmin, dmax),
mode,
first_color,
last_color,
}
}
}
impl Gradient for BasisGradient {
fn at(&self, t: f32) -> Color {
if t <= self.domain.0 {
return self.first_color.clone();
}
if t >= self.domain.1 {
return self.last_color.clone();
}
if t.is_nan() {
return Color::new(0.0, 0.0, 0.0, 1.0);
}
let mut low = 0;
let mut high = self.positions.len();
let n = high - 1;
while low < high {
let mid = (low + high) / 2;
if self.positions[mid] < t {
low = mid + 1;
} else {
high = mid;
}
}
if low == 0 {
low = 1;
}
let pos0 = self.positions[low - 1];
let pos1 = self.positions[low];
let val0 = self.values[low - 1];
let val1 = self.values[low];
let i = low - 1;
let t = (t - pos0) / (pos1 - pos0);
let mut zz = [0.0; 4];
for (j, (v1, v2)) in val0.iter().zip(val1.iter()).enumerate() {
let v0 = if i > 0 {
self.values[i - 1][j]
} else {
2.0 * v1 - v2
};
let v3 = if i < (n - 1) {
self.values[i + 2][j]
} else {
2.0 * v2 - v1
};
zz[j] = basis(t, v0, *v1, *v2, v3);
}
let [c0, c1, c2, c3] = zz;
match self.mode {
BlendMode::Rgb => Color::new(c0, c1, c2, c3),
BlendMode::LinearRgb => Color::from_linear_rgba(c0, c1, c2, c3),
BlendMode::Oklab => Color::from_oklaba(c0, c1, c2, c3),
#[cfg(feature = "lab")]
BlendMode::Lab => Color::from_laba(c0, c1, c2, c3),
}
}
fn domain(&self) -> (f32, f32) {
self.domain
}
}
impl TryFrom<&mut GradientBuilder> for BasisGradient {
type Error = GradientBuilderError;
fn try_from(gb: &mut GradientBuilder) -> Result<Self, Self::Error> {
gb.prepare_build()?;
Ok(Self::new(&gb.colors, gb.positions.clone(), gb.mode))
}
}