tawasuyu

tawasuyu/brahman

Rust 572TOML 217Markdown 68JSON 198326 Definitions

⎇ 52acaabcf42e81de31acf884f80c607fe8b829fe crates/modules/gioser/gioser-palette/src/lib.rs Rust · 130 lines · 3.9 KB History Who touched what raw

gioser 52acaabcf · sergio ·

1//! Paleta visual de GioSer: cuatro elementos + cosmos.
2//!
3//! Los colores se exponen en RGB lineal (rango `0..=1`). Para CSS,
4//! convertir con `to_srgb_hex()`. Para shaders WebGL, pasar como `vec3`.
5//! La separación lineal/sRGB es deliberada: el motor blending suma luz
6//! en lineal y el ojo lee sRGB.
7
8#[derive(Clone, Copy, Debug, PartialEq)]
9pub struct Rgb(pub f32, pub f32, pub f32);
10
11impl Rgb {
12    pub const fn new(r: f32, g: f32, b: f32) -> Self {
13        Self(r, g, b)
14    }
15
16    pub const fn array(self) -> [f32; 3] {
17        [self.0, self.1, self.2]
18    }
19
20    /// Hex string sRGB `#rrggbb` en bytes ASCII (7 chars).
21    /// Hex en bytes evita allocs al pasar a CSS desde WASM.
22    pub fn to_srgb_hex(self) -> [u8; 7] {
23        fn encode(c: f32) -> u8 {
24            let g = if c <= 0.003_130_8 {
25                12.92 * c
26            } else {
27                1.055 * c.clamp(0.0, 1.0).powf(1.0 / 2.4) - 0.055
28            };
29            (g.clamp(0.0, 1.0) * 255.0 + 0.5) as u8
30        }
31        fn nib(x: u8) -> u8 {
32            if x < 10 {
33                b'0' + x
34            } else {
35                b'a' + (x - 10)
36            }
37        }
38        let r = encode(self.0);
39        let g = encode(self.1);
40        let b = encode(self.2);
41        [
42            b'#',
43            nib(r >> 4),
44            nib(r & 0x0f),
45            nib(g >> 4),
46            nib(g & 0x0f),
47            nib(b >> 4),
48            nib(b & 0x0f),
49        ]
50    }
51
52    pub fn lerp(self, other: Rgb, t: f32) -> Rgb {
53        Rgb(
54            self.0 + (other.0 - self.0) * t,
55            self.1 + (other.1 - self.1) * t,
56            self.2 + (other.2 - self.2) * t,
57        )
58    }
59}
60
61/// Los cuatro elementos canónicos.
62pub mod elements {
63    use super::Rgb;
64    /// Aire — azul-blanco luminoso. Software, IA, aspiración.
65    pub const AIRE: Rgb = Rgb(0.78, 0.86, 1.00);
66    /// Agua — cyan profundo. Espiritualidad aplicada.
67    pub const AGUA: Rgb = Rgb(0.28, 0.74, 0.95);
68    /// Fuego — ámbar/escarlata. Inspiración.
69    pub const FUEGO: Rgb = Rgb(0.98, 0.45, 0.18);
70    /// Tierra — ocre cálido. Cuerpo.
71    pub const TIERRA: Rgb = Rgb(0.82, 0.55, 0.28);
72
73    pub const ALL: [(&str, Rgb); 4] = [
74        ("aire", AIRE),
75        ("agua", AGUA),
76        ("fuego", FUEGO),
77        ("tierra", TIERRA),
78    ];
79}
80
81/// Fondo cósmico + elementos arquitectónicos.
82pub mod cosmos {
83    use super::Rgb;
84    /// Vacío profundo, casi negro con tinte violeta.
85    pub const VOID: Rgb = Rgb(0.030, 0.025, 0.060);
86    /// Nebulosa interior — violeta tenue.
87    pub const NEBULA_A: Rgb = Rgb(0.220, 0.130, 0.380);
88    /// Nebulosa exterior — azul profundo.
89    pub const NEBULA_B: Rgb = Rgb(0.080, 0.180, 0.320);
90    /// Núcleo solar central.
91    pub const SUN_CORE: Rgb = Rgb(1.000, 0.860, 0.520);
92    /// Líneas de la chacana — cyan helado.
93    pub const CHACANA_LINE: Rgb = Rgb(0.55, 0.92, 1.00);
94    /// Aro de fuego del logo — dorado-ámbar.
95    pub const CHACANA_RIM: Rgb = Rgb(0.95, 0.65, 0.32);
96    /// Polvo de estrellas.
97    pub const STARDUST: Rgb = Rgb(0.85, 0.88, 1.00);
98}
99
100#[cfg(test)]
101mod tests {
102    use super::*;
103
104    #[test]
105    fn hex_white() {
106        assert_eq!(&Rgb(1.0, 1.0, 1.0).to_srgb_hex(), b"#ffffff");
107    }
108
109    #[test]
110    fn hex_black() {
111        assert_eq!(&Rgb(0.0, 0.0, 0.0).to_srgb_hex(), b"#000000");
112    }
113
114    #[test]
115    fn lerp_midpoint() {
116        let m = Rgb(0.0, 0.0, 0.0).lerp(Rgb(1.0, 0.5, 0.0), 0.5);
117        assert_eq!(m, Rgb(0.5, 0.25, 0.0));
118    }
119
120    #[test]
121    fn linear_to_srgb_midgray_lifts_brightness() {
122        // 0.5 lineal codifica a sRGB ≈ 0.735 → byte ≈ 187 (0xbb..0xbc segun redondeo de powf).
123        // Bandgap [0xb8, 0xbf] permite drift de implementaciones de powf entre plataformas.
124        let h = Rgb(0.5, 0.5, 0.5).to_srgb_hex();
125        let lo = b"#b8b8b8";
126        let hi = b"#bfbfbf";
127        assert!(h.as_slice() >= lo.as_slice() && h.as_slice() <= hi.as_slice(),
128            "got {:?}", core::str::from_utf8(&h).unwrap_or(""));
129    }
130}