ecolor/
lib.rs

1//! Color conversions and types.
2//!
3//! If you want a compact color representation, use [`Color32`].
4//! If you want to manipulate RGBA colors use [`Rgba`].
5//! If you want to manipulate colors in a way closer to how humans think about colors, use [`HsvaGamma`].
6//!
7//! ## Feature flags
8#![cfg_attr(feature = "document-features", doc = document_features::document_features!())]
9//!
10
11#![allow(clippy::wrong_self_convention)]
12
13#[cfg(feature = "cint")]
14mod cint_impl;
15
16mod color32;
17pub use color32::*;
18
19mod hsva_gamma;
20pub use hsva_gamma::*;
21
22mod hsva;
23pub use hsva::*;
24
25#[cfg(feature = "color-hex")]
26mod hex_color_macro;
27#[cfg(feature = "color-hex")]
28#[doc(hidden)]
29pub use color_hex;
30
31mod rgba;
32pub use rgba::*;
33
34mod hex_color_runtime;
35pub use hex_color_runtime::*;
36
37// ----------------------------------------------------------------------------
38// Color conversion:
39
40impl From<Color32> for Rgba {
41    fn from(srgba: Color32) -> Self {
42        Self([
43            linear_f32_from_gamma_u8(srgba.0[0]),
44            linear_f32_from_gamma_u8(srgba.0[1]),
45            linear_f32_from_gamma_u8(srgba.0[2]),
46            linear_f32_from_linear_u8(srgba.0[3]),
47        ])
48    }
49}
50
51impl From<Rgba> for Color32 {
52    fn from(rgba: Rgba) -> Self {
53        Self([
54            gamma_u8_from_linear_f32(rgba.0[0]),
55            gamma_u8_from_linear_f32(rgba.0[1]),
56            gamma_u8_from_linear_f32(rgba.0[2]),
57            linear_u8_from_linear_f32(rgba.0[3]),
58        ])
59    }
60}
61
62/// gamma [0, 255] -> linear [0, 1].
63pub fn linear_f32_from_gamma_u8(s: u8) -> f32 {
64    if s <= 10 {
65        s as f32 / 3294.6
66    } else {
67        ((s as f32 + 14.025) / 269.025).powf(2.4)
68    }
69}
70
71/// linear [0, 255] -> linear [0, 1].
72/// Useful for alpha-channel.
73#[inline(always)]
74pub fn linear_f32_from_linear_u8(a: u8) -> f32 {
75    a as f32 / 255.0
76}
77
78/// linear [0, 1] -> gamma [0, 255] (clamped).
79/// Values outside this range will be clamped to the range.
80pub fn gamma_u8_from_linear_f32(l: f32) -> u8 {
81    if l <= 0.0 {
82        0
83    } else if l <= 0.0031308 {
84        fast_round(3294.6 * l)
85    } else if l <= 1.0 {
86        fast_round(269.025 * l.powf(1.0 / 2.4) - 14.025)
87    } else {
88        255
89    }
90}
91
92/// linear [0, 1] -> linear [0, 255] (clamped).
93/// Useful for alpha-channel.
94#[inline(always)]
95pub fn linear_u8_from_linear_f32(a: f32) -> u8 {
96    fast_round(a * 255.0)
97}
98
99fn fast_round(r: f32) -> u8 {
100    (r + 0.5) as _ // rust does a saturating cast since 1.45
101}
102
103#[test]
104pub fn test_srgba_conversion() {
105    for b in 0..=255 {
106        let l = linear_f32_from_gamma_u8(b);
107        assert!(0.0 <= l && l <= 1.0);
108        assert_eq!(gamma_u8_from_linear_f32(l), b);
109    }
110}
111
112/// gamma [0, 1] -> linear [0, 1] (not clamped).
113/// Works for numbers outside this range (e.g. negative numbers).
114pub fn linear_from_gamma(gamma: f32) -> f32 {
115    if gamma < 0.0 {
116        -linear_from_gamma(-gamma)
117    } else if gamma <= 0.04045 {
118        gamma / 12.92
119    } else {
120        ((gamma + 0.055) / 1.055).powf(2.4)
121    }
122}
123
124/// linear [0, 1] -> gamma [0, 1] (not clamped).
125/// Works for numbers outside this range (e.g. negative numbers).
126pub fn gamma_from_linear(linear: f32) -> f32 {
127    if linear < 0.0 {
128        -gamma_from_linear(-linear)
129    } else if linear <= 0.0031308 {
130        12.92 * linear
131    } else {
132        1.055 * linear.powf(1.0 / 2.4) - 0.055
133    }
134}
135
136// ----------------------------------------------------------------------------
137
138/// Cheap and ugly.
139/// Made for graying out disabled `Ui`s.
140pub fn tint_color_towards(color: Color32, target: Color32) -> Color32 {
141    let [mut r, mut g, mut b, mut a] = color.to_array();
142
143    if a == 0 {
144        r /= 2;
145        g /= 2;
146        b /= 2;
147    } else if a < 170 {
148        // Cheapish and looks ok.
149        // Works for e.g. grid stripes.
150        let div = (2 * 255 / a as i32) as u8;
151        r = r / 2 + target.r() / div;
152        g = g / 2 + target.g() / div;
153        b = b / 2 + target.b() / div;
154        a /= 2;
155    } else {
156        r = r / 2 + target.r() / 2;
157        g = g / 2 + target.g() / 2;
158        b = b / 2 + target.b() / 2;
159    }
160    Color32::from_rgba_premultiplied(r, g, b, a)
161}