Skip to content

Commit c8c63c3

Browse files
author
BuildTools
committed
Reworked and reorganized lookup table generation and added documentation
1 parent a2e8ae7 commit c8c63c3

File tree

9 files changed

+1689
-619
lines changed

9 files changed

+1689
-619
lines changed

codegen/src/lut.rs

+95-225
Large diffs are not rendered by default.

codegen/src/lut/model.rs

+129
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
use super::TransferFn;
2+
3+
/// This struct contains the scale and bias for a linear
4+
/// regression model of a transfer function on a given interval.
5+
///
6+
/// This model is calculated by using simple linear regression with
7+
/// integration instead of summation.
8+
pub(super) struct LinearModel {
9+
scale: f64,
10+
bias: f64,
11+
}
12+
13+
impl LinearModel {
14+
pub(super) fn new(
15+
transfer_fn: &TransferFn,
16+
start: u32,
17+
end: u32,
18+
man_index_width: u32,
19+
t_width: u32,
20+
) -> Self {
21+
let TransferFn {
22+
linear_scale,
23+
alpha,
24+
beta,
25+
gamma,
26+
..
27+
} = *transfer_fn;
28+
29+
let beta_bits = (beta as f32).to_bits();
30+
// Corresponds to the scale between differentials. Specifically,
31+
// `dx = exp_scale * dt`
32+
let exp_scale = f32::from_bits(((start >> 23) - man_index_width - t_width) << 23) as f64;
33+
let start_x = f32::from_bits(start) as f64;
34+
let end_x = f32::from_bits(end) as f64;
35+
36+
// If the transfer function is purely linear on a given interval,
37+
// integration is unnecessary.
38+
if let Some(linear_scale) = linear_scale {
39+
if end <= beta_bits {
40+
return Self {
41+
scale: linear_scale * exp_scale,
42+
bias: linear_scale * start_x,
43+
};
44+
}
45+
}
46+
47+
let max_t = 2.0f64.powi(t_width as i32);
48+
49+
let (integral_y, integral_ty) = match linear_scale {
50+
Some(linear_scale) if start < beta_bits => {
51+
let beta_t =
52+
(beta_bits << (9 + man_index_width)) as f64 * 2.0f64.powi(t_width as i32 - 32);
53+
let int_linear =
54+
integrate_linear((start_x, beta), (0.0, beta_t), linear_scale, exp_scale);
55+
let int_exponential =
56+
integrate_exponential((beta, end_x), (beta_t, max_t), alpha, gamma, exp_scale);
57+
(
58+
int_linear.0 + int_exponential.0,
59+
int_linear.1 + int_exponential.1,
60+
)
61+
}
62+
_ => integrate_exponential((start_x, end_x), (0.0, max_t), alpha, gamma, exp_scale),
63+
};
64+
let max_t2 = max_t * max_t;
65+
let integral_t = max_t2 * 0.5;
66+
let integral_t2 = max_t2 * max_t / 3.0;
67+
68+
let scale = (max_t * integral_ty - integral_t * integral_y)
69+
/ (max_t * integral_t2 - integral_t * integral_t);
70+
Self {
71+
scale,
72+
bias: (integral_y - scale * integral_t) / max_t,
73+
}
74+
}
75+
76+
pub(super) fn into_u8_lookup(self) -> u32 {
77+
let scale_uint = (255.0 * self.scale * 65536.0 + 0.5) as u32;
78+
let bias_uint = (((255.0 * self.bias + 0.5) * 128.0 + 0.5) as u32) << 9;
79+
(bias_uint << 7) | scale_uint
80+
}
81+
82+
pub(super) fn into_u16_lookup(self) -> u64 {
83+
let scale_uint = (65535.0 * self.scale * 4294967296.0 + 0.5) as u64;
84+
let bias_uint = (((65535.0 * self.bias + 0.5) * 32768.0 + 0.5) as u64) << 17;
85+
(bias_uint << 15) | scale_uint
86+
}
87+
}
88+
89+
fn integrate_linear(
90+
(start_x, end_x): (f64, f64),
91+
(start_t, end_t): (f64, f64),
92+
linear_scale: f64,
93+
exp_scale: f64,
94+
) -> (f64, f64) {
95+
let antiderive_y = |x: f64| 0.5 * linear_scale * x * x / exp_scale;
96+
let antiderive_ty =
97+
|x: f64, t: f64| 0.5 * linear_scale * x * x * (t - x / (3.0 * exp_scale)) / exp_scale;
98+
99+
(
100+
antiderive_y(end_x) - antiderive_y(start_x),
101+
antiderive_ty(end_x, end_t) - antiderive_ty(start_x, start_t),
102+
)
103+
}
104+
105+
fn integrate_exponential(
106+
(start_x, end_x): (f64, f64),
107+
(start_t, end_t): (f64, f64),
108+
alpha: f64,
109+
gamma: f64,
110+
exp_scale: f64,
111+
) -> (f64, f64) {
112+
let one_plus_gamma_inv = 1.0 + gamma.recip();
113+
let antiderive_y = |x: f64, t: f64| {
114+
alpha * gamma * x.powf(one_plus_gamma_inv) / (exp_scale * (1.0 + gamma)) + (1.0 - alpha) * t
115+
};
116+
let antiderive_ty = |x: f64, t: f64| {
117+
alpha
118+
* gamma
119+
* x.powf(one_plus_gamma_inv)
120+
* (t - gamma * x / (exp_scale * (1.0 + 2.0 * gamma)))
121+
/ (exp_scale * (1.0 + gamma))
122+
+ 0.5 * (1.0 - alpha) * t * t
123+
};
124+
125+
(
126+
antiderive_y(end_x, end_t) - antiderive_y(start_x, start_t),
127+
antiderive_ty(end_x, end_t) - antiderive_ty(start_x, start_t),
128+
)
129+
}

palette/src/encoding/adobe.rs

+15
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,21 @@ use super::{FromLinear, IntoLinear};
1717
/// in cyan-green hues.
1818
///
1919
/// The Adobe RGB standard uses a gamma 2.2 transfer function.
20+
///
21+
///# As transfer function
22+
///
23+
/// `AdobeRgb` will not use any kind of approximation when converting from `T` to
24+
/// `T`. This involves calls to `powf`, which may make it too slow for certain
25+
/// applications.
26+
///
27+
/// There are some specialized cases where it has been optimized:
28+
///
29+
/// * When converting from `u8` to `f32` or `f64`, while converting to linear
30+
/// space. This uses lookup tables with precomputed values.
31+
/// * When converting from `f32` or `f64` to `u8`, while converting from linear
32+
/// space. This uses a fast algorithm that guarantees a maximum error in the
33+
/// result of less than 0.6 in line with [this DirectX spec]
34+
/// (https://microsoft.github.io/DirectX-Specs/d3d/archive/D3D11_3_FunctionalSpec.htm#FLOATtoSRGB).
2035
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
2136
pub struct AdobeRgb;
2237

palette/src/encoding/lut.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ mod codegen;
33
const MAX_FLOAT_BITS: u32 = 0x3f7fffff; // 1.0 - f32::EPSILON
44

55
// SAFETY: Only use this macro if `input` is clamped between `min_float` and `max_float`.
6-
macro_rules! linear_float_to_encoded_uint {
6+
macro_rules! unsafe_linear_float_to_encoded_uint {
77
($enc:ty, $lut:ty, $input:ident, $min_float_bits:ident, $table:ident, $bit_width:expr, $man_index_width:expr) => {{
88
let input_bits = $input.to_bits();
99
#[cfg(test)]
@@ -16,7 +16,7 @@ macro_rules! linear_float_to_encoded_uint {
1616
{
1717
debug_assert!($table.get(i).is_some());
1818
}
19-
unsafe { *$table.get_unchecked(i) }
19+
*$table.get_unchecked(i)
2020
};
2121

2222
let bias = (entry >> (2 * $bit_width)) << ($bit_width + 1);
@@ -44,7 +44,7 @@ fn linear_f32_to_encoded_u8(linear: f32, min_float_bits: u32, table: &[u32]) ->
4444
input = max_float;
4545
}
4646

47-
linear_float_to_encoded_uint!(u8, u32, input, min_float_bits, table, 8, 3)
47+
unsafe { unsafe_linear_float_to_encoded_uint!(u8, u32, input, min_float_bits, table, 8, 3) }
4848
}
4949

5050
#[cfg(feature = "gamma_lut_u16")]
@@ -69,5 +69,5 @@ fn linear_f32_to_encoded_u16_with_linear_scale(
6969
return ((linear_scale * input + 8388608.0).to_bits() & 65535) as u16;
7070
}
7171

72-
linear_float_to_encoded_uint!(u16, u64, input, min_float_bits, table, 16, 7)
72+
unsafe { unsafe_linear_float_to_encoded_uint!(u16, u64, input, min_float_bits, table, 16, 7) }
7373
}

0 commit comments

Comments
 (0)