1#![cfg_attr(feature = "document-features", doc = document_features::document_features!())]
20#![expect(clippy::wrong_self_convention)]
23
24#[cfg(feature = "cint")]
25mod cint_impl;
26
27mod color32;
28pub use color32::*;
29
30mod hsva_gamma;
31pub use hsva_gamma::*;
32
33mod hsva;
34pub use hsva::*;
35
36#[cfg(feature = "color-hex")]
37mod hex_color_macro;
38#[cfg(feature = "color-hex")]
39#[doc(hidden)]
40pub use color_hex;
41
42mod rgba;
43pub use rgba::*;
44
45mod hex_color_runtime;
46pub use hex_color_runtime::*;
47
48impl From<Color32> for Rgba {
52 fn from(srgba: Color32) -> Self {
53 let [r, g, b, a] = srgba.to_array();
54 if a == 0 {
55 Self([
57 linear_f32_from_gamma_u8(r),
58 linear_f32_from_gamma_u8(g),
59 linear_f32_from_gamma_u8(b),
60 0.0,
61 ])
62 } else {
63 let a = linear_f32_from_linear_u8(a);
64 Self([
65 linear_from_gamma(r as f32 / (255.0 * a)) * a,
66 linear_from_gamma(g as f32 / (255.0 * a)) * a,
67 linear_from_gamma(b as f32 / (255.0 * a)) * a,
68 a,
69 ])
70 }
71 }
72}
73
74impl From<Rgba> for Color32 {
75 fn from(rgba: Rgba) -> Self {
76 let [r, g, b, a] = rgba.to_array();
77 if a == 0.0 {
78 Self([
80 gamma_u8_from_linear_f32(r),
81 gamma_u8_from_linear_f32(g),
82 gamma_u8_from_linear_f32(b),
83 0,
84 ])
85 } else {
86 Self([
87 fast_round(gamma_u8_from_linear_f32(r / a) as f32 * a),
88 fast_round(gamma_u8_from_linear_f32(g / a) as f32 * a),
89 fast_round(gamma_u8_from_linear_f32(b / a) as f32 * a),
90 linear_u8_from_linear_f32(a),
91 ])
92 }
93 }
94}
95
96pub fn linear_f32_from_gamma_u8(s: u8) -> f32 {
98 if s <= 10 {
99 s as f32 / 3294.6
100 } else {
101 ((s as f32 + 14.025) / 269.025).powf(2.4)
102 }
103}
104
105#[inline(always)]
108pub const fn linear_f32_from_linear_u8(a: u8) -> f32 {
109 a as f32 / 255.0
110}
111
112pub fn gamma_u8_from_linear_f32(l: f32) -> u8 {
115 if l <= 0.0 {
116 0
117 } else if l <= 0.0031308 {
118 fast_round(3294.6 * l)
119 } else if l <= 1.0 {
120 fast_round(269.025 * l.powf(1.0 / 2.4) - 14.025)
121 } else {
122 255
123 }
124}
125
126#[inline(always)]
129pub fn linear_u8_from_linear_f32(a: f32) -> u8 {
130 fast_round(a * 255.0)
131}
132
133const fn fast_round(r: f32) -> u8 {
134 (r + 0.5) as _ }
136
137#[inline]
139const fn mul_frac_round(val: u8, frac: u8) -> u8 {
140 let p = (val as u16) * (frac as u16) + 128;
142 ((p + (p >> 8)) >> 8) as u8
143 }
147
148#[test]
149pub fn test_srgba_conversion() {
150 for b in 0..=255 {
151 let l = linear_f32_from_gamma_u8(b);
152 assert!(0.0 <= l && l <= 1.0);
153 assert_eq!(gamma_u8_from_linear_f32(l), b);
154 }
155}
156
157pub fn linear_from_gamma(gamma: f32) -> f32 {
160 if gamma < 0.0 {
161 -linear_from_gamma(-gamma)
162 } else if gamma <= 0.04045 {
163 gamma / 12.92
164 } else {
165 ((gamma + 0.055) / 1.055).powf(2.4)
166 }
167}
168
169pub fn gamma_from_linear(linear: f32) -> f32 {
172 if linear < 0.0 {
173 -gamma_from_linear(-linear)
174 } else if linear <= 0.0031308 {
175 12.92 * linear
176 } else {
177 1.055 * linear.powf(1.0 / 2.4) - 0.055
178 }
179}
180
181pub fn tint_color_towards(color: Color32, target: Color32) -> Color32 {
186 let [mut r, mut g, mut b, mut a] = color.to_array();
187
188 if a == 0 {
189 r /= 2;
190 g /= 2;
191 b /= 2;
192 } else if a < 170 {
193 let div = (2 * 255 / a as i32) as u8;
196 r = r / 2 + target.r() / div;
197 g = g / 2 + target.g() / div;
198 b = b / 2 + target.b() / div;
199 a /= 2;
200 } else {
201 r = r / 2 + target.r() / 2;
202 g = g / 2 + target.g() / 2;
203 b = b / 2 + target.b() / 2;
204 }
205 Color32::from_rgba_premultiplied(r, g, b, a)
206}