Skip to main content

pfx_post/
color.rs

1pub const REC709: [f32; 3] = [0.2126, 0.7152, 0.0722];
2
3pub fn dot3(a: [f32; 3], b: [f32; 3]) -> f32 {
4    a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
5}
6
7pub fn luma(c: [f32; 3], weights: [f32; 3]) -> f32 {
8    dot3(c, weights)
9}
10
11pub fn length3(v: [f32; 3]) -> f32 {
12    dot3(v, v).sqrt()
13}
14
15pub fn normalize(v: [f32; 3]) -> [f32; 3] {
16    let len = length3(v);
17    if len < 1e-8 {
18        [0.0, 0.0, 1.0]
19    } else {
20        [v[0] / len, v[1] / len, v[2] / len]
21    }
22}
23
24pub fn lerp(a: f32, b: f32, t: f32) -> f32 {
25    a + (b - a) * t
26}
27
28pub fn lerp3(a: [f32; 3], b: [f32; 3], t: f32) -> [f32; 3] {
29    [
30        lerp(a[0], b[0], t),
31        lerp(a[1], b[1], t),
32        lerp(a[2], b[2], t),
33    ]
34}
35
36pub fn smoothstep(edge0: f32, edge1: f32, x: f32) -> f32 {
37    let span = edge1 - edge0;
38    if span.abs() < 1e-8 {
39        return if x < edge0 { 0.0 } else { 1.0 };
40    }
41    let t = ((x - edge0) / span).clamp(0.0, 1.0);
42    t * t * (3.0 - 2.0 * t)
43}
44
45pub fn saturate(c: [f32; 3], amount: f32, weights: [f32; 3]) -> [f32; 3] {
46    let l = luma(c, weights);
47    [
48        (l + (c[0] - l) * amount).max(0.0),
49        (l + (c[1] - l) * amount).max(0.0),
50        (l + (c[2] - l) * amount).max(0.0),
51    ]
52}
53
54pub use pfx_materials::linear_channel as srgb_channel;
55
56pub fn encode_channel(c: f32) -> f32 {
57    if c <= 0.0031308 {
58        c * 12.92
59    } else {
60        1.055 * c.max(0.0).powf(1.0 / 2.4) - 0.055
61    }
62}
63
64pub fn parse_hex(text: &str) -> Option<[f32; 3]> {
65    let hex = text.strip_prefix('#')?;
66    if hex.len() != 6 || !hex.chars().all(|c| c.is_ascii_hexdigit()) {
67        return None;
68    }
69    let byte = |i: usize| -> Option<f32> {
70        let v = u8::from_str_radix(&hex[i..i + 2], 16).ok()?;
71        Some(srgb_channel(v as f32 / 255.0))
72    };
73    Some([byte(0)?, byte(2)?, byte(4)?])
74}
75
76pub fn sobel_luma(image: &crate::image::Image, x: i32, y: i32) -> f32 {
77    let l = |dx: i32, dy: i32| luma(crate::image::rgb(image.get(x + dx, y + dy)), REC709);
78    let gx = -l(-1, -1) + l(1, -1) - 2.0 * l(-1, 0) + 2.0 * l(1, 0) - l(-1, 1) + l(1, 1);
79    let gy = -l(-1, -1) - 2.0 * l(0, -1) - l(1, -1) + l(-1, 1) + 2.0 * l(0, 1) + l(1, 1);
80    (gx * gx + gy * gy).sqrt()
81}
82
83pub fn safe_pow(a: f32, b: f32) -> f32 {
84    if a <= 0.0 { 0.0 } else { a.powf(b) }
85}