#![allow(dead_code)]
#[allow(dead_code)]
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub enum GammaMode {
#[default]
Srgb,
Power(f32),
Linear,
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct GammaCorrectionState {
pub mode: GammaMode,
pub lift: f32,
pub gain: f32,
}
impl Default for GammaCorrectionState {
fn default() -> Self {
Self {
mode: GammaMode::Srgb,
lift: 0.0,
gain: 1.0,
}
}
}
#[allow(dead_code)]
pub fn new_gamma_correction_state() -> GammaCorrectionState {
GammaCorrectionState::default()
}
#[allow(dead_code)]
pub fn gamma_encode(linear: f32, mode: GammaMode) -> f32 {
let v = linear.clamp(0.0, 1.0);
match mode {
GammaMode::Linear => v,
GammaMode::Power(g) => v.powf(1.0 / g.max(1e-6)),
GammaMode::Srgb => {
if v <= 0.003_130_8 {
v * 12.92
} else {
1.055 * v.powf(1.0 / 2.4) - 0.055
}
}
}
}
#[allow(dead_code)]
pub fn gamma_decode(encoded: f32, mode: GammaMode) -> f32 {
let v = encoded.clamp(0.0, 1.0);
match mode {
GammaMode::Linear => v,
GammaMode::Power(g) => v.powf(g.max(1e-6)),
GammaMode::Srgb => {
if v <= 0.040_45 {
v / 12.92
} else {
((v + 0.055) / 1.055).powf(2.4)
}
}
}
}
#[allow(dead_code)]
pub fn gc_apply(rgb: [f32; 3], state: &GammaCorrectionState) -> [f32; 3] {
rgb.map(|c| {
let c = (c * state.gain + state.lift).clamp(0.0, 1.0);
gamma_encode(c, state.mode)
})
}
#[allow(dead_code)]
pub fn gc_set_lift(state: &mut GammaCorrectionState, v: f32) {
state.lift = v.clamp(-0.1, 0.1);
}
#[allow(dead_code)]
pub fn gc_set_gain(state: &mut GammaCorrectionState, v: f32) {
state.gain = v.clamp(0.5, 2.0);
}
#[allow(dead_code)]
pub fn gc_reset(state: &mut GammaCorrectionState) {
*state = GammaCorrectionState::default();
}
#[allow(dead_code)]
pub fn gc_is_identity(state: &GammaCorrectionState) -> bool {
matches!(state.mode, GammaMode::Linear)
&& state.lift.abs() < 1e-5
&& (state.gain - 1.0).abs() < 1e-5
}
#[allow(dead_code)]
pub fn gc_to_json(state: &GammaCorrectionState) -> String {
format!("{{\"lift\":{:.4},\"gain\":{:.4}}}", state.lift, state.gain)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn encode_decode_srgb_roundtrip() {
let v = 0.5_f32;
let enc = gamma_encode(v, GammaMode::Srgb);
let dec = gamma_decode(enc, GammaMode::Srgb);
assert!((dec - v).abs() < 1e-4);
}
#[test]
fn linear_mode_identity() {
let v = 0.7_f32;
assert!((gamma_encode(v, GammaMode::Linear) - v).abs() < 1e-6);
assert!((gamma_decode(v, GammaMode::Linear) - v).abs() < 1e-6);
}
#[test]
fn power_gamma_encode_increases() {
let a = gamma_encode(0.5, GammaMode::Power(2.2));
assert!(a > 0.5);
}
#[test]
fn clamps_input_high() {
assert!((gamma_encode(5.0, GammaMode::Srgb) - 1.0).abs() < 1e-5);
}
#[test]
fn clamps_input_low() {
assert!(gamma_encode(-1.0, GammaMode::Srgb) < 1e-6);
}
#[test]
fn lift_clamps() {
let mut s = new_gamma_correction_state();
gc_set_lift(&mut s, 5.0);
assert!((s.lift - 0.1).abs() < 1e-6);
}
#[test]
fn gain_clamps() {
let mut s = new_gamma_correction_state();
gc_set_gain(&mut s, 0.0);
assert!((s.gain - 0.5).abs() < 1e-6);
}
#[test]
fn reset_to_default() {
let mut s = new_gamma_correction_state();
gc_set_gain(&mut s, 2.0);
gc_reset(&mut s);
assert!((s.gain - 1.0).abs() < 1e-6);
}
#[test]
fn apply_produces_valid_range() {
let s = new_gamma_correction_state();
let out = gc_apply([0.5, 0.3, 0.8], &s);
assert!(out.iter().all(|&v| (0.0..=1.0).contains(&v)));
}
#[test]
fn json_has_keys() {
let j = gc_to_json(&new_gamma_correction_state());
assert!(j.contains("lift") && j.contains("gain"));
}
}