use crate::{math::approx_powf, pipeline};
use tiny_skia_path::{NormalizedF32, Scalar};
#[cfg(all(not(feature = "std"), feature = "no-std-float"))]
use tiny_skia_path::NoStdFloat;
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([u8; 4]);
impl ColorU8 {
pub const fn from_rgba(r: u8, g: u8, b: u8, a: u8) -> Self {
ColorU8([r, g, b, a])
}
pub const fn red(self) -> u8 {
self.0[0]
}
pub const fn green(self) -> u8 {
self.0[1]
}
pub const fn blue(self) -> u8 {
self.0[2]
}
pub const fn alpha(self) -> u8 {
self.0[3]
}
pub fn is_opaque(&self) -> bool {
self.alpha() == ALPHA_U8_OPAQUE
}
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 core::fmt::Debug for ColorU8 {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::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([u8; 4]);
unsafe impl bytemuck::Zeroable for PremultipliedColorU8 {}
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([r, g, b, a]))
} else {
None
}
}
pub(crate) const fn from_rgba_unchecked(r: u8, g: u8, b: u8, a: u8) -> Self {
PremultipliedColorU8([r, g, b, a])
}
pub const fn red(self) -> u8 {
self.0[0]
}
pub const fn green(self) -> u8 {
self.0[1]
}
pub const fn blue(self) -> u8 {
self.0[2]
}
pub const fn alpha(self) -> u8 {
self.0[3]
}
pub fn is_opaque(&self) -> bool {
self.alpha() == ALPHA_U8_OPAQUE
}
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 core::fmt::Debug for PremultipliedColorU8 {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::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,
};
pub const unsafe fn from_rgba_unchecked(r: f32, g: f32, b: f32, a: f32) -> Self {
Color {
r: NormalizedF32::new_unchecked(r),
g: NormalizedF32::new_unchecked(g),
b: NormalizedF32::new_unchecked(b),
a: NormalizedF32::new_unchecked(a),
}
}
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)?,
})
}
pub fn from_rgba8(r: u8, g: u8, b: u8, a: u8) -> Self {
Color {
r: NormalizedF32::new_u8(r),
g: NormalizedF32::new_u8(g),
b: NormalizedF32::new_u8(b),
a: NormalizedF32::new_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_clamped(c);
}
pub fn set_green(&mut self, c: f32) {
self.g = NormalizedF32::new_clamped(c);
}
pub fn set_blue(&mut self, c: f32) {
self.b = NormalizedF32::new_clamped(c);
}
pub fn set_alpha(&mut self, c: f32) {
self.a = NormalizedF32::new_clamped(c);
}
pub fn apply_opacity(&mut self, opacity: f32) {
self.a = NormalizedF32::new_clamped(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_clamped(self.r.get() * self.a.get()),
g: NormalizedF32::new_clamped(self.g.get() * self.a.get()),
b: NormalizedF32::new_clamped(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_clamped(self.r.get() / a),
g: NormalizedF32::new_clamped(self.g.get() / a),
b: NormalizedF32::new_clamped(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
}
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,
]
}
#[derive(Copy, Clone, Default, Eq, PartialEq, Ord, PartialOrd, Debug)]
pub enum ColorSpace {
#[default]
Linear,
Gamma2,
SimpleSRGB,
FullSRGBGamma,
}
impl ColorSpace {
pub(crate) fn expand_channel(self, x: NormalizedF32) -> NormalizedF32 {
match self {
ColorSpace::Linear => x,
ColorSpace::Gamma2 => x * x,
ColorSpace::SimpleSRGB => NormalizedF32::new_clamped(approx_powf(x.get(), 2.2)),
ColorSpace::FullSRGBGamma => {
let x = x.get();
let x = if x <= 0.04045 {
x / 12.92
} else {
approx_powf((x + 0.055) / 1.055, 2.4)
};
NormalizedF32::new_clamped(x)
}
}
}
pub(crate) fn expand_color(self, mut color: Color) -> Color {
color.r = self.expand_channel(color.r);
color.g = self.expand_channel(color.g);
color.b = self.expand_channel(color.b);
color
}
#[allow(unused)]
pub(crate) fn compress_channel(self, x: NormalizedF32) -> NormalizedF32 {
match self {
ColorSpace::Linear => x,
ColorSpace::Gamma2 => NormalizedF32::new_clamped(x.get().sqrt()),
ColorSpace::SimpleSRGB => NormalizedF32::new_clamped(approx_powf(x.get(), 0.45454545)),
ColorSpace::FullSRGBGamma => {
let x = x.get();
let x = if x <= 0.0031308 {
x * 12.92
} else {
approx_powf(x, 0.416666666) * 1.055 - 0.055
};
NormalizedF32::new_clamped(x)
}
}
}
pub(crate) fn expand_stage(self) -> Option<pipeline::Stage> {
match self {
ColorSpace::Linear => None,
ColorSpace::Gamma2 => Some(pipeline::Stage::GammaExpand2),
ColorSpace::SimpleSRGB => Some(pipeline::Stage::GammaExpand22),
ColorSpace::FullSRGBGamma => Some(pipeline::Stage::GammaExpandSrgb),
}
}
pub(crate) fn expand_dest_stage(self) -> Option<pipeline::Stage> {
match self {
ColorSpace::Linear => None,
ColorSpace::Gamma2 => Some(pipeline::Stage::GammaExpandDestination2),
ColorSpace::SimpleSRGB => Some(pipeline::Stage::GammaExpandDestination22),
ColorSpace::FullSRGBGamma => Some(pipeline::Stage::GammaExpandDestinationSrgb),
}
}
pub(crate) fn compress_stage(self) -> Option<pipeline::Stage> {
match self {
ColorSpace::Linear => None,
ColorSpace::Gamma2 => Some(pipeline::Stage::GammaCompress2),
ColorSpace::SimpleSRGB => Some(pipeline::Stage::GammaCompress22),
ColorSpace::FullSRGBGamma => Some(pipeline::Stage::GammaCompressSrgb),
}
}
}
#[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)
);
}
#[test]
fn bytemuck_casts_rgba() {
let slice = &[
PremultipliedColorU8::from_rgba_unchecked(0, 1, 2, 3),
PremultipliedColorU8::from_rgba_unchecked(10, 11, 12, 13),
];
let bytes: &[u8] = bytemuck::cast_slice(slice);
assert_eq!(bytes, &[0, 1, 2, 3, 10, 11, 12, 13]);
}
}