#![allow(dead_code)]
#[allow(dead_code)]
#[derive(Debug, Clone, PartialEq)]
pub struct GammaConfig {
pub gamma: f32,
pub use_srgb: bool,
pub brightness: f32,
pub contrast: f32,
}
impl Default for GammaConfig {
fn default() -> Self {
Self {
gamma: 2.2,
use_srgb: true,
brightness: 0.0,
contrast: 1.0,
}
}
}
#[allow(dead_code)]
pub fn linear_to_srgb(x: f32) -> f32 {
let x = x.clamp(0.0, 1.0);
if x <= 0.0031308 {
x * 12.92
} else {
1.055 * x.powf(1.0 / 2.4) - 0.055
}
}
#[allow(dead_code)]
pub fn srgb_to_linear(x: f32) -> f32 {
let x = x.clamp(0.0, 1.0);
if x <= 0.04045 {
x / 12.92
} else {
((x + 0.055) / 1.055).powf(2.4)
}
}
#[allow(dead_code)]
pub fn gamma_encode(x: f32, gamma: f32) -> f32 {
if gamma.abs() < 1e-6 {
return x.clamp(0.0, 1.0);
}
x.clamp(0.0, 1.0).powf(1.0 / gamma)
}
#[allow(dead_code)]
pub fn gamma_decode(x: f32, gamma: f32) -> f32 {
if gamma.abs() < 1e-6 {
return x.clamp(0.0, 1.0);
}
x.clamp(0.0, 1.0).powf(gamma)
}
#[allow(dead_code)]
pub fn brightness_contrast(x: f32, brightness: f32, contrast: f32) -> f32 {
((x - 0.5) * contrast + 0.5 + brightness).clamp(0.0, 1.0)
}
#[allow(dead_code)]
pub fn apply_gamma_rgb(color: [f32; 3], config: &GammaConfig) -> [f32; 3] {
let mut result = [0.0_f32; 3];
for i in 0..3 {
let c = brightness_contrast(color[i], config.brightness, config.contrast);
result[i] = if config.use_srgb {
linear_to_srgb(c)
} else {
gamma_encode(c, config.gamma)
};
}
result
}
#[allow(dead_code)]
pub fn remove_gamma_rgb(color: [f32; 3], config: &GammaConfig) -> [f32; 3] {
let mut result = [0.0_f32; 3];
for i in 0..3 {
result[i] = if config.use_srgb {
srgb_to_linear(color[i])
} else {
gamma_decode(color[i], config.gamma)
};
}
result
}
#[allow(dead_code)]
pub fn mid_grey(gamma: f32) -> f32 {
gamma_encode(0.18, gamma)
}
#[allow(dead_code)]
pub fn fast_srgb_to_linear(x: f32) -> f32 {
x.clamp(0.0, 1.0).powf(2.2)
}
#[allow(dead_code)]
pub fn fast_linear_to_srgb(x: f32) -> f32 {
x.clamp(0.0, 1.0).powf(1.0 / 2.2)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_config() {
let c = GammaConfig::default();
assert!((c.gamma - 2.2).abs() < 1e-5);
assert!(c.use_srgb);
}
#[test]
fn test_linear_to_srgb_zero() {
assert!(linear_to_srgb(0.0).abs() < 1e-6);
}
#[test]
fn test_linear_to_srgb_one() {
assert!((linear_to_srgb(1.0) - 1.0).abs() < 1e-5);
}
#[test]
fn test_srgb_roundtrip() {
let values = [0.0, 0.1, 0.18, 0.5, 0.8, 1.0];
for &v in &values {
let encoded = linear_to_srgb(v);
let decoded = srgb_to_linear(encoded);
assert!((v - decoded).abs() < 1e-5, "Roundtrip failed for {v}");
}
}
#[test]
fn test_gamma_encode_decode_roundtrip() {
let gamma = 2.2;
let values = [0.0, 0.1, 0.5, 1.0];
for &v in &values {
let encoded = gamma_encode(v, gamma);
let decoded = gamma_decode(encoded, gamma);
assert!((v - decoded).abs() < 1e-5);
}
}
#[test]
fn test_brightness_contrast_neutral() {
let v = brightness_contrast(0.5, 0.0, 1.0);
assert!((v - 0.5).abs() < 1e-5);
}
#[test]
fn test_brightness_increase() {
let v = brightness_contrast(0.5, 0.1, 1.0);
assert!(v > 0.5);
}
#[test]
fn test_apply_gamma_rgb_clamped() {
let r = apply_gamma_rgb([1.5, -0.1, 0.5], &GammaConfig::default());
for ch in &r {
assert!((0.0..=1.0).contains(ch));
}
}
#[test]
fn test_mid_grey() {
let mg = mid_grey(2.2);
assert!(mg > 0.4 && mg < 0.5);
}
#[test]
fn test_fast_srgb_approximation() {
let exact = srgb_to_linear(0.5);
let fast = fast_srgb_to_linear(0.5);
assert!((exact - fast).abs() < 0.01);
}
}