use crate::{
color_difference::EuclideanDistance, impl_componentwise_vector_space, Alpha, ColorToComponents,
ColorToPacked, Gray, Luminance, Mix, StandardColor,
};
use bevy_math::{Vec3, Vec4};
#[cfg(feature = "bevy_reflect")]
use bevy_reflect::prelude::*;
use bytemuck::{Pod, Zeroable};
#[doc = include_str!("../docs/conversion.md")]
#[doc = include_str!("../docs/diagrams/model_graph.svg")]
#[derive(Debug, Clone, Copy, PartialEq, Pod, Zeroable)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(PartialEq, Default))]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
all(feature = "serialize", feature = "bevy_reflect"),
reflect(Serialize, Deserialize)
)]
#[repr(C)]
pub struct LinearRgba {
pub red: f32,
pub green: f32,
pub blue: f32,
pub alpha: f32,
}
impl StandardColor for LinearRgba {}
impl_componentwise_vector_space!(LinearRgba, [red, green, blue, alpha]);
impl LinearRgba {
pub const BLACK: Self = Self {
red: 0.0,
green: 0.0,
blue: 0.0,
alpha: 1.0,
};
pub const WHITE: Self = Self {
red: 1.0,
green: 1.0,
blue: 1.0,
alpha: 1.0,
};
pub const NONE: Self = Self {
red: 0.0,
green: 0.0,
blue: 0.0,
alpha: 0.0,
};
pub const RED: Self = Self {
red: 1.0,
green: 0.0,
blue: 0.0,
alpha: 1.0,
};
pub const GREEN: Self = Self {
red: 0.0,
green: 1.0,
blue: 0.0,
alpha: 1.0,
};
pub const BLUE: Self = Self {
red: 0.0,
green: 0.0,
blue: 1.0,
alpha: 1.0,
};
pub const NAN: Self = Self {
red: f32::NAN,
green: f32::NAN,
blue: f32::NAN,
alpha: f32::NAN,
};
pub const fn new(red: f32, green: f32, blue: f32, alpha: f32) -> Self {
Self {
red,
green,
blue,
alpha,
}
}
pub const fn rgb(red: f32, green: f32, blue: f32) -> Self {
Self {
red,
green,
blue,
alpha: 1.0,
}
}
pub const fn with_red(self, red: f32) -> Self {
Self { red, ..self }
}
pub const fn with_green(self, green: f32) -> Self {
Self { green, ..self }
}
pub const fn with_blue(self, blue: f32) -> Self {
Self { blue, ..self }
}
fn adjust_lightness(&mut self, amount: f32) {
let luminance = self.luminance();
let target_luminance = (luminance + amount).clamp(0.0, 1.0);
if target_luminance < luminance {
let adjustment = (luminance - target_luminance) / luminance;
self.mix_assign(Self::new(0.0, 0.0, 0.0, self.alpha), adjustment);
} else if target_luminance > luminance {
let adjustment = (target_luminance - luminance) / (1. - luminance);
self.mix_assign(Self::new(1.0, 1.0, 1.0, self.alpha), adjustment);
}
}
pub fn as_u32(&self) -> u32 {
u32::from_le_bytes(self.to_u8_array())
}
}
impl Default for LinearRgba {
fn default() -> Self {
Self::WHITE
}
}
impl Luminance for LinearRgba {
#[inline]
fn luminance(&self) -> f32 {
self.red * 0.2126 + self.green * 0.7152 + self.blue * 0.0722
}
#[inline]
fn with_luminance(&self, luminance: f32) -> Self {
let current_luminance = self.luminance();
let adjustment = luminance / current_luminance;
Self {
red: (self.red * adjustment).clamp(0., 1.),
green: (self.green * adjustment).clamp(0., 1.),
blue: (self.blue * adjustment).clamp(0., 1.),
alpha: self.alpha,
}
}
#[inline]
fn darker(&self, amount: f32) -> Self {
let mut result = *self;
result.adjust_lightness(-amount);
result
}
#[inline]
fn lighter(&self, amount: f32) -> Self {
let mut result = *self;
result.adjust_lightness(amount);
result
}
}
impl Mix for LinearRgba {
#[inline]
fn mix(&self, other: &Self, factor: f32) -> Self {
let n_factor = 1.0 - factor;
Self {
red: self.red * n_factor + other.red * factor,
green: self.green * n_factor + other.green * factor,
blue: self.blue * n_factor + other.blue * factor,
alpha: self.alpha * n_factor + other.alpha * factor,
}
}
}
impl Gray for LinearRgba {
const BLACK: Self = Self::BLACK;
const WHITE: Self = Self::WHITE;
}
impl Alpha for LinearRgba {
#[inline]
fn with_alpha(&self, alpha: f32) -> Self {
Self { alpha, ..*self }
}
#[inline]
fn alpha(&self) -> f32 {
self.alpha
}
#[inline]
fn set_alpha(&mut self, alpha: f32) {
self.alpha = alpha;
}
}
impl EuclideanDistance for LinearRgba {
#[inline]
fn distance_squared(&self, other: &Self) -> f32 {
let dr = self.red - other.red;
let dg = self.green - other.green;
let db = self.blue - other.blue;
dr * dr + dg * dg + db * db
}
}
impl ColorToComponents for LinearRgba {
fn to_f32_array(self) -> [f32; 4] {
[self.red, self.green, self.blue, self.alpha]
}
fn to_f32_array_no_alpha(self) -> [f32; 3] {
[self.red, self.green, self.blue]
}
fn to_vec4(self) -> Vec4 {
Vec4::new(self.red, self.green, self.blue, self.alpha)
}
fn to_vec3(self) -> Vec3 {
Vec3::new(self.red, self.green, self.blue)
}
fn from_f32_array(color: [f32; 4]) -> Self {
Self {
red: color[0],
green: color[1],
blue: color[2],
alpha: color[3],
}
}
fn from_f32_array_no_alpha(color: [f32; 3]) -> Self {
Self {
red: color[0],
green: color[1],
blue: color[2],
alpha: 1.0,
}
}
fn from_vec4(color: Vec4) -> Self {
Self {
red: color[0],
green: color[1],
blue: color[2],
alpha: color[3],
}
}
fn from_vec3(color: Vec3) -> Self {
Self {
red: color[0],
green: color[1],
blue: color[2],
alpha: 1.0,
}
}
}
impl ColorToPacked for LinearRgba {
fn to_u8_array(self) -> [u8; 4] {
[self.red, self.green, self.blue, self.alpha]
.map(|v| (v.clamp(0.0, 1.0) * 255.0).round() as u8)
}
fn to_u8_array_no_alpha(self) -> [u8; 3] {
[self.red, self.green, self.blue].map(|v| (v.clamp(0.0, 1.0) * 255.0).round() as u8)
}
fn from_u8_array(color: [u8; 4]) -> Self {
Self::from_f32_array(color.map(|u| u as f32 / 255.0))
}
fn from_u8_array_no_alpha(color: [u8; 3]) -> Self {
Self::from_f32_array_no_alpha(color.map(|u| u as f32 / 255.0))
}
}
#[cfg(feature = "wgpu-types")]
impl From<LinearRgba> for wgpu_types::Color {
fn from(color: LinearRgba) -> Self {
wgpu_types::Color {
r: color.red as f64,
g: color.green as f64,
b: color.blue as f64,
a: color.alpha as f64,
}
}
}
impl encase::ShaderType for LinearRgba {
type ExtraMetadata = ();
const METADATA: encase::private::Metadata<Self::ExtraMetadata> = {
let size =
encase::private::SizeValue::from(<f32 as encase::private::ShaderSize>::SHADER_SIZE)
.mul(4);
let alignment = encase::private::AlignmentValue::from_next_power_of_two_size(size);
encase::private::Metadata {
alignment,
has_uniform_min_alignment: false,
is_pod: true,
min_size: size,
extra: (),
}
};
const UNIFORM_COMPAT_ASSERT: fn() = || {};
}
impl encase::private::WriteInto for LinearRgba {
fn write_into<B: encase::private::BufferMut>(&self, writer: &mut encase::private::Writer<B>) {
for el in &[self.red, self.green, self.blue, self.alpha] {
encase::private::WriteInto::write_into(el, writer);
}
}
}
impl encase::private::ReadFrom for LinearRgba {
fn read_from<B: encase::private::BufferRef>(
&mut self,
reader: &mut encase::private::Reader<B>,
) {
let mut buffer = [0.0f32; 4];
for el in &mut buffer {
encase::private::ReadFrom::read_from(el, reader);
}
*self = LinearRgba {
red: buffer[0],
green: buffer[1],
blue: buffer[2],
alpha: buffer[3],
}
}
}
impl encase::private::CreateFrom for LinearRgba {
fn create_from<B>(reader: &mut encase::private::Reader<B>) -> Self
where
B: encase::private::BufferRef,
{
let red: f32 = encase::private::CreateFrom::create_from(reader);
let green: f32 = encase::private::CreateFrom::create_from(reader);
let blue: f32 = encase::private::CreateFrom::create_from(reader);
let alpha: f32 = encase::private::CreateFrom::create_from(reader);
LinearRgba {
red,
green,
blue,
alpha,
}
}
}
impl encase::ShaderSize for LinearRgba {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn euclidean_distance() {
let a = LinearRgba::new(0.0, 0.0, 0.0, 1.0);
let b = LinearRgba::new(1.0, 1.0, 1.0, 1.0);
assert_eq!(a.distance_squared(&b), 3.0);
let a = LinearRgba::new(0.0, 0.0, 0.0, 1.0);
let b = LinearRgba::new(1.0, 1.0, 1.0, 0.0);
assert_eq!(a.distance_squared(&b), 3.0);
let a = LinearRgba::new(0.0, 0.0, 0.0, 1.0);
let b = LinearRgba::new(1.0, 0.0, 0.0, 1.0);
assert_eq!(a.distance_squared(&b), 1.0);
}
#[test]
fn to_and_from_u8() {
let a = LinearRgba::from_u8_array([255, 0, 0, 255]);
let b = LinearRgba::new(1.0, 0.0, 0.0, 1.0);
assert_eq!(a, b);
let a = LinearRgba::from_u8_array_no_alpha([255, 255, 0]);
let b = LinearRgba::rgb(1.0, 1.0, 0.0);
assert_eq!(a, b);
let a = LinearRgba::new(0.0, 0.0, 1.0, 1.0).to_u8_array();
let b = [0, 0, 255, 255];
assert_eq!(a, b);
let a = LinearRgba::rgb(0.0, 1.0, 1.0).to_u8_array_no_alpha();
let b = [0, 255, 255];
assert_eq!(a, b);
let a = LinearRgba::rgb(0.0, 100.0, -100.0).to_u8_array_no_alpha();
let b = [0, 255, 0];
assert_eq!(a, b);
}
#[test]
fn darker_lighter() {
let color = LinearRgba::new(0.4, 0.5, 0.6, 1.0);
let darker1 = color.darker(0.1);
let darker2 = darker1.darker(0.1);
let twice_as_dark = color.darker(0.2);
assert!(darker2.distance_squared(&twice_as_dark) < 0.0001);
let lighter1 = color.lighter(0.1);
let lighter2 = lighter1.lighter(0.1);
let twice_as_light = color.lighter(0.2);
assert!(lighter2.distance_squared(&twice_as_light) < 0.0001);
}
}