Skip to content

Commit 21fd596

Browse files
committed
added srgb_lut feature to optimize cpu calculations for srgb -> linear by using lookup tables
1 parent 6b11dd3 commit 21fd596

File tree

4 files changed

+102
-0
lines changed

4 files changed

+102
-0
lines changed

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,7 @@ hex = "0.4"
1818
[profile.release]
1919
lto = "thin"
2020
codegen-units = 1
21+
22+
[features]
23+
default = []
24+
srgb_lut = [] # bool

src/color/lut.rs

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
// A lookup table for color to linear conversions,
2+
// sacrificing memory for speed (replacing heavy pow calculations).
3+
// This should primarily be used for CPU-based calculations,
4+
// since GPUs have built-in pow functions and don't need this optimization.
5+
6+
#![allow(dead_code)]
7+
8+
// Compile-time params
9+
10+
use std::sync::OnceLock;
11+
12+
/// linear -> sRGB table size
13+
const N_ENC: usize = 4096;
14+
15+
static SRGB_TO_LINEAR_F32: OnceLock<[f32; 256]> = OnceLock::new();
16+
static LINEAR_TO_SRGB_U8: OnceLock<[u8; N_ENC]> = OnceLock::new();
17+
18+
#[inline]
19+
fn build_srgb_to_linear_f32() -> [f32; 256] {
20+
let mut t = [0.0f32; 256];
21+
for (v, item) in t.iter_mut().enumerate() {
22+
let x = (v as f32) / 255.0;
23+
*item = if x <= 0.04045 {
24+
x / 12.92
25+
} else {
26+
((x + 0.055) / 1.055).powf(2.4)
27+
};
28+
}
29+
t
30+
}
31+
32+
#[inline]
33+
fn build_linear_to_srgb_u8() -> [u8; N_ENC] {
34+
let mut t = [0u8; N_ENC];
35+
for (i, item) in t.iter_mut().enumerate() {
36+
let x = (i as f32) / (N_ENC as f32 - 1.0); // 0..1
37+
let y = if x <= 0.003_130_8 {
38+
12.92 * x
39+
} else {
40+
1.055 * x.powf(1.0 / 2.4) - 0.055
41+
};
42+
*item = ((y.clamp(0.0, 1.0) * 255.0) + 0.5).floor() as u8;
43+
}
44+
t
45+
}
46+
47+
#[inline]
48+
fn get_srgb_to_linear_f32() -> &'static [f32; 256] {
49+
SRGB_TO_LINEAR_F32.get_or_init(build_srgb_to_linear_f32)
50+
}
51+
52+
#[inline]
53+
fn get_linear_to_srgb_u8() -> &'static [u8; N_ENC] {
54+
LINEAR_TO_SRGB_U8.get_or_init(build_linear_to_srgb_u8)
55+
}
56+
57+
// we'll hide the public api behind a feature
58+
59+
#[cfg(feature = "srgb_lut")]
60+
#[inline]
61+
pub(crate) fn decode_srgb_lut_f32(v: u8) -> f32 {
62+
get_srgb_to_linear_f32()[v as usize]
63+
}
64+
65+
#[cfg(feature = "srgb_lut")]
66+
#[inline]
67+
pub(crate) fn encode_srgb_lut_f32(x: f32) -> u8 {
68+
let x = x.clamp(0.0, 1.0);
69+
let idx = x * (N_ENC as f32 - 1.0);
70+
let i = idx as usize;
71+
if i >= N_ENC - 1 {
72+
return get_linear_to_srgb_u8()[N_ENC - 1];
73+
}
74+
let f = idx - i as f32;
75+
let a = get_linear_to_srgb_u8()[i] as u16;
76+
let b = get_linear_to_srgb_u8()[i + 1] as u16;
77+
// linear interp in integer space, then round
78+
let y = a as f32 + (b as f32 - a as f32) * f;
79+
(y + 0.5).floor() as u8
80+
}

src/color/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
1+
#[cfg(feature = "srgb_lut")]
2+
pub mod lut;
13
pub mod model;
24
pub mod parse;

src/color/model.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,15 @@ use std::fmt::{self};
1010

1111
use crate::traits::float::{Float, clamp_generic};
1212

13+
/// Decode an 8 bit sRGB value into a linear float using a lookup table.
14+
#[cfg(feature = "srgb_lut")]
15+
#[inline]
16+
fn decode_srgb<T: Float>(srgb_u8: u8) -> T {
17+
T::from_f32(crate::color::lut::decode_srgb_lut_f32(srgb_u8))
18+
}
19+
1320
/// Decode an 8 bit sRGB value into a linear float.
21+
#[cfg(not(feature = "srgb_lut"))]
1422
#[inline]
1523
fn decode_srgb<T: Float>(srgb_u8: u8) -> T {
1624
let srgb = T::from_f64((srgb_u8 as f64) / 255.0);
@@ -29,7 +37,15 @@ fn decode_srgb<T: Float>(srgb_u8: u8) -> T {
2937
}
3038
}
3139

40+
/// Encode an 8 bit sRGB value into a linear float using a lookup table.
41+
#[cfg(feature = "srgb_lut")]
42+
#[inline]
43+
fn encode_srgb<T: Float>(lin: T) -> u8 {
44+
crate::color::lut::encode_srgb_lut_f32(lin.to_f32())
45+
}
46+
3247
/// Encode a linear float into an 8 bit sRGB value.
48+
#[cfg(not(feature = "srgb_lut"))]
3349
#[inline]
3450
fn encode_srgb<T: Float>(lin: T) -> u8 {
3551
let l = lin.clamp01();

0 commit comments

Comments
 (0)