veloren_common/util/
color.rs

1use vek::{Mat3, Rgb, Rgba, Vec3};
2
3/// This function is optimized for speed over perfect accuracy
4#[inline(always)]
5#[expect(clippy::excessive_precision)]
6pub fn srgb_to_linear_fast(col: Rgb<f32>) -> Rgb<f32> {
7    col.map(|c| {
8        if c <= 0.104 {
9            c * 0.08677088
10        } else {
11            0.012522878 * c + 0.682171111 * c * c + 0.305306011 * c * c * c
12        }
13    })
14}
15
16/// directly converted from 'vec3 srgb_to_linear(vec3 srgb)' function in
17/// 'srgb.glsl'
18#[inline(always)]
19pub fn srgb_to_linear(col: Rgb<f32>) -> Rgb<f32> {
20    col.map(|c| {
21        if c <= 0.04045 {
22            c / 12.92
23        } else {
24            f32::powf((c + 0.055) / 1.055, 2.4)
25        }
26    })
27}
28
29#[inline(always)]
30#[expect(clippy::excessive_precision)]
31pub fn linear_to_srgb(col: Rgb<f32>) -> Rgb<f32> {
32    col.map(|c| {
33        if c <= 0.0060 {
34            c * 11.500726
35        } else {
36            let s1 = c.sqrt();
37            let s2 = s1.sqrt();
38            let s3 = s2.sqrt();
39            0.585122381 * s1 + 0.783140355 * s2 - 0.368262736 * s3
40        }
41    })
42}
43
44#[inline(always)]
45pub fn srgba_to_linear(col: Rgba<f32>) -> Rgba<f32> {
46    Rgba::from_translucent(srgb_to_linear_fast(Rgb::from(col)), col.a)
47}
48
49#[inline(always)]
50pub fn linear_to_srgba(col: Rgba<f32>) -> Rgba<f32> {
51    Rgba::from_translucent(linear_to_srgb(Rgb::from(col)), col.a)
52}
53
54/// Convert rgb to hsv. Expects rgb to be [0, 1].
55#[inline(always)]
56pub fn rgb_to_hsv(rgb: Rgb<f32>) -> Vec3<f32> {
57    let (r, g, b) = rgb.into_tuple();
58    let (max, min, diff, add) = {
59        let (max, min, diff, add) = if r > g {
60            (r, g, g - b, 0.0)
61        } else {
62            (g, r, b - r, 2.0)
63        };
64        if b > max {
65            (b, min, r - g, 4.0)
66        } else {
67            (max, b.min(min), diff, add)
68        }
69    };
70
71    let v = max;
72    let h = if max == min {
73        0.0
74    } else {
75        let mut h = 60.0 * (add + diff / (max - min));
76        if h < 0.0 {
77            h += 360.0;
78        }
79        h
80    };
81    let s = if max == 0.0 { 0.0 } else { (max - min) / max };
82
83    Vec3::new(h, s, v)
84}
85
86/// Convert hsv to rgb. Expects h [0, 360], s [0, 1], v [0, 1]
87#[inline(always)]
88pub fn hsv_to_rgb(hsv: Vec3<f32>) -> Rgb<f32> {
89    let (h, s, v) = hsv.into_tuple();
90    let c = s * v;
91    let h = h / 60.0;
92    let x = c * (1.0 - (h % 2.0 - 1.0).abs());
93    let m = v - c;
94
95    let (r, g, b) = if (0.0..=1.0).contains(&h) {
96        (c, x, 0.0)
97    } else if h <= 2.0 {
98        (x, c, 0.0)
99    } else if h <= 3.0 {
100        (0.0, c, x)
101    } else if h <= 4.0 {
102        (0.0, x, c)
103    } else if h <= 5.0 {
104        (x, 0.0, c)
105    } else {
106        (c, 0.0, x)
107    };
108
109    Rgb::new(r + m, g + m, b + m)
110}
111
112/// Convert linear rgb to CIEXYZ
113#[inline(always)]
114pub fn rgb_to_xyz(rgb: Rgb<f32>) -> Vec3<f32> {
115    // XYZ
116    Mat3::new(
117        0.4124, 0.3576, 0.1805, 0.2126, 0.7152, 0.0722, 0.0193, 0.1192, 0.9504,
118    ) * Vec3::from(rgb)
119}
120
121/// Convert linear rgb to CIExyY
122#[inline(always)]
123pub fn rgb_to_xyy(rgb: Rgb<f32>) -> Vec3<f32> {
124    // XYZ
125    let xyz = rgb_to_xyz(rgb);
126
127    let sum = xyz.sum();
128    Vec3::new(xyz.x / sum, xyz.y / sum, xyz.y)
129}
130
131/// Convert to CIExyY to linear rgb
132#[inline(always)]
133pub fn xyy_to_rgb(xyy: Vec3<f32>) -> Rgb<f32> {
134    let xyz = Vec3::new(
135        xyy.z / xyy.y * xyy.x,
136        xyy.z,
137        xyy.z / xyy.y * (1.0 - xyy.x - xyy.y),
138    );
139
140    Rgb::from(
141        Mat3::new(
142            3.2406, -1.5372, -0.4986, -0.9689, 1.8758, 0.0415, 0.0557, -0.2040, 1.0570,
143        ) * xyz,
144    )
145}
146
147// TO-DO: speed this up
148#[inline(always)]
149pub fn saturate_srgb(col: Rgb<f32>, value: f32) -> Rgb<f32> {
150    let mut hsv = rgb_to_hsv(srgb_to_linear_fast(col));
151    hsv.y *= 1.0 + value;
152    linear_to_srgb(hsv_to_rgb(hsv).map(|e| e.clamp(0.0, 1.0)))
153}
154
155/// Preserves the luma of one color while changing its chromaticity to match the
156/// other
157#[inline(always)]
158pub fn chromify_srgb(luma: Rgb<f32>, chroma: Rgb<f32>) -> Rgb<f32> {
159    let l = rgb_to_xyy(srgb_to_linear_fast(luma)).z;
160    let mut xyy = rgb_to_xyy(srgb_to_linear_fast(chroma));
161    xyy.z = l;
162
163    linear_to_srgb(xyy_to_rgb(xyy).map(|e| e.clamp(0.0, 1.0)))
164}