#![deny(missing_docs, rustdoc::broken_intra_doc_links)]
#![no_std]
#[cfg(feature = "std")]
extern crate std;
use core::fmt;
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default, Hash)]
#[repr(C)]
pub struct EncodedColor {
pub r: u8,
pub g: u8,
pub b: u8,
pub a: u8,
}
impl EncodedColor {
pub const WHITE: EncodedColor = EncodedColor::new(255, 255, 255, 255);
pub const BLACK: EncodedColor = EncodedColor::new(0, 0, 0, 255);
pub const CLEAR: EncodedColor = EncodedColor::new(0, 0, 0, 0);
pub const FUCHSIA: EncodedColor = EncodedColor::new(255, 0, 255, 255);
pub const fn new(r: u8, g: u8, b: u8, a: u8) -> Self {
Self { r, g, b, a }
}
#[inline]
pub fn to_linear(self) -> LinearColor {
LinearColor {
r: encoded_to_linear(self.r),
g: encoded_to_linear(self.g),
b: encoded_to_linear(self.b),
a: self.a as f32 / 255.0,
}
}
#[inline]
pub fn to_encoded_f32s(self) -> [f32; 4] {
[
self.r as f32 / 255.0,
self.g as f32 / 255.0,
self.b as f32 / 255.0,
self.a as f32 / 255.0,
]
}
#[inline]
pub fn from_encoded_f32s(input: [f32; 4]) -> Self {
Self::new(
(input[0] * 255.0) as u8,
(input[1] * 255.0) as u8,
(input[2] * 255.0) as u8,
(input[3] * 255.0) as u8,
)
}
#[inline]
pub const fn from_rgba_u32(input: u32) -> Self {
let bytes = input.to_ne_bytes();
Self {
r: bytes[3],
g: bytes[2],
b: bytes[1],
a: bytes[0],
}
}
#[inline]
pub const fn to_rgba_u32(self) -> u32 {
let mut bytes = [0, 0, 0, 0];
bytes[3] = self.r;
bytes[2] = self.g;
bytes[1] = self.b;
bytes[0] = self.a;
u32::from_ne_bytes(bytes)
}
#[inline]
pub const fn from_bgra_u32(input: u32) -> Self {
let bytes = input.to_ne_bytes();
Self {
r: bytes[1],
g: bytes[2],
b: bytes[3],
a: bytes[0],
}
}
#[inline]
pub const fn to_bgra_u32(self) -> u32 {
let mut bytes = [0, 0, 0, 0];
bytes[1] = self.r;
bytes[2] = self.g;
bytes[3] = self.b;
bytes[0] = self.a;
u32::from_ne_bytes(bytes)
}
pub const fn from_bits_u32(value: u32) -> Self {
unsafe { core::mem::transmute(value) }
}
pub const fn from_bits(value: [u8; 4]) -> Self {
unsafe { core::mem::transmute(value) }
}
}
impl From<(u8, u8, u8, u8)> for EncodedColor {
fn from(o: (u8, u8, u8, u8)) -> Self {
Self {
r: o.0,
g: o.1,
b: o.2,
a: o.3,
}
}
}
impl From<EncodedColor> for (u8, u8, u8, u8) {
fn from(o: EncodedColor) -> Self {
(o.r, o.g, o.b, o.a)
}
}
impl fmt::Debug for EncodedColor {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("EncodedColor")
.field(&self.r)
.field(&self.g)
.field(&self.b)
.field(&self.a)
.finish()
}
}
impl fmt::Display for EncodedColor {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"r: {}, g: {}, b: {}, a: {}, {:x}{:x}{:x}{:x}",
self.r, self.g, self.b, self.a, self.r, self.g, self.b, self.a
)
}
}
impl fmt::LowerHex for EncodedColor {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let val = self.to_rgba_u32();
fmt::LowerHex::fmt(&val, f)
}
}
impl fmt::UpperHex for EncodedColor {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let val = self.to_rgba_u32();
fmt::UpperHex::fmt(&val, f)
}
}
#[derive(Clone, Copy, PartialEq, PartialOrd, Default)]
pub struct LinearColor {
pub r: f32,
pub g: f32,
pub b: f32,
pub a: f32,
}
impl LinearColor {
#[inline]
pub const fn new(r: f32, g: f32, b: f32, a: f32) -> Self {
Self { r, g, b, a }
}
#[inline]
pub fn to_encoded_space(self) -> EncodedColor {
EncodedColor {
r: linear_to_encoded(self.r),
g: linear_to_encoded(self.g),
b: linear_to_encoded(self.b),
a: (self.a * 255.0) as u8,
}
}
#[inline]
pub fn to_array(self) -> [f32; 4] {
self.into()
}
#[inline]
pub fn to_bits(self) -> [u8; 16] {
unsafe { core::mem::transmute(self.to_array()) }
}
pub fn from_bits(value: [u8; 16]) -> Self {
unsafe { core::mem::transmute(value) }
}
}
impl From<LinearColor> for [f32; 4] {
fn from(o: LinearColor) -> Self {
[o.r, o.g, o.b, o.a]
}
}
impl From<[f32; 4]> for LinearColor {
fn from(o: [f32; 4]) -> Self {
Self::new(o[0], o[1], o[2], o[3])
}
}
impl From<LinearColor> for (f32, f32, f32, f32) {
fn from(o: LinearColor) -> Self {
(o.r, o.g, o.b, o.a)
}
}
impl From<(f32, f32, f32, f32)> for LinearColor {
fn from(o: (f32, f32, f32, f32)) -> Self {
Self::new(o.0, o.1, o.2, o.3)
}
}
impl fmt::Debug for LinearColor {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("LinearColor")
.field(&self.r)
.field(&self.g)
.field(&self.b)
.field(&self.a)
.finish()
}
}
impl fmt::Display for LinearColor {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"r: {}, g: {}, b: {}, a: {}",
self.r, self.g, self.b, self.a
)
}
}
impl From<LinearColor> for EncodedColor {
fn from(o: LinearColor) -> Self {
o.to_encoded_space()
}
}
impl From<EncodedColor> for LinearColor {
fn from(o: EncodedColor) -> Self {
o.to_linear()
}
}
pub const fn encoded_to_linear(c: u8) -> f32 {
ENCODED_TO_LINEAR_LUT[c as usize]
}
#[rustfmt::skip]
pub const ENCODED_TO_LINEAR_LUT: [f32; 256] = [
0.0, 0.000303527, 0.000607054, 0.000910581, 0.001214108, 0.001517635, 0.001821162, 0.0021246888,
0.002428216, 0.0027317428, 0.00303527, 0.0033465358, 0.0036765074, 0.004024717, 0.004391442,
0.0047769533, 0.0051815165, 0.0056053917, 0.006048833, 0.0065120906, 0.00699541, 0.007499032,
0.008023193, 0.008568126, 0.009134059, 0.009721218, 0.010329823, 0.010960094, 0.011612245,
0.012286488, 0.0129830325, 0.013702083, 0.014443844, 0.015208514, 0.015996294, 0.016807375,
0.017641954, 0.01850022, 0.019382361, 0.020288562, 0.02121901, 0.022173885, 0.023153367,
0.024157632, 0.02518686, 0.026241222, 0.027320892, 0.02842604, 0.029556835, 0.030713445,
0.031896032, 0.033104766, 0.034339808, 0.035601314, 0.03688945, 0.038204372, 0.039546236,
0.0409152, 0.04231141, 0.04373503, 0.045186203, 0.046665087, 0.048171826, 0.049706567,
0.051269457, 0.052860647, 0.054480277, 0.05612849, 0.05780543, 0.059511237, 0.061246052,
0.063010015, 0.064803265, 0.06662594, 0.06847817, 0.070360094, 0.07227185, 0.07421357,
0.07618538, 0.07818742, 0.08021982, 0.08228271, 0.08437621, 0.08650046, 0.08865558, 0.09084171,
0.093058966, 0.09530747, 0.09758735, 0.099898726, 0.10224173, 0.104616486, 0.107023105, 0.10946171,
0.11193243, 0.114435375, 0.116970666, 0.11953843, 0.122138776, 0.12477182, 0.12743768, 0.13013647,
0.13286832, 0.13563333, 0.13843161, 0.14126329, 0.14412847, 0.14702727, 0.14995979, 0.15292615,
0.15592647, 0.15896083, 0.16202937, 0.1651322, 0.1682694, 0.17144111, 0.1746474, 0.17788842, 0.18116425,
0.18447499, 0.18782078, 0.19120169, 0.19461784, 0.19806932, 0.20155625, 0.20507874, 0.20863687,
0.21223076, 0.2158605, 0.2195262, 0.22322796, 0.22696587, 0.23074006, 0.23455058, 0.23839757, 0.24228112,
0.24620132, 0.25015828, 0.2541521, 0.25818285, 0.26225066, 0.2663556, 0.2704978, 0.2746773, 0.27889428,
0.28314874, 0.28744084, 0.29177064, 0.29613826, 0.30054379, 0.3049873, 0.30946892, 0.31398872, 0.31854677,
0.3231432, 0.3277781, 0.33245152, 0.33716363, 0.34191442, 0.34670407, 0.3515326, 0.35640013, 0.3613068,
0.3662526, 0.3712377, 0.37626213, 0.38132602, 0.38642943, 0.39157248, 0.39675522, 0.40197778, 0.4072402,
0.4125426, 0.41788507, 0.42326766, 0.4286905, 0.43415365, 0.43965718, 0.4452012, 0.4507858, 0.45641103,
0.462077, 0.4677838, 0.47353148, 0.47932017, 0.48514995, 0.49102086, 0.49693298, 0.5028865, 0.50888133,
0.5149177, 0.52099556, 0.5271151, 0.5332764, 0.5394795, 0.54572445, 0.55201143, 0.5583404, 0.5647115,
0.57112485, 0.57758045, 0.58407843, 0.59061885, 0.59720176, 0.60382736, 0.61049557, 0.6172066, 0.6239604,
0.63075715, 0.63759685, 0.6444797, 0.65140563, 0.65837485, 0.6653873, 0.67244315, 0.6795425, 0.6866853,
0.69387174, 0.7011019, 0.70837575, 0.7156935, 0.7230551, 0.73046076, 0.7379104, 0.7454042, 0.7529422,
0.7605245, 0.76815116, 0.7758222, 0.7835378, 0.7912979, 0.7991027, 0.80695224, 0.8148466, 0.82278574,
0.8307699, 0.838799, 0.8468732, 0.8549926, 0.8631572, 0.8713671, 0.8796224, 0.8879231, 0.8962694, 0.9046612,
0.91309863, 0.92158186, 0.9301109, 0.9386857, 0.9473065, 0.9559733, 0.9646863, 0.9734453, 0.9822506,
0.9911021, 1.0,
];
pub fn linear_to_encoded(input: f32) -> u8 {
#[cfg(feature = "libm")]
use libm::powf;
#[cfg(feature = "std")]
fn powf(f: f32, e: f32) -> f32 {
f.powf(e)
}
let encoded_f32 = if input >= 0.0031308 {
1.055 * powf(input, 1.0 / 2.4) - 0.055
} else {
12.92 * input
};
(encoded_f32 * 256.0) as u8
}
#[cfg(feature = "bytemuck")]
unsafe impl bytemuck::Pod for EncodedColor {}
#[cfg(feature = "bytemuck")]
unsafe impl bytemuck::Zeroable for EncodedColor {}
#[cfg(feature = "bytemuck")]
unsafe impl bytemuck::Pod for LinearColor {}
#[cfg(feature = "bytemuck")]
unsafe impl bytemuck::Zeroable for LinearColor {}
#[cfg(feature = "serde")]
const ENCODED_NAME: &str = "Encoded Rgb";
#[cfg(feature = "serde")]
impl serde::Serialize for EncodedColor {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
use serde::ser::SerializeTupleStruct;
let mut seq = serializer.serialize_tuple_struct(ENCODED_NAME, 4)?;
seq.serialize_field(&self.r)?;
seq.serialize_field(&self.g)?;
seq.serialize_field(&self.b)?;
seq.serialize_field(&self.a)?;
seq.end()
}
}
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for EncodedColor {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
struct DeserializeColor;
impl<'de> serde::de::Visitor<'de> for DeserializeColor {
type Value = EncodedColor;
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("a sequence of u8 colors")
}
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: serde::de::SeqAccess<'de>,
{
let r = seq
.next_element()?
.ok_or_else(|| serde::de::Error::invalid_length(0, &self))?;
let g = seq
.next_element()?
.ok_or_else(|| serde::de::Error::invalid_length(1, &self))?;
let b = seq
.next_element()?
.ok_or_else(|| serde::de::Error::invalid_length(2, &self))?;
let a = seq
.next_element()?
.ok_or_else(|| serde::de::Error::invalid_length(3, &self))?;
Ok(EncodedColor { r, g, b, a })
}
}
deserializer.deserialize_tuple_struct(ENCODED_NAME, 4, DeserializeColor)
}
}
#[cfg(feature = "rand")]
impl rand::distributions::Distribution<EncodedColor> for rand::distributions::Standard {
fn sample<R: rand::Rng + ?Sized>(&self, rng: &mut R) -> EncodedColor {
EncodedColor {
r: rng.gen(),
g: rng.gen(),
b: rng.gen(),
a: rng.gen(),
}
}
}
#[cfg(feature = "rand")]
impl rand::distributions::Distribution<LinearColor> for rand::distributions::Standard {
fn sample<R: rand::Rng + ?Sized>(&self, rng: &mut R) -> LinearColor {
LinearColor {
r: rng.gen(),
g: rng.gen(),
b: rng.gen(),
a: rng.gen(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
static_assertions::assert_eq_align!(EncodedColor, u8);
static_assertions::assert_eq_size!(EncodedColor, [u8; 4]);
#[test]
fn from_u32s() {
let cornwall_blue_in_rgba: u32 = 0x6b9ebeff;
let cornwall_blue_in_bgra: u32 = 0xbe9e6bff;
let cornwall_encoded = EncodedColor {
r: 107,
g: 158,
b: 190,
a: 255,
};
let encoded_rgba = EncodedColor::from_rgba_u32(cornwall_blue_in_rgba);
assert_eq!(encoded_rgba, cornwall_encoded);
assert_eq!(encoded_rgba.to_rgba_u32(), cornwall_blue_in_rgba);
let encoded_bgra = EncodedColor::from_bgra_u32(cornwall_blue_in_bgra);
assert_eq!(encoded_bgra, cornwall_encoded);
assert_eq!(encoded_bgra.to_bgra_u32(), cornwall_blue_in_bgra);
#[cfg(feature = "std")]
{
let rgba_as_hex = std::format!("{:x}", encoded_rgba);
assert_eq!(rgba_as_hex, "6b9ebeff");
let rgba_as_hex = std::format!("{:#X}", encoded_rgba);
assert_eq!(rgba_as_hex, "0x6B9EBEFF");
}
}
#[test]
fn encoding_decoding() {
fn encode(input: u8, output: f32) {
let o = encoded_to_linear(input);
assert!((o - output).abs() < f32::EPSILON);
}
fn decode(input: f32, output: u8) {
let o = linear_to_encoded(input);
assert_eq!(o, output);
}
encode(66, 0.05448028);
encode(0, 0.0);
encode(255, 1.0);
encode(240, 0.8713671);
encode(100, 0.1274377);
encode(128, 0.2158605);
decode(0.05448028, 66);
decode(0.0, 0);
decode(1.0, 255);
decode(0.1274377, 100);
decode(0.8713672, 240);
decode(0.2158605, 128);
}
#[test]
#[allow(clippy::float_cmp)]
#[cfg(feature = "std")]
fn test_lut() {
fn srgb_to_linear_high_precision(component: u8) -> f32 {
let c = component as f64 / 255.0;
(if c > 0.04045 {
((c + 0.055) / 1.055).powf(2.4)
} else {
c / 12.92
}) as f32
}
let expect = (0..=255u8)
.map(srgb_to_linear_high_precision)
.collect::<std::vec::Vec<_>>();
assert_eq!(expect, ENCODED_TO_LINEAR_LUT);
for c in 0..=255u8 {
assert_eq!(encoded_to_linear(c), expect[c as usize]);
}
}
#[test]
#[cfg(feature = "serde")]
fn serde() {
let color = EncodedColor::new(50, 50, 50, 255);
let serialized = serde_json::to_string(&color).unwrap();
assert_eq!("[50,50,50,255]", serialized);
let deserialized = serde_json::from_str(&serialized).unwrap();
assert_eq!(color, deserialized);
let serialized = serde_yaml::to_string(&color).unwrap();
assert_eq!("---\n- 50\n- 50\n- 50\n- 255\n", serialized);
let deserialized = serde_yaml::from_str(&serialized).unwrap();
assert_eq!(color, deserialized);
let start = "---\n- 22\n- 33\n- 100\n- 210";
let color: EncodedColor = serde_yaml::from_str(start).unwrap();
let base = EncodedColor::new(22, 33, 100, 210);
assert_eq!(color, base);
let o = serde_yaml::from_str::<EncodedColor>("[0.2, 50, 50, 255]");
assert!(o.is_err());
let o = serde_yaml::from_str::<EncodedColor>("[20, 50, 50, 256]");
assert!(o.is_err());
let o = serde_yaml::from_str::<EncodedColor>("[20, 50, 245]");
assert!(o.is_err());
let o = serde_yaml::from_str::<EncodedColor>("[-20, 20, 50, 255]");
assert!(o.is_err());
let o = serde_yaml::from_str::<EncodedColor>("[20, 20, 50, 255, 255]");
assert!(o.is_err());
let color = EncodedColor::new(44, 232, 8, 255);
let buff = bincode::serialize(&color).unwrap();
assert_eq!(buff, [44, 232, 8, 255]);
let color = EncodedColor::new(200, 21, 22, 203);
let buff = bincode::serialize(&color).unwrap();
assert_eq!(buff, [200, 21, 22, 203]);
let round_trip_color: EncodedColor = bincode::deserialize(&buff).unwrap();
assert_eq!(color, round_trip_color);
let buf = [14u8, 12, 3];
let o = bincode::deserialize::<EncodedColor>(bytemuck::cast_slice(&buf));
assert!(o.is_err());
use bincode::Options;
let deserialize = bincode::DefaultOptions::new();
let buf = [14, 12];
let o = deserialize.deserialize::<EncodedColor>(bytemuck::cast_slice(&buf));
assert!(o.is_err());
let buf = [14u64];
let o = deserialize.deserialize::<EncodedColor>(bytemuck::cast_slice(&buf));
assert!(o.is_err());
let buf = [31.0f32];
let o = deserialize.deserialize::<EncodedColor>(bytemuck::cast_slice(&buf));
assert!(o.is_ok());
}
}