use crate::color::Color;
use crate::encoding::{Encoded, Linear};
use crate::fixed::Q0_16;
use crate::interp::InterpLut;
use crate::space::Srgb;
#[derive(Clone, Copy)]
pub struct SrgbOetf {
lut: InterpLut<257>,
}
impl SrgbOetf {
#[must_use]
pub const fn from_knots(knots: [Q0_16; 257], max_err_lsb: u16) -> Self {
Self {
lut: InterpLut::from_knots(knots, max_err_lsb),
}
}
#[must_use]
pub const fn max_err_lsb(&self) -> u16 {
self.lut.max_err_lsb()
}
#[must_use]
pub const fn lookup(&self, x: Q0_16) -> Q0_16 {
self.lut.lookup(x)
}
#[must_use]
pub const fn encode(&self, color: Color<Srgb, Linear>) -> Color<Srgb, Encoded> {
self.lut.encode(color)
}
#[cfg(feature = "f32")]
#[must_use]
pub fn encode_f32(
&self,
color: crate::ColorF32<Srgb, Linear>,
) -> crate::ColorF32<Srgb, Encoded> {
self.lut.encode_f32(color)
}
}
#[derive(Clone, Copy)]
pub struct SrgbEotf {
lut: InterpLut<257>,
}
impl SrgbEotf {
#[must_use]
pub const fn from_knots(knots: [Q0_16; 257], max_err_lsb: u16) -> Self {
Self {
lut: InterpLut::from_knots(knots, max_err_lsb),
}
}
#[must_use]
pub const fn max_err_lsb(&self) -> u16 {
self.lut.max_err_lsb()
}
#[must_use]
pub const fn lookup(&self, x: Q0_16) -> Q0_16 {
self.lut.lookup(x)
}
#[must_use]
pub const fn decode(&self, color: Color<Srgb, Encoded>) -> Color<Srgb, Linear> {
self.lut.decode(color)
}
#[cfg(feature = "f32")]
#[must_use]
pub fn decode_f32(
&self,
color: crate::ColorF32<Srgb, Encoded>,
) -> crate::ColorF32<Srgb, Linear> {
self.lut.decode_f32(color)
}
}
pub const SRGB_OETF_MAX_ERR_LSB: u16 = 115;
pub const SRGB_OETF: SrgbOetf = SrgbOetf::from_knots(
Q0_16::array_from_raw([
0, 3255, 5552, 7237, 8618, 9809, 10867, 11827, 12710, 13531, 14300, 15025, 15713, 16368,
16995, 17595, 18173, 18730, 19269, 19790, 20295, 20786, 21263, 21728, 22181, 22624, 23056,
23478, 23892, 24297, 24694, 25083, 25465, 25840, 26209, 26571, 26927, 27278, 27623, 27963,
28298, 28627, 28953, 29273, 29590, 29902, 30210, 30515, 30815, 31112, 31406, 31696, 31983,
32266, 32547, 32824, 33099, 33370, 33639, 33906, 34169, 34430, 34689, 34945, 35199, 35450,
35699, 35947, 36191, 36434, 36675, 36914, 37151, 37385, 37619, 37850, 38079, 38307, 38533,
38757, 38980, 39201, 39420, 39638, 39854, 40069, 40282, 40494, 40705, 40914, 41122, 41328,
41533, 41737, 41939, 42141, 42341, 42539, 42737, 42934, 43129, 43323, 43516, 43708, 43899,
44089, 44277, 44465, 44652, 44837, 45022, 45206, 45388, 45570, 45751, 45931, 46110, 46288,
46465, 46642, 46817, 46992, 47166, 47339, 47511, 47682, 47853, 48023, 48192, 48360, 48527,
48694, 48860, 49025, 49190, 49354, 49517, 49679, 49841, 50002, 50162, 50322, 50481, 50639,
50797, 50954, 51111, 51266, 51422, 51576, 51730, 51884, 52036, 52189, 52340, 52491, 52642,
52792, 52941, 53090, 53238, 53386, 53533, 53680, 53826, 53972, 54117, 54262, 54406, 54549,
54693, 54835, 54977, 55119, 55260, 55401, 55541, 55681, 55820, 55959, 56098, 56236, 56373,
56510, 56647, 56783, 56919, 57054, 57189, 57324, 57458, 57592, 57725, 57858, 57990, 58122,
58254, 58385, 58516, 58647, 58777, 58907, 59036, 59165, 59294, 59422, 59550, 59678, 59805,
59932, 60058, 60184, 60310, 60435, 60561, 60685, 60810, 60934, 61058, 61181, 61304, 61427,
61549, 61671, 61793, 61915, 62036, 62157, 62277, 62398, 62518, 62637, 62757, 62876, 62994,
63113, 63231, 63349, 63466, 63584, 63701, 63817, 63934, 64050, 64166, 64281, 64397, 64512,
64626, 64741, 64855, 64969, 65083, 65196, 65309, 65422, 65535,
]),
SRGB_OETF_MAX_ERR_LSB,
);
pub const SRGB_EOTF_MAX_ERR_LSB: u16 = 1;
pub const SRGB_EOTF: SrgbEotf = SrgbEotf::from_knots(
Q0_16::array_from_raw([
0, 20, 40, 59, 79, 99, 119, 139, 159, 178, 198, 218, 240, 263, 286, 312, 338, 365, 394,
424, 456, 489, 523, 558, 595, 633, 673, 714, 756, 800, 845, 892, 940, 990, 1041, 1094,
1148, 1204, 1262, 1320, 1381, 1443, 1507, 1572, 1639, 1707, 1778, 1849, 1923, 1998, 2075,
2154, 2234, 2316, 2400, 2485, 2572, 2661, 2752, 2845, 2939, 3035, 3133, 3233, 3334, 3438,
3543, 3650, 3759, 3870, 3982, 4097, 4214, 4332, 4452, 4575, 4699, 4825, 4953, 5083, 5215,
5349, 5485, 5623, 5763, 5906, 6050, 6196, 6344, 6494, 6646, 6800, 6957, 7115, 7276, 7438,
7603, 7770, 7939, 8110, 8283, 8458, 8636, 8816, 8997, 9181, 9367, 9556, 9746, 9939, 10134,
10331, 10530, 10732, 10936, 11142, 11350, 11561, 11773, 11988, 12206, 12425, 12647, 12872,
13098, 13327, 13558, 13791, 14027, 14265, 14506, 14749, 14994, 15241, 15491, 15743, 15998,
16255, 16514, 16776, 17041, 17307, 17576, 17848, 18122, 18398, 18677, 18958, 19242, 19528,
19816, 20108, 20401, 20697, 20996, 21297, 21600, 21906, 22215, 22526, 22840, 23156, 23474,
23796, 24119, 24446, 24775, 25106, 25440, 25777, 26116, 26458, 26802, 27149, 27499, 27851,
28206, 28563, 28923, 29286, 29651, 30019, 30390, 30763, 31139, 31518, 31899, 32283, 32670,
33059, 33451, 33846, 34243, 34644, 35046, 35452, 35860, 36271, 36685, 37102, 37521, 37943,
38368, 38795, 39226, 39659, 40095, 40533, 40975, 41419, 41866, 42316, 42768, 43224, 43682,
44143, 44607, 45073, 45543, 46015, 46491, 46969, 47450, 47934, 48420, 48910, 49402, 49897,
50396, 50897, 51401, 51908, 52417, 52930, 53446, 53964, 54486, 55010, 55537, 56067, 56601,
57137, 57676, 58218, 58763, 59311, 59862, 60415, 60972, 61532, 62095, 62661, 63230, 63801,
64376, 64954, 65535,
]),
SRGB_EOTF_MAX_ERR_LSB,
);
const _: () = {
let tables = core::mem::size_of::<SrgbOetf>().saturating_add(core::mem::size_of::<SrgbEotf>());
assert!(tables <= 2048, "default shipped tables exceed 2 KiB");
};
#[cfg(test)]
mod tests {
use super::*;
use crate::color::Color;
use crate::encoding::{Encoded, Linear};
use crate::space::Srgb;
#[test]
fn default_tables_fit_2kib() {
let n =
core::mem::size_of_val(&SRGB_OETF).saturating_add(core::mem::size_of_val(&SRGB_EOTF));
assert!(n <= 2048, "default shipped tables {n} bytes exceed 2 KiB");
}
#[test]
fn max_err_lsb_is_the_bake_companion() {
assert_eq!(SRGB_OETF.max_err_lsb(), SRGB_OETF_MAX_ERR_LSB);
assert_eq!(SRGB_EOTF.max_err_lsb(), SRGB_EOTF_MAX_ERR_LSB);
}
#[test]
fn endpoints_match_knots() {
assert_eq!(SRGB_OETF.lookup(Q0_16::ZERO), Q0_16::ZERO);
assert_eq!(SRGB_OETF.lookup(Q0_16::ONE), Q0_16::ONE);
assert_eq!(SRGB_EOTF.lookup(Q0_16::ZERO), Q0_16::ZERO);
assert_eq!(SRGB_EOTF.lookup(Q0_16::ONE), Q0_16::ONE);
}
#[test]
fn encode_decode_are_typed_to_srgb() {
let lin = Color::<Srgb, Linear>::new(Q0_16::array_from_raw([0, 32768, 65535]));
let enc = SRGB_OETF.encode(lin);
let back = SRGB_EOTF.decode(enc);
let _: Color<Srgb, Encoded> = enc;
let _: Color<Srgb, Linear> = back;
let [r, _, b] = enc.ch;
assert_eq!(r, Q0_16::ZERO);
assert_eq!(b, Q0_16::ONE);
}
#[test]
fn oetf_is_increasing() {
let mut prev = SRGB_OETF.lookup(Q0_16::ZERO);
for x in 1..=u16::MAX {
let y = SRGB_OETF.lookup(Q0_16::from_raw(x));
assert!(y >= prev, "x={x} y={y:?} prev={prev:?}");
prev = y;
}
}
#[test]
fn knot_boundaries_and_midpoints() {
let last = 256u32;
for i in 0..=last {
let x = match i.saturating_mul(65535).checked_div(last) {
Some(v) if v <= u32::from(u16::MAX) => v as u16,
_ => 0,
};
let o = SRGB_OETF.lookup(Q0_16::from_raw(x));
let e = SRGB_EOTF.lookup(Q0_16::from_raw(x));
if i == 0 {
assert_eq!((o, e), (Q0_16::ZERO, Q0_16::ZERO));
}
if i == last {
assert_eq!((o, e), (Q0_16::ONE, Q0_16::ONE));
}
if i < last {
let next = match (i.saturating_add(1))
.saturating_mul(65535)
.checked_div(last)
{
Some(v) if v <= u32::from(u16::MAX) => v as u16,
_ => 0,
};
let mid = x.saturating_add(next.saturating_sub(x) / 2);
let om = SRGB_OETF.lookup(Q0_16::from_raw(mid));
let em = SRGB_EOTF.lookup(Q0_16::from_raw(mid));
let on = SRGB_OETF.lookup(Q0_16::from_raw(next));
let en = SRGB_EOTF.lookup(Q0_16::from_raw(next));
assert!(om >= o && om <= on);
assert!(em >= e && em <= en);
}
}
}
}