#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Color {
pub r: f64,
pub g: f64,
pub b: f64,
pub a: f64,
}
impl Color {
pub const fn rgb(r: f64, g: f64, b: f64) -> Self {
Self { r, g, b, a: 1.0 }
}
pub const fn rgba(r: f64, g: f64, b: f64, a: f64) -> Self {
Self { r, g, b, a }
}
pub fn to_rgba8(&self) -> [u8; 4] {
[
(self.r * 255.0) as u8,
(self.g * 255.0) as u8,
(self.b * 255.0) as u8,
(self.a * 255.0) as u8,
]
}
pub const BLACK: Color = Color::rgb(0.0, 0.0, 0.0);
pub const WHITE: Color = Color::rgb(1.0, 1.0, 1.0);
pub const TRANSPARENT: Color = Color::rgba(0.0, 0.0, 0.0, 0.0);
}
#[derive(Debug, Clone)]
pub struct ColorScaleLevel {
value: f32,
color: Color,
}
impl ColorScaleLevel {
pub fn new(value: f32, color: Color) -> Self {
Self { value, color }
}
}
#[derive(Debug, Clone)]
pub struct DiscreteColorScale {
levels: Vec<ColorScaleLevel>,
}
impl DiscreteColorScale {
pub fn new(mut levels: Vec<ColorScaleLevel>) -> Self {
levels.sort_by(|a, b| b.value.total_cmp(&a.value));
Self { levels }
}
pub fn color(&self, value: f32) -> Color {
let mut color = Color::BLACK;
for level in &self.levels {
if value >= level.value {
return level.color;
}
color = level.color;
}
color
}
pub fn levels(&self) -> &[ColorScaleLevel] {
&self.levels
}
}
#[derive(Debug, Clone)]
pub struct ColorStop {
pub value: f32,
pub color: Color,
}
impl ColorStop {
pub fn new(value: f32, color: Color) -> Self {
Self { value, color }
}
}
#[derive(Debug, Clone)]
pub struct ContinuousColorScale {
stops: Vec<ColorStop>,
}
impl ContinuousColorScale {
pub fn new(mut stops: Vec<ColorStop>) -> Self {
stops.sort_by(|a, b| a.value.total_cmp(&b.value));
Self { stops }
}
pub fn color(&self, value: f32) -> Color {
if self.stops.is_empty() {
return Color::BLACK;
}
if value <= self.stops[0].value {
return self.stops[0].color;
}
let last = self.stops.len() - 1;
if value >= self.stops[last].value {
return self.stops[last].color;
}
for i in 0..last {
let low = &self.stops[i];
let high = &self.stops[i + 1];
if value >= low.value && value <= high.value {
let range = high.value - low.value;
if range == 0.0 {
return low.color;
}
let t = ((value - low.value) / range) as f64;
return Color::rgba(
low.color.r + (high.color.r - low.color.r) * t,
low.color.g + (high.color.g - low.color.g) * t,
low.color.b + (high.color.b - low.color.b) * t,
low.color.a + (high.color.a - low.color.a) * t,
);
}
}
self.stops[last].color
}
pub fn stops(&self) -> &[ColorStop] {
&self.stops
}
}
#[derive(Debug, Clone)]
pub enum ColorScale {
Discrete(DiscreteColorScale),
Continuous(ContinuousColorScale),
}
impl ColorScale {
pub fn color(&self, value: f32) -> Color {
match self {
ColorScale::Discrete(scale) => scale.color(value),
ColorScale::Continuous(scale) => scale.color(value),
}
}
}
impl From<DiscreteColorScale> for ColorScale {
fn from(scale: DiscreteColorScale) -> Self {
ColorScale::Discrete(scale)
}
}
impl From<ContinuousColorScale> for ColorScale {
fn from(scale: ContinuousColorScale) -> Self {
ColorScale::Continuous(scale)
}
}
#[derive(Debug, Clone)]
pub struct ColorLookupTable {
table: Vec<[u8; 4]>,
min_value: f32,
range: f32,
}
impl ColorLookupTable {
pub fn from_scale(
scale: &DiscreteColorScale,
min_value: f32,
max_value: f32,
size: usize,
) -> Self {
let range = max_value - min_value;
let mut table = Vec::with_capacity(size);
for i in 0..size {
let value = min_value + (i as f32 / (size - 1) as f32) * range;
let color = scale.color(value);
table.push(color.to_rgba8());
}
Self {
table,
min_value,
range,
}
}
pub fn from_color_scale(
scale: &ColorScale,
min_value: f32,
max_value: f32,
size: usize,
) -> Self {
let range = max_value - min_value;
let mut table = Vec::with_capacity(size);
for i in 0..size {
let value = min_value + (i as f32 / (size - 1) as f32) * range;
let color = scale.color(value);
table.push(color.to_rgba8());
}
Self {
table,
min_value,
range,
}
}
#[inline]
pub fn color(&self, value: f32) -> [u8; 4] {
let normalized = (value - self.min_value) / self.range;
let index = (normalized * (self.table.len() - 1) as f32) as usize;
let index = index.min(self.table.len() - 1);
self.table[index]
}
}
pub fn nws_reflectivity_scale() -> DiscreteColorScale {
DiscreteColorScale::new(vec![
ColorScaleLevel::new(0.0, Color::rgb(0.0000, 0.0000, 0.0000)),
ColorScaleLevel::new(5.0, Color::rgb(0.0000, 1.0000, 1.0000)),
ColorScaleLevel::new(10.0, Color::rgb(0.5294, 0.8078, 0.9216)),
ColorScaleLevel::new(15.0, Color::rgb(0.0000, 0.0000, 1.0000)),
ColorScaleLevel::new(20.0, Color::rgb(0.0000, 1.0000, 0.0000)),
ColorScaleLevel::new(25.0, Color::rgb(0.1961, 0.8039, 0.1961)),
ColorScaleLevel::new(30.0, Color::rgb(0.1333, 0.5451, 0.1333)),
ColorScaleLevel::new(35.0, Color::rgb(0.9333, 0.9333, 0.0000)),
ColorScaleLevel::new(40.0, Color::rgb(0.9333, 0.8627, 0.5098)),
ColorScaleLevel::new(45.0, Color::rgb(0.9333, 0.4627, 0.1294)),
ColorScaleLevel::new(50.0, Color::rgb(1.0000, 0.1882, 0.1882)),
ColorScaleLevel::new(55.0, Color::rgb(0.6902, 0.1882, 0.3765)),
ColorScaleLevel::new(60.0, Color::rgb(0.6902, 0.1882, 0.3765)),
ColorScaleLevel::new(65.0, Color::rgb(0.7294, 0.3333, 0.8275)),
ColorScaleLevel::new(70.0, Color::rgb(1.0000, 0.0000, 1.0000)),
ColorScaleLevel::new(75.0, Color::rgb(1.0000, 1.0000, 1.0000)),
])
}
pub fn velocity_scale() -> DiscreteColorScale {
DiscreteColorScale::new(vec![
ColorScaleLevel::new(-64.0, Color::rgb(0.0000, 0.3922, 0.0000)),
ColorScaleLevel::new(-48.0, Color::rgb(0.0000, 0.5451, 0.0000)),
ColorScaleLevel::new(-32.0, Color::rgb(0.0000, 0.8039, 0.0000)),
ColorScaleLevel::new(-16.0, Color::rgb(0.5647, 0.9333, 0.5647)),
ColorScaleLevel::new(-4.0, Color::rgb(0.6627, 0.6627, 0.6627)),
ColorScaleLevel::new(4.0, Color::rgb(0.6627, 0.6627, 0.6627)),
ColorScaleLevel::new(16.0, Color::rgb(1.0000, 0.7529, 0.7961)),
ColorScaleLevel::new(32.0, Color::rgb(1.0000, 0.4118, 0.4118)),
ColorScaleLevel::new(48.0, Color::rgb(0.8039, 0.0000, 0.0000)),
ColorScaleLevel::new(64.0, Color::rgb(0.5451, 0.0000, 0.0000)),
])
}
pub fn spectrum_width_scale() -> DiscreteColorScale {
DiscreteColorScale::new(vec![
ColorScaleLevel::new(0.0, Color::rgb(0.5020, 0.5020, 0.5020)),
ColorScaleLevel::new(4.0, Color::rgb(0.0000, 0.0000, 0.8039)),
ColorScaleLevel::new(8.0, Color::rgb(0.0000, 0.8039, 0.8039)),
ColorScaleLevel::new(12.0, Color::rgb(0.0000, 0.8039, 0.0000)),
ColorScaleLevel::new(16.0, Color::rgb(0.9333, 0.9333, 0.0000)),
ColorScaleLevel::new(20.0, Color::rgb(1.0000, 0.6471, 0.0000)),
ColorScaleLevel::new(25.0, Color::rgb(1.0000, 0.0000, 0.0000)),
])
}
pub fn differential_reflectivity_scale() -> DiscreteColorScale {
DiscreteColorScale::new(vec![
ColorScaleLevel::new(-2.0, Color::rgb(0.5020, 0.0000, 0.5020)),
ColorScaleLevel::new(-1.0, Color::rgb(0.0000, 0.0000, 0.8039)),
ColorScaleLevel::new(0.0, Color::rgb(0.6627, 0.6627, 0.6627)),
ColorScaleLevel::new(0.5, Color::rgb(0.5647, 0.9333, 0.5647)),
ColorScaleLevel::new(1.5, Color::rgb(0.9333, 0.9333, 0.0000)),
ColorScaleLevel::new(2.5, Color::rgb(1.0000, 0.6471, 0.0000)),
ColorScaleLevel::new(4.0, Color::rgb(1.0000, 0.0000, 0.0000)),
])
}
pub fn correlation_coefficient_scale() -> DiscreteColorScale {
DiscreteColorScale::new(vec![
ColorScaleLevel::new(0.0, Color::rgb(0.0000, 0.0000, 0.0000)),
ColorScaleLevel::new(0.2, Color::rgb(0.3922, 0.0000, 0.5882)),
ColorScaleLevel::new(0.5, Color::rgb(0.0000, 0.0000, 0.8039)),
ColorScaleLevel::new(0.7, Color::rgb(0.0000, 0.5451, 0.5451)),
ColorScaleLevel::new(0.85, Color::rgb(0.0000, 0.8039, 0.4000)),
ColorScaleLevel::new(0.92, Color::rgb(0.0000, 0.8039, 0.0000)),
ColorScaleLevel::new(0.96, Color::rgb(0.9333, 0.9333, 0.0000)),
ColorScaleLevel::new(0.98, Color::rgb(0.9020, 0.9020, 0.9020)),
])
}
pub fn differential_phase_scale() -> DiscreteColorScale {
DiscreteColorScale::new(vec![
ColorScaleLevel::new(0.0, Color::rgb(0.5020, 0.0000, 0.5020)),
ColorScaleLevel::new(45.0, Color::rgb(0.0000, 0.0000, 0.8039)),
ColorScaleLevel::new(90.0, Color::rgb(0.0000, 0.8039, 0.8039)),
ColorScaleLevel::new(135.0, Color::rgb(0.0000, 0.8039, 0.0000)),
ColorScaleLevel::new(180.0, Color::rgb(0.9333, 0.9333, 0.0000)),
ColorScaleLevel::new(225.0, Color::rgb(1.0000, 0.6471, 0.0000)),
ColorScaleLevel::new(270.0, Color::rgb(1.0000, 0.0000, 0.0000)),
ColorScaleLevel::new(315.0, Color::rgb(1.0000, 0.0000, 1.0000)),
])
}
pub fn clutter_filter_power_scale() -> DiscreteColorScale {
DiscreteColorScale::new(vec![
ColorScaleLevel::new(-20.0, Color::rgb(0.0000, 0.0000, 0.5451)),
ColorScaleLevel::new(-10.0, Color::rgb(0.0000, 0.0000, 0.8039)),
ColorScaleLevel::new(-5.0, Color::rgb(0.6784, 0.8471, 0.9020)),
ColorScaleLevel::new(-1.0, Color::rgb(0.6627, 0.6627, 0.6627)),
ColorScaleLevel::new(1.0, Color::rgb(0.6627, 0.6627, 0.6627)),
ColorScaleLevel::new(5.0, Color::rgb(1.0000, 0.7529, 0.7961)),
ColorScaleLevel::new(10.0, Color::rgb(1.0000, 0.4118, 0.4118)),
ColorScaleLevel::new(20.0, Color::rgb(0.8039, 0.0000, 0.0000)),
])
}