1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
use vek::{Mat3, Rgb, Rgba, Vec3};

/// This function is optimized for speed over perfect accuracy
#[inline(always)]
#[allow(clippy::excessive_precision)]
pub fn srgb_to_linear_fast(col: Rgb<f32>) -> Rgb<f32> {
    col.map(|c| {
        if c <= 0.104 {
            c * 0.08677088
        } else {
            0.012522878 * c + 0.682171111 * c * c + 0.305306011 * c * c * c
        }
    })
}

/// directly converted from 'vec3 srgb_to_linear(vec3 srgb)' function in
/// 'srgb.glsl'
#[inline(always)]
pub fn srgb_to_linear(col: Rgb<f32>) -> Rgb<f32> {
    col.map(|c| {
        if c <= 0.04045 {
            c / 12.92
        } else {
            f32::powf((c + 0.055) / 1.055, 2.4)
        }
    })
}

#[inline(always)]
#[allow(clippy::excessive_precision)]
pub fn linear_to_srgb(col: Rgb<f32>) -> Rgb<f32> {
    col.map(|c| {
        if c <= 0.0060 {
            c * 11.500726
        } else {
            let s1 = c.sqrt();
            let s2 = s1.sqrt();
            let s3 = s2.sqrt();
            0.585122381 * s1 + 0.783140355 * s2 - 0.368262736 * s3
        }
    })
}

#[inline(always)]
pub fn srgba_to_linear(col: Rgba<f32>) -> Rgba<f32> {
    Rgba::from_translucent(srgb_to_linear_fast(Rgb::from(col)), col.a)
}

#[inline(always)]
pub fn linear_to_srgba(col: Rgba<f32>) -> Rgba<f32> {
    Rgba::from_translucent(linear_to_srgb(Rgb::from(col)), col.a)
}

/// Convert rgb to hsv. Expects rgb to be [0, 1].
#[inline(always)]
pub fn rgb_to_hsv(rgb: Rgb<f32>) -> Vec3<f32> {
    let (r, g, b) = rgb.into_tuple();
    let (max, min, diff, add) = {
        let (max, min, diff, add) = if r > g {
            (r, g, g - b, 0.0)
        } else {
            (g, r, b - r, 2.0)
        };
        if b > max {
            (b, min, r - g, 4.0)
        } else {
            (max, b.min(min), diff, add)
        }
    };

    let v = max;
    let h = if max == min {
        0.0
    } else {
        let mut h = 60.0 * (add + diff / (max - min));
        if h < 0.0 {
            h += 360.0;
        }
        h
    };
    let s = if max == 0.0 { 0.0 } else { (max - min) / max };

    Vec3::new(h, s, v)
}

/// Convert hsv to rgb. Expects h [0, 360], s [0, 1], v [0, 1]
#[inline(always)]
pub fn hsv_to_rgb(hsv: Vec3<f32>) -> Rgb<f32> {
    let (h, s, v) = hsv.into_tuple();
    let c = s * v;
    let h = h / 60.0;
    let x = c * (1.0 - (h % 2.0 - 1.0).abs());
    let m = v - c;

    let (r, g, b) = if (0.0..=1.0).contains(&h) {
        (c, x, 0.0)
    } else if h <= 2.0 {
        (x, c, 0.0)
    } else if h <= 3.0 {
        (0.0, c, x)
    } else if h <= 4.0 {
        (0.0, x, c)
    } else if h <= 5.0 {
        (x, 0.0, c)
    } else {
        (c, 0.0, x)
    };

    Rgb::new(r + m, g + m, b + m)
}

/// Convert linear rgb to CIEXYZ
#[inline(always)]
pub fn rgb_to_xyz(rgb: Rgb<f32>) -> Vec3<f32> {
    // XYZ
    Mat3::new(
        0.4124, 0.3576, 0.1805, 0.2126, 0.7152, 0.0722, 0.0193, 0.1192, 0.9504,
    ) * Vec3::from(rgb)
}

/// Convert linear rgb to CIExyY
#[inline(always)]
pub fn rgb_to_xyy(rgb: Rgb<f32>) -> Vec3<f32> {
    // XYZ
    let xyz = rgb_to_xyz(rgb);

    let sum = xyz.sum();
    Vec3::new(xyz.x / sum, xyz.y / sum, xyz.y)
}

/// Convert to CIExyY to linear rgb
#[inline(always)]
pub fn xyy_to_rgb(xyy: Vec3<f32>) -> Rgb<f32> {
    let xyz = Vec3::new(
        xyy.z / xyy.y * xyy.x,
        xyy.z,
        xyy.z / xyy.y * (1.0 - xyy.x - xyy.y),
    );

    Rgb::from(
        Mat3::new(
            3.2406, -1.5372, -0.4986, -0.9689, 1.8758, 0.0415, 0.0557, -0.2040, 1.0570,
        ) * xyz,
    )
}

// TO-DO: speed this up
#[inline(always)]
pub fn saturate_srgb(col: Rgb<f32>, value: f32) -> Rgb<f32> {
    let mut hsv = rgb_to_hsv(srgb_to_linear_fast(col));
    hsv.y *= 1.0 + value;
    linear_to_srgb(hsv_to_rgb(hsv).map(|e| e.clamp(0.0, 1.0)))
}

/// Preserves the luma of one color while changing its chromaticity to match the
/// other
#[inline(always)]
pub fn chromify_srgb(luma: Rgb<f32>, chroma: Rgb<f32>) -> Rgb<f32> {
    let l = rgb_to_xyy(srgb_to_linear_fast(luma)).z;
    let mut xyy = rgb_to_xyy(srgb_to_linear_fast(chroma));
    xyy.z = l;

    linear_to_srgb(xyy_to_rgb(xyy).map(|e| e.clamp(0.0, 1.0)))
}