217 lines
5.3 KiB
Rust
217 lines
5.3 KiB
Rust
use crate::math::clamp;
|
|
|
|
/// 0-255 gamma space `sRGBA` color with premultiplied alpha.
|
|
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
|
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
|
pub struct Srgba {
|
|
pub r: u8,
|
|
pub g: u8,
|
|
pub b: u8,
|
|
/// Alpha is in linear space (not subject to sRGBA gamma conversion)
|
|
pub a: u8,
|
|
}
|
|
/// 0-1 linear space `RGBA` color with premultiplied alpha.
|
|
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
|
pub struct Rgba {
|
|
pub r: f32,
|
|
pub g: f32,
|
|
pub b: f32,
|
|
pub a: f32,
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// Color conversion:
|
|
|
|
impl From<Srgba> for Rgba {
|
|
fn from(srgba: Srgba) -> Rgba {
|
|
Rgba {
|
|
r: linear_from_srgb_byte(srgba.r),
|
|
g: linear_from_srgb_byte(srgba.g),
|
|
b: linear_from_srgb_byte(srgba.b),
|
|
a: srgba.a as f32 / 255.0,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<Rgba> for Srgba {
|
|
fn from(rgba: Rgba) -> Srgba {
|
|
Srgba {
|
|
r: srgb_byte_from_linear(rgba.r),
|
|
g: srgb_byte_from_linear(rgba.g),
|
|
b: srgb_byte_from_linear(rgba.b),
|
|
a: clamp(rgba.a * 255.0, 0.0..=255.0).round() as u8,
|
|
}
|
|
}
|
|
}
|
|
|
|
fn linear_from_srgb_byte(s: u8) -> f32 {
|
|
if s <= 10 {
|
|
s as f32 / 3294.6
|
|
} else {
|
|
((s as f32 + 14.025) / 269.025).powf(2.4)
|
|
}
|
|
}
|
|
|
|
fn srgb_byte_from_linear(l: f32) -> u8 {
|
|
if l <= 0.0 {
|
|
0
|
|
} else if l <= 0.0031308 {
|
|
(3294.6 * l).round() as u8
|
|
} else if l <= 1.0 {
|
|
(269.025 * l.powf(1.0 / 2.4) - 14.025).round() as u8
|
|
} else {
|
|
255
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_srgba_conversion() {
|
|
#![allow(clippy::float_cmp)]
|
|
for b in 0..=255 {
|
|
let l = linear_from_srgb_byte(b);
|
|
assert!(0.0 <= l && l <= 1.0);
|
|
assert_eq!(srgb_byte_from_linear(l), b);
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
pub const fn srgba(r: u8, g: u8, b: u8, a: u8) -> Srgba {
|
|
Srgba { r, g, b, a }
|
|
}
|
|
|
|
impl Srgba {
|
|
pub const fn gray(l: u8) -> Self {
|
|
Self {
|
|
r: l,
|
|
g: l,
|
|
b: l,
|
|
a: 255,
|
|
}
|
|
}
|
|
|
|
pub const fn black_alpha(a: u8) -> Self {
|
|
Self {
|
|
r: 0,
|
|
g: 0,
|
|
b: 0,
|
|
a,
|
|
}
|
|
}
|
|
|
|
pub const fn additive_luminance(l: u8) -> Self {
|
|
Self {
|
|
r: l,
|
|
g: l,
|
|
b: l,
|
|
a: 0,
|
|
}
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
pub const TRANSPARENT: Srgba = srgba(0, 0, 0, 0);
|
|
pub const BLACK: Srgba = srgba(0, 0, 0, 255);
|
|
pub const LIGHT_GRAY: Srgba = srgba(220, 220, 220, 255);
|
|
pub const GRAY: Srgba = srgba(160, 160, 160, 255);
|
|
pub const WHITE: Srgba = srgba(255, 255, 255, 255);
|
|
pub const RED: Srgba = srgba(255, 0, 0, 255);
|
|
pub const GREEN: Srgba = srgba(0, 255, 0, 255);
|
|
pub const BLUE: Srgba = srgba(0, 0, 255, 255);
|
|
pub const YELLOW: Srgba = srgba(255, 255, 0, 255);
|
|
pub const LIGHT_BLUE: Srgba = srgba(140, 160, 255, 255);
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
impl Rgba {
|
|
pub const TRANSPARENT: Rgba = Rgba::new(0.0, 0.0, 0.0, 0.0);
|
|
pub const BLACK: Rgba = Rgba::new(0.0, 0.0, 0.0, 1.0);
|
|
pub const WHITE: Rgba = Rgba::new(1.0, 1.0, 1.0, 1.0);
|
|
pub const RED: Rgba = Rgba::new(1.0, 0.0, 0.0, 1.0);
|
|
pub const GREEN: Rgba = Rgba::new(0.0, 1.0, 0.0, 1.0);
|
|
pub const BLUE: Rgba = Rgba::new(0.0, 0.0, 1.0, 1.0);
|
|
|
|
pub const fn new(r: f32, g: f32, b: f32, a: f32) -> Self {
|
|
Self { r, g, b, a }
|
|
}
|
|
|
|
pub const fn gray(l: f32) -> Self {
|
|
Self {
|
|
r: l,
|
|
g: l,
|
|
b: l,
|
|
a: 1.0,
|
|
}
|
|
}
|
|
|
|
pub fn luminance_alpha(l: f32, a: f32) -> Self {
|
|
debug_assert!(0.0 <= l && l <= 1.0);
|
|
debug_assert!(0.0 <= a && a <= 1.0);
|
|
Self {
|
|
r: l * a,
|
|
g: l * a,
|
|
b: l * a,
|
|
a,
|
|
}
|
|
}
|
|
|
|
/// Transparent white
|
|
pub fn white_alpha(a: f32) -> Self {
|
|
debug_assert!(0.0 <= a && a <= 1.0);
|
|
Self {
|
|
r: a,
|
|
g: a,
|
|
b: a,
|
|
a,
|
|
}
|
|
}
|
|
|
|
/// Multiply with e.g. 0.5 to make us half transparent
|
|
pub fn multiply(self, alpha: f32) -> Self {
|
|
Self {
|
|
r: alpha * self.r,
|
|
g: alpha * self.g,
|
|
b: alpha * self.b,
|
|
a: alpha * self.a,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl std::ops::Add for Rgba {
|
|
type Output = Rgba;
|
|
fn add(self, rhs: Rgba) -> Rgba {
|
|
Rgba {
|
|
r: self.r + rhs.r,
|
|
g: self.g + rhs.g,
|
|
b: self.b + rhs.b,
|
|
a: self.a + rhs.a,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl std::ops::Mul<f32> for Rgba {
|
|
type Output = Rgba;
|
|
fn mul(self, factor: f32) -> Rgba {
|
|
Rgba {
|
|
r: self.r * factor,
|
|
g: self.g * factor,
|
|
b: self.b * factor,
|
|
a: self.a * factor,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl std::ops::Mul<Rgba> for f32 {
|
|
type Output = Rgba;
|
|
fn mul(self, rgba: Rgba) -> Rgba {
|
|
Rgba {
|
|
r: self * rgba.r,
|
|
g: self * rgba.g,
|
|
b: self * rgba.b,
|
|
a: self * rgba.a,
|
|
}
|
|
}
|
|
}
|