use crate::floating_point::NormalizedF32;
use crate::scalar::Scalar;
pub type AlphaU8 = u8;
pub const ALPHA_U8_TRANSPARENT: AlphaU8 = 0x00;
pub const ALPHA_U8_OPAQUE: AlphaU8 = 0xFF;
pub const ALPHA_TRANSPARENT: NormalizedF32 = NormalizedF32::ZERO;
pub const ALPHA_OPAQUE: NormalizedF32 = NormalizedF32::ONE;
#[repr(transparent)]
#[derive(Copy, Clone, PartialEq)]
pub struct ColorU8(u32);
impl ColorU8 {
pub const fn from_rgba(r: u8, g: u8, b: u8, a: u8) -> Self {
ColorU8(pack_rgba(r, g, b, a))
}
pub const fn red(self) -> u8 {
((self.0 >> 0) & 0xFF) as u8
}
pub const fn green(self) -> u8 {
((self.0 >> 8) & 0xFF) as u8
}
pub const fn blue(self) -> u8 {
((self.0 >> 16) & 0xFF) as u8
}
pub const fn alpha(self) -> u8 {
((self.0 >> 24) & 0xFF) as u8
}
pub fn is_opaque(&self) -> bool {
self.alpha() == ALPHA_U8_OPAQUE
}
pub const fn get(self) -> u32 {
self.0
}
pub fn premultiply(&self) -> PremultipliedColorU8 {
let a = self.alpha();
if a != ALPHA_U8_OPAQUE {
PremultipliedColorU8::from_rgba_unchecked(
premultiply_u8(self.red(), a),
premultiply_u8(self.green(), a),
premultiply_u8(self.blue(), a),
a,
)
} else {
PremultipliedColorU8::from_rgba_unchecked(self.red(), self.green(), self.blue(), a)
}
}
}
impl std::fmt::Debug for ColorU8 {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ColorU8")
.field("r", &self.red())
.field("g", &self.green())
.field("b", &self.blue())
.field("a", &self.alpha())
.finish()
}
}
#[repr(transparent)]
#[derive(Copy, Clone, PartialEq)]
pub struct PremultipliedColorU8(u32);
#[allow(unsafe_code)] unsafe impl bytemuck::Zeroable for PremultipliedColorU8 {}
#[allow(unsafe_code)] unsafe impl bytemuck::Pod for PremultipliedColorU8 {}
impl PremultipliedColorU8 {
pub const TRANSPARENT: Self = PremultipliedColorU8::from_rgba_unchecked(0, 0, 0, 0);
pub fn from_rgba(r: u8, g: u8, b: u8, a: u8) -> Option<Self> {
if r <= a && g <= a && b <= a {
Some(PremultipliedColorU8(pack_rgba(r, g, b, a)))
} else {
None
}
}
pub(crate) const fn from_rgba_unchecked(r: u8, g: u8, b: u8, a: u8) -> Self {
PremultipliedColorU8(pack_rgba(r, g, b, a))
}
pub const fn red(self) -> u8 {
((self.0 >> 0) & 0xFF) as u8
}
pub const fn green(self) -> u8 {
((self.0 >> 8) & 0xFF) as u8
}
pub const fn blue(self) -> u8 {
((self.0 >> 16) & 0xFF) as u8
}
pub const fn alpha(self) -> u8 {
((self.0 >> 24) & 0xFF) as u8
}
pub fn is_opaque(&self) -> bool {
self.alpha() == ALPHA_U8_OPAQUE
}
pub const fn get(self) -> u32 {
self.0
}
pub fn demultiply(&self) -> ColorU8 {
let alpha = self.alpha();
if alpha == ALPHA_U8_OPAQUE {
ColorU8(self.0)
} else {
let a = alpha as f64 / 255.0;
ColorU8::from_rgba(
(self.red() as f64 / a + 0.5) as u8,
(self.green() as f64 / a + 0.5) as u8,
(self.blue() as f64 / a + 0.5) as u8,
alpha,
)
}
}
}
impl std::fmt::Debug for PremultipliedColorU8 {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("PremultipliedColorU8")
.field("r", &self.red())
.field("g", &self.green())
.field("b", &self.blue())
.field("a", &self.alpha())
.finish()
}
}
#[derive(Copy, Clone, PartialEq, Debug)]
pub struct Color {
r: NormalizedF32,
g: NormalizedF32,
b: NormalizedF32,
a: NormalizedF32,
}
const NV_ZERO: NormalizedF32 = NormalizedF32::ZERO;
const NV_ONE: NormalizedF32 = NormalizedF32::ONE;
impl Color {
pub const TRANSPARENT: Color = Color { r: NV_ZERO, g: NV_ZERO, b: NV_ZERO, a: NV_ZERO };
pub const BLACK: Color = Color { r: NV_ZERO, g: NV_ZERO, b: NV_ZERO, a: NV_ONE };
pub const WHITE: Color = Color { r: NV_ONE, g: NV_ONE, b: NV_ONE, a: NV_ONE };
#[inline]
pub fn from_rgba(r: f32, g: f32, b: f32, a: f32) -> Option<Self> {
Some(Color {
r: NormalizedF32::new(r)?,
g: NormalizedF32::new(g)?,
b: NormalizedF32::new(b)?,
a: NormalizedF32::new(a)?,
})
}
#[inline]
pub fn from_rgba8(r: u8, g: u8, b: u8, a: u8) -> Self {
Color {
r: NormalizedF32::from_u8(r),
g: NormalizedF32::from_u8(g),
b: NormalizedF32::from_u8(b),
a: NormalizedF32::from_u8(a),
}
}
pub fn red(&self) -> f32 {
self.r.get()
}
pub fn green(&self) -> f32 {
self.g.get()
}
pub fn blue(&self) -> f32 {
self.b.get()
}
pub fn alpha(&self) -> f32 {
self.a.get()
}
pub fn set_red(&mut self, c: f32) {
self.r = NormalizedF32::new_bounded(c);
}
pub fn set_green(&mut self, c: f32) {
self.g = NormalizedF32::new_bounded(c);
}
pub fn set_blue(&mut self, c: f32) {
self.b = NormalizedF32::new_bounded(c);
}
pub fn set_alpha(&mut self, c: f32) {
self.a = NormalizedF32::new_bounded(c);
}
pub fn apply_opacity(&mut self, opacity: f32) {
self.a = NormalizedF32::new_bounded(self.a.get() * opacity.bound(0.0, 1.0));
}
pub fn is_opaque(&self) -> bool {
self.a == ALPHA_OPAQUE
}
pub fn premultiply(&self) -> PremultipliedColor {
if self.is_opaque() {
PremultipliedColor {
r: self.r,
g: self.g,
b: self.b,
a: self.a,
}
} else {
PremultipliedColor {
r: NormalizedF32::new_bounded(self.r.get() * self.a.get()),
g: NormalizedF32::new_bounded(self.g.get() * self.a.get()),
b: NormalizedF32::new_bounded(self.b.get() * self.a.get()),
a: self.a,
}
}
}
pub fn to_color_u8(&self) -> ColorU8 {
let c = color_f32_to_u8(self.r, self.g, self.b, self.a);
ColorU8::from_rgba(c[0], c[1], c[2], c[3])
}
}
#[derive(Copy, Clone, PartialEq, Debug)]
pub struct PremultipliedColor {
r: NormalizedF32,
g: NormalizedF32,
b: NormalizedF32,
a: NormalizedF32,
}
impl PremultipliedColor {
pub fn red(&self) -> f32 {
self.r.get()
}
pub fn green(&self) -> f32 {
self.g.get()
}
pub fn blue(&self) -> f32 {
self.b.get()
}
pub fn alpha(&self) -> f32 {
self.a.get()
}
pub fn demultiply(&self) -> Color {
let a = self.a.get();
if a == 0.0 {
Color::TRANSPARENT
} else {
Color {
r: NormalizedF32::new_bounded(self.r.get() / a),
g: NormalizedF32::new_bounded(self.g.get() / a),
b: NormalizedF32::new_bounded(self.b.get() / a),
a: self.a,
}
}
}
pub fn to_color_u8(&self) -> PremultipliedColorU8 {
let c = color_f32_to_u8(self.r, self.g, self.b, self.a);
PremultipliedColorU8::from_rgba_unchecked(c[0], c[1], c[2], c[3])
}
}
pub fn premultiply_u8(c: u8, a: u8) -> u8 {
let prod = u32::from(c) * u32::from(a) + 128;
((prod + (prod >> 8)) >> 8) as u8
}
const fn pack_rgba(r: u8, g: u8, b: u8, a: u8) -> u32 {
((a as u32) << 24) | ((b as u32) << 16) | ((g as u32) << 8) | ((r as u32) << 0)
}
fn color_f32_to_u8(
r: NormalizedF32,
g: NormalizedF32,
b: NormalizedF32,
a: NormalizedF32,
) -> [u8; 4] {
[
(r.get() * 255.0 + 0.5) as u8,
(g.get() * 255.0 + 0.5) as u8,
(b.get() * 255.0 + 0.5) as u8,
(a.get() * 255.0 + 0.5) as u8,
]
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn premultiply_u8() {
assert_eq!(
ColorU8::from_rgba(10, 20, 30, 40).premultiply(),
PremultipliedColorU8::from_rgba_unchecked(2, 3, 5, 40)
);
}
#[test]
fn premultiply_u8_opaque() {
assert_eq!(
ColorU8::from_rgba(10, 20, 30, 255).premultiply(),
PremultipliedColorU8::from_rgba_unchecked(10, 20, 30, 255)
);
}
#[test]
fn demultiply_u8_1() {
assert_eq!(
PremultipliedColorU8::from_rgba_unchecked(2, 3, 5, 40).demultiply(),
ColorU8::from_rgba(13, 19, 32, 40)
);
}
#[test]
fn demultiply_u8_2() {
assert_eq!(
PremultipliedColorU8::from_rgba_unchecked(10, 20, 30, 255).demultiply(),
ColorU8::from_rgba(10, 20, 30, 255)
);
}
#[test]
fn demultiply_u8_3() {
assert_eq!(
PremultipliedColorU8::from_rgba_unchecked(153, 99, 54, 180).demultiply(),
ColorU8::from_rgba(217, 140, 77, 180)
);
}
}