use crate::math;
pub type Color = [u8; 4];
#[derive(Clone, Copy, Debug, Default)]
struct GradientPoint {
pos: f64,
color: Color,
}
#[derive(Clone, Debug, Default)]
pub struct ColorGradient {
gradient_points: Vec<GradientPoint>,
}
impl ColorGradient {
pub fn new() -> Self {
let gradient = Self {
gradient_points: Vec::new(),
};
gradient.build_grayscale_gradient()
}
pub fn add_gradient_point(mut self, pos: f64, color: Color) -> Self {
if !self
.gradient_points
.iter()
.any(|&x| (x.pos - pos).abs() < std::f64::EPSILON)
{
let insertion_point = self.find_insertion_point(pos);
self.gradient_points
.insert(insertion_point, GradientPoint { pos, color });
}
self
}
fn find_insertion_point(&self, pos: f64) -> usize {
self.gradient_points
.iter()
.position(|x| x.pos >= pos)
.unwrap_or_else(|| self.gradient_points.len())
}
pub fn clear_gradient(mut self) -> Self {
self.gradient_points.clear();
self
}
pub fn build_grayscale_gradient(self) -> Self {
self.clear_gradient()
.add_gradient_point(-1.0, [0, 0, 0, 255])
.add_gradient_point(1.0, [255, 255, 255, 255])
}
pub fn build_terrain_gradient(self) -> Self {
self.clear_gradient()
.add_gradient_point(-1.00, [0, 0, 128, 255])
.add_gradient_point(-0.20, [32, 64, 128, 255])
.add_gradient_point(-0.04, [64, 96, 192, 255])
.add_gradient_point(-0.02, [192, 192, 128, 255])
.add_gradient_point(0.00, [0, 192, 0, 255])
.add_gradient_point(0.25, [192, 192, 0, 255])
.add_gradient_point(0.50, [160, 96, 64, 255])
.add_gradient_point(0.75, [128, 255, 255, 255])
.add_gradient_point(1.00, [255, 255, 255, 255])
}
pub fn build_rainbow_gradient(self) -> Self {
self.clear_gradient()
.add_gradient_point(-1.0, [255, 0, 0, 255])
.add_gradient_point(-0.7, [255, 255, 0, 255])
.add_gradient_point(-0.4, [0, 255, 0, 255])
.add_gradient_point(0.0, [0, 255, 255, 255])
.add_gradient_point(0.3, [0, 0, 255, 255])
.add_gradient_point(0.6, [255, 0, 255, 255])
.add_gradient_point(1.0, [255, 0, 0, 255])
}
pub fn get_color(&self, pos: f64) -> Color {
assert!(self.gradient_points.len() >= 2);
let clamped_pos = math::clamp(
pos,
self.gradient_points[0].pos,
self.gradient_points[self.gradient_points.len() - 1].pos,
);
let index = self
.gradient_points
.iter()
.position(|&x| (x.pos > clamped_pos))
.unwrap_or_else(|| self.gradient_points.len());
if index < 1 {
println!(
"index_pos in curve was less than 1! source value was {}",
pos
);
}
let index1 = math::clamp(index - 1, 0, self.gradient_points.len() - 1);
let index2 = math::clamp(index, 0, self.gradient_points.len() - 1);
if index1 == index2 {
return self.gradient_points[index1].color;
}
let input0 = self.gradient_points[index1].pos;
let input1 = self.gradient_points[index2].pos;
let alpha = (pos - input0) / (input1 - input0);
linerp_color(
self.gradient_points[index1].color,
self.gradient_points[index2].color,
alpha,
)
}
}
fn blend_channels(channel0: u8, channel1: u8, alpha: f64) -> u8 {
let c0 = (f64::from(channel0)) / 255.0;
let c1 = (f64::from(channel1)) / 255.0;
(((c1 * alpha) + (c0 * (1.0 - alpha))) * 255.0) as u8
}
fn linerp_color(color0: Color, color1: Color, alpha: f64) -> Color {
let r = blend_channels(color0[0], color1[0], alpha);
let g = blend_channels(color0[1], color1[1], alpha);
let b = blend_channels(color0[2], color1[2], alpha);
let a = blend_channels(color0[3], color1[3], alpha);
[r, g, b, a]
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn blend_channels_min() {
let result = blend_channels(0, 255, 0.0);
assert_eq!(
0, result,
"blend_channels should've created 0, produced `{}` instead",
result
);
}
#[test]
fn blend_channels_mid() {
let result = blend_channels(0, 255, 0.5);
assert_eq!(
127, result,
"blend_channels should've created 127, produced `{}` instead",
result
);
}
#[test]
fn blend_channels_max() {
let result = blend_channels(0, 255, 1.0);
assert_eq!(
255, result,
"blend_channels should've created 255, produced `{}` instead",
result
);
}
#[test]
fn linerp_color_1() {
assert_eq!(
[0, 127, 255, 0],
linerp_color([0, 0, 255, 0], [0, 255, 255, 0], 0.5)
);
}
#[test]
fn color_gradient_1() {
let gradient = ColorGradient::new();
let gradient = gradient
.clear_gradient()
.add_gradient_point(0.0, [0, 0, 0, 0])
.add_gradient_point(1.0, [255, 255, 255, 255]);
assert_eq!([127, 127, 127, 127], gradient.get_color(0.5));
}
}