Implement conversion between sRGB and linear RGB
This commit is contained in:
1 parent
74acc5ae7c
commit
93a410bafd
3 files changed
+499
-24
No files matched your search
+98
-24
@@ -1,3 +1,5 @@
|
||||
use super::*;
|
||||
|
||||
use ccvl_proc_macros::Pixel;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Pixel)]
|
||||
@@ -15,6 +17,29 @@ pub struct SRGB {
|
||||
pub b: f32,
|
||||
}
|
||||
|
||||
const CUTOFF: f32 = 4.05e-2;
|
||||
const LINEAR_DIVISOR: f32 = 12.92;
|
||||
const SHIFT: f32 = 5.5e-2;
|
||||
const DIVISOR: f32 = 1.055;
|
||||
const POWER: f32 = 2.4;
|
||||
const INVERSE_CUTOFF: f32 = 3.1308e-3;
|
||||
|
||||
pub fn to_linear(c: f32) -> f32 {
|
||||
if c <= CUTOFF {
|
||||
c / LINEAR_DIVISOR
|
||||
} else {
|
||||
((c + SHIFT) / DIVISOR).powf(POWER)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_linear(c: f32) -> f32 {
|
||||
if c <= INVERSE_CUTOFF {
|
||||
c * LINEAR_DIVISOR
|
||||
} else {
|
||||
c.powf(1.0 / POWER) * DIVISOR - SHIFT
|
||||
}
|
||||
}
|
||||
|
||||
impl SRGBA {
|
||||
pub fn new(r: f32, g: f32, b: f32, a: f32) -> Self {
|
||||
Self { r, g, b, a }
|
||||
@@ -22,9 +47,9 @@ impl SRGBA {
|
||||
}
|
||||
|
||||
impl Into<SRGB> for SRGBA {
|
||||
fn into(self) -> SRGB {
|
||||
SRGB::new(self.r, self.g, self.b)
|
||||
}
|
||||
fn into(self) -> SRGB {
|
||||
SRGB::new(self.r, self.g, self.b)
|
||||
}
|
||||
}
|
||||
|
||||
impl SRGB {
|
||||
@@ -34,31 +59,80 @@ impl SRGB {
|
||||
}
|
||||
|
||||
impl Into<SRGBA> for SRGB {
|
||||
fn into(self) -> SRGBA {
|
||||
SRGBA::new(self.r, self.g, self.b, 1.0)
|
||||
}
|
||||
fn into(self) -> SRGBA {
|
||||
SRGBA::new(self.r, self.g, self.b, 1.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<LinearRGB> for SRGB {
|
||||
fn into(self) -> LinearRGB {
|
||||
LinearRGB::new(
|
||||
to_linear(self.r),
|
||||
to_linear(self.g),
|
||||
to_linear(self.b),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LinearRGB> for SRGB {
|
||||
fn from(linear: LinearRGB) -> Self {
|
||||
Self::new(
|
||||
from_linear(linear.r),
|
||||
from_linear(linear.g),
|
||||
from_linear(linear.b),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use super::*;
|
||||
use proptest::prelude::*;
|
||||
|
||||
#[test]
|
||||
fn srgb_from_srgba_drops_alpha()
|
||||
{
|
||||
let srgb: SRGB = SRGBA::new(0.1, 0.2, 0.3, 0.4).into();
|
||||
assert_eq!(srgb.r, 0.1);
|
||||
assert_eq!(srgb.g, 0.2);
|
||||
assert_eq!(srgb.b, 0.3);
|
||||
}
|
||||
macro_rules! prop_assert_approx {
|
||||
( $act:expr, $exp:expr ) => {
|
||||
prop_assert!(($act - $exp).abs() < 1e-6)
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn srgba_from_srgb_adds_alpha_1()
|
||||
{
|
||||
let srgba: SRGBA = SRGB::new(0.1, 0.2, 0.3).into();
|
||||
assert_eq!(srgba.r, 0.1);
|
||||
assert_eq!(srgba.g, 0.2);
|
||||
assert_eq!(srgba.b, 0.3);
|
||||
assert_eq!(srgba.a, 1.0);
|
||||
}
|
||||
proptest! {
|
||||
#[test]
|
||||
fn srgb_from_srgba_drops_alpha(
|
||||
r in 0.0f32..1.0,
|
||||
g in 0.0f32..1.0,
|
||||
b in 0.0f32..1.0,
|
||||
a in 0.0f32..1.0,
|
||||
) {
|
||||
let res: SRGB = SRGBA::new(r, g, b, a).into();
|
||||
prop_assert_eq!(res.r, r);
|
||||
prop_assert_eq!(res.g, g);
|
||||
prop_assert_eq!(res.b, b);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn srgba_from_srgb_adds_alpha_1(
|
||||
r in 0.0f32..1.0,
|
||||
g in 0.0f32..1.0,
|
||||
b in 0.0f32..1.0,
|
||||
) {
|
||||
let res: SRGBA = SRGB::new(r, g, b).into();
|
||||
prop_assert_eq!(res.r, r);
|
||||
prop_assert_eq!(res.g, g);
|
||||
prop_assert_eq!(res.b, b);
|
||||
prop_assert_eq!(res.a, 1.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn approx_unchanged_from_linear_round_trip(
|
||||
r in 0.0f32..1.0,
|
||||
g in 0.0f32..1.0,
|
||||
b in 0.0f32..1.0,
|
||||
) {
|
||||
let lin: LinearRGB = SRGB::new(r, g, b).into();
|
||||
let res: SRGB = lin.into();
|
||||
prop_assert_approx!(res.r, r);
|
||||
prop_assert_approx!(res.g, g);
|
||||
prop_assert_approx!(res.b, b);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user