use super::interpolation::interpolate;
use super::types::{ColorStop, InterpolationMode, SpreadMode};
use crate::style::Color;
#[derive(Clone, Debug)]
pub struct Gradient {
stops: Vec<ColorStop>,
pub interpolation: InterpolationMode,
pub spread: SpreadMode,
}
impl Default for Gradient {
fn default() -> Self {
Self {
stops: vec![ColorStop::start(Color::BLACK), ColorStop::end(Color::WHITE)],
interpolation: InterpolationMode::default(),
spread: SpreadMode::default(),
}
}
}
impl Gradient {
pub fn new(mut stops: Vec<ColorStop>) -> Self {
stops.sort_by(|a, b| {
a.position
.partial_cmp(&b.position)
.unwrap_or(std::cmp::Ordering::Equal)
});
Self {
stops,
interpolation: InterpolationMode::default(),
spread: SpreadMode::default(),
}
}
pub fn linear(from: Color, to: Color) -> Self {
Self::new(vec![ColorStop::start(from), ColorStop::end(to)])
}
pub fn three(start: Color, middle: Color, end: Color) -> Self {
Self::new(vec![
ColorStop::new(0.0, start),
ColorStop::new(0.5, middle),
ColorStop::new(1.0, end),
])
}
pub fn from_colors(colors: &[Color]) -> Self {
if colors.is_empty() {
return Self::default();
}
if colors.len() == 1 {
return Self::linear(colors[0], colors[0]);
}
let step = 1.0 / (colors.len() - 1) as f32;
let stops = colors
.iter()
.enumerate()
.map(|(i, &color)| ColorStop::new(i as f32 * step, color))
.collect();
Self::new(stops)
}
pub fn interpolation(mut self, mode: InterpolationMode) -> Self {
self.interpolation = mode;
self
}
pub fn spread(mut self, mode: SpreadMode) -> Self {
self.spread = mode;
self
}
pub fn add_stop(&mut self, stop: ColorStop) {
self.stops.push(stop);
self.stops.sort_by(|a, b| {
a.position
.partial_cmp(&b.position)
.unwrap_or(std::cmp::Ordering::Equal)
});
}
pub fn len(&self) -> usize {
self.stops.len()
}
pub fn is_empty(&self) -> bool {
self.stops.is_empty()
}
pub fn stops(&self) -> &[ColorStop] {
&self.stops
}
fn normalize_position(&self, t: f32) -> f32 {
match self.spread {
SpreadMode::Clamp => t.clamp(0.0, 1.0),
SpreadMode::Repeat => t.rem_euclid(1.0),
SpreadMode::Reflect => {
let t = t.rem_euclid(2.0);
if t > 1.0 {
2.0 - t
} else {
t
}
}
}
}
pub fn at(&self, t: f32) -> Color {
if self.stops.is_empty() {
return Color::BLACK;
}
if self.stops.len() == 1 {
return self.stops[0].color;
}
let t = self.normalize_position(t);
let mut prev_stop = &self.stops[0];
let mut next_stop = &self.stops[self.stops.len() - 1];
for stop in &self.stops {
if stop.position <= t {
prev_stop = stop;
}
if stop.position >= t && stop.position < next_stop.position {
next_stop = stop;
break;
}
}
if prev_stop.position >= next_stop.position {
return prev_stop.color;
}
let local_t = (t - prev_stop.position) / (next_stop.position - prev_stop.position);
interpolate(
prev_stop.color,
next_stop.color,
local_t,
self.interpolation,
)
}
pub fn colors(&self, width: usize) -> Vec<Color> {
if width == 0 {
return vec![];
}
if width == 1 {
return vec![self.at(0.5)];
}
(0..width)
.map(|i| {
let t = i as f32 / (width - 1) as f32;
self.at(t)
})
.collect()
}
pub fn reversed(&self) -> Self {
let stops = self
.stops
.iter()
.map(|s| ColorStop::new(1.0 - s.position, s.color))
.rev()
.collect();
Self {
stops,
interpolation: self.interpolation,
spread: self.spread,
}
}
}