bevy_color/
linear_rgba.rs

1use crate::{
2    color_difference::EuclideanDistance, impl_componentwise_vector_space, Alpha, ColorToComponents,
3    ColorToPacked, Gray, Luminance, Mix, StandardColor,
4};
5use bevy_math::{ops, Vec3, Vec4};
6#[cfg(feature = "bevy_reflect")]
7use bevy_reflect::prelude::*;
8use bytemuck::{Pod, Zeroable};
9
10/// Linear RGB color with alpha.
11#[doc = include_str!("../docs/conversion.md")]
12/// <div>
13#[doc = include_str!("../docs/diagrams/model_graph.svg")]
14/// </div>
15#[derive(Debug, Clone, Copy, PartialEq, Pod, Zeroable)]
16#[cfg_attr(
17    feature = "bevy_reflect",
18    derive(Reflect),
19    reflect(Clone, PartialEq, Default)
20)]
21#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
22#[cfg_attr(
23    all(feature = "serialize", feature = "bevy_reflect"),
24    reflect(Serialize, Deserialize)
25)]
26#[repr(C)]
27pub struct LinearRgba {
28    /// The red channel. [0.0, 1.0]
29    pub red: f32,
30    /// The green channel. [0.0, 1.0]
31    pub green: f32,
32    /// The blue channel. [0.0, 1.0]
33    pub blue: f32,
34    /// The alpha channel. [0.0, 1.0]
35    pub alpha: f32,
36}
37
38impl StandardColor for LinearRgba {}
39
40impl_componentwise_vector_space!(LinearRgba, [red, green, blue, alpha]);
41
42impl LinearRgba {
43    /// A fully black color with full alpha.
44    pub const BLACK: Self = Self {
45        red: 0.0,
46        green: 0.0,
47        blue: 0.0,
48        alpha: 1.0,
49    };
50
51    /// A fully white color with full alpha.
52    pub const WHITE: Self = Self {
53        red: 1.0,
54        green: 1.0,
55        blue: 1.0,
56        alpha: 1.0,
57    };
58
59    /// A fully transparent color.
60    pub const NONE: Self = Self {
61        red: 0.0,
62        green: 0.0,
63        blue: 0.0,
64        alpha: 0.0,
65    };
66
67    /// A fully red color with full alpha.
68    pub const RED: Self = Self {
69        red: 1.0,
70        green: 0.0,
71        blue: 0.0,
72        alpha: 1.0,
73    };
74
75    /// A fully green color with full alpha.
76    pub const GREEN: Self = Self {
77        red: 0.0,
78        green: 1.0,
79        blue: 0.0,
80        alpha: 1.0,
81    };
82
83    /// A fully blue color with full alpha.
84    pub const BLUE: Self = Self {
85        red: 0.0,
86        green: 0.0,
87        blue: 1.0,
88        alpha: 1.0,
89    };
90
91    /// An invalid color.
92    ///
93    /// This type can be used to represent an invalid color value;
94    /// in some rendering applications the color will be ignored,
95    /// enabling performant hacks like hiding lines by setting their color to `INVALID`.
96    pub const NAN: Self = Self {
97        red: f32::NAN,
98        green: f32::NAN,
99        blue: f32::NAN,
100        alpha: f32::NAN,
101    };
102
103    /// Construct a new [`LinearRgba`] color from components.
104    pub const fn new(red: f32, green: f32, blue: f32, alpha: f32) -> Self {
105        Self {
106            red,
107            green,
108            blue,
109            alpha,
110        }
111    }
112
113    /// Construct a new [`LinearRgba`] color from (r, g, b) components, with the default alpha (1.0).
114    ///
115    /// # Arguments
116    ///
117    /// * `red` - Red channel. [0.0, 1.0]
118    /// * `green` - Green channel. [0.0, 1.0]
119    /// * `blue` - Blue channel. [0.0, 1.0]
120    pub const fn rgb(red: f32, green: f32, blue: f32) -> Self {
121        Self {
122            red,
123            green,
124            blue,
125            alpha: 1.0,
126        }
127    }
128
129    /// Return a copy of this color with the red channel set to the given value.
130    pub const fn with_red(self, red: f32) -> Self {
131        Self { red, ..self }
132    }
133
134    /// Return a copy of this color with the green channel set to the given value.
135    pub const fn with_green(self, green: f32) -> Self {
136        Self { green, ..self }
137    }
138
139    /// Return a copy of this color with the blue channel set to the given value.
140    pub const fn with_blue(self, blue: f32) -> Self {
141        Self { blue, ..self }
142    }
143
144    /// Make the color lighter or darker by some amount
145    fn adjust_lightness(&mut self, amount: f32) {
146        let luminance = self.luminance();
147        let target_luminance = (luminance + amount).clamp(0.0, 1.0);
148        if target_luminance < luminance {
149            let adjustment = (luminance - target_luminance) / luminance;
150            self.mix_assign(Self::new(0.0, 0.0, 0.0, self.alpha), adjustment);
151        } else if target_luminance > luminance {
152            let adjustment = (target_luminance - luminance) / (1. - luminance);
153            self.mix_assign(Self::new(1.0, 1.0, 1.0, self.alpha), adjustment);
154        }
155    }
156
157    /// Converts this color to a u32.
158    ///
159    /// Maps the RGBA channels in RGBA order to a little-endian byte array (GPUs are little-endian).
160    /// `A` will be the most significant byte and `R` the least significant.
161    pub fn as_u32(&self) -> u32 {
162        u32::from_le_bytes(self.to_u8_array())
163    }
164}
165
166impl Default for LinearRgba {
167    /// Construct a new [`LinearRgba`] color with the default values (white with full alpha).
168    fn default() -> Self {
169        Self::WHITE
170    }
171}
172
173impl Luminance for LinearRgba {
174    /// Luminance calculated using the [CIE XYZ formula](https://en.wikipedia.org/wiki/Relative_luminance).
175    #[inline]
176    fn luminance(&self) -> f32 {
177        self.red * 0.2126 + self.green * 0.7152 + self.blue * 0.0722
178    }
179
180    #[inline]
181    fn with_luminance(&self, luminance: f32) -> Self {
182        let current_luminance = self.luminance();
183        let adjustment = luminance / current_luminance;
184        Self {
185            red: (self.red * adjustment).clamp(0., 1.),
186            green: (self.green * adjustment).clamp(0., 1.),
187            blue: (self.blue * adjustment).clamp(0., 1.),
188            alpha: self.alpha,
189        }
190    }
191
192    #[inline]
193    fn darker(&self, amount: f32) -> Self {
194        let mut result = *self;
195        result.adjust_lightness(-amount);
196        result
197    }
198
199    #[inline]
200    fn lighter(&self, amount: f32) -> Self {
201        let mut result = *self;
202        result.adjust_lightness(amount);
203        result
204    }
205}
206
207impl Mix for LinearRgba {
208    #[inline]
209    fn mix(&self, other: &Self, factor: f32) -> Self {
210        let n_factor = 1.0 - factor;
211        Self {
212            red: self.red * n_factor + other.red * factor,
213            green: self.green * n_factor + other.green * factor,
214            blue: self.blue * n_factor + other.blue * factor,
215            alpha: self.alpha * n_factor + other.alpha * factor,
216        }
217    }
218}
219
220impl Gray for LinearRgba {
221    const BLACK: Self = Self::BLACK;
222    const WHITE: Self = Self::WHITE;
223}
224
225impl Alpha for LinearRgba {
226    #[inline]
227    fn with_alpha(&self, alpha: f32) -> Self {
228        Self { alpha, ..*self }
229    }
230
231    #[inline]
232    fn alpha(&self) -> f32 {
233        self.alpha
234    }
235
236    #[inline]
237    fn set_alpha(&mut self, alpha: f32) {
238        self.alpha = alpha;
239    }
240}
241
242impl EuclideanDistance for LinearRgba {
243    #[inline]
244    fn distance_squared(&self, other: &Self) -> f32 {
245        let dr = self.red - other.red;
246        let dg = self.green - other.green;
247        let db = self.blue - other.blue;
248        dr * dr + dg * dg + db * db
249    }
250}
251
252impl ColorToComponents for LinearRgba {
253    fn to_f32_array(self) -> [f32; 4] {
254        [self.red, self.green, self.blue, self.alpha]
255    }
256
257    fn to_f32_array_no_alpha(self) -> [f32; 3] {
258        [self.red, self.green, self.blue]
259    }
260
261    fn to_vec4(self) -> Vec4 {
262        Vec4::new(self.red, self.green, self.blue, self.alpha)
263    }
264
265    fn to_vec3(self) -> Vec3 {
266        Vec3::new(self.red, self.green, self.blue)
267    }
268
269    fn from_f32_array(color: [f32; 4]) -> Self {
270        Self {
271            red: color[0],
272            green: color[1],
273            blue: color[2],
274            alpha: color[3],
275        }
276    }
277
278    fn from_f32_array_no_alpha(color: [f32; 3]) -> Self {
279        Self {
280            red: color[0],
281            green: color[1],
282            blue: color[2],
283            alpha: 1.0,
284        }
285    }
286
287    fn from_vec4(color: Vec4) -> Self {
288        Self {
289            red: color[0],
290            green: color[1],
291            blue: color[2],
292            alpha: color[3],
293        }
294    }
295
296    fn from_vec3(color: Vec3) -> Self {
297        Self {
298            red: color[0],
299            green: color[1],
300            blue: color[2],
301            alpha: 1.0,
302        }
303    }
304}
305
306impl ColorToPacked for LinearRgba {
307    fn to_u8_array(self) -> [u8; 4] {
308        [self.red, self.green, self.blue, self.alpha]
309            .map(|v| ops::round(v.clamp(0.0, 1.0) * 255.0) as u8)
310    }
311
312    fn to_u8_array_no_alpha(self) -> [u8; 3] {
313        [self.red, self.green, self.blue].map(|v| ops::round(v.clamp(0.0, 1.0) * 255.0) as u8)
314    }
315
316    fn from_u8_array(color: [u8; 4]) -> Self {
317        Self::from_f32_array(color.map(|u| u as f32 / 255.0))
318    }
319
320    fn from_u8_array_no_alpha(color: [u8; 3]) -> Self {
321        Self::from_f32_array_no_alpha(color.map(|u| u as f32 / 255.0))
322    }
323}
324
325#[cfg(feature = "wgpu-types")]
326impl From<LinearRgba> for wgpu_types::Color {
327    fn from(color: LinearRgba) -> Self {
328        wgpu_types::Color {
329            r: color.red as f64,
330            g: color.green as f64,
331            b: color.blue as f64,
332            a: color.alpha as f64,
333        }
334    }
335}
336
337// [`LinearRgba`] is intended to be used with shaders
338// So it's the only color type that implements [`ShaderType`] to make it easier to use inside shaders
339#[cfg(feature = "encase")]
340impl encase::ShaderType for LinearRgba {
341    type ExtraMetadata = ();
342
343    const METADATA: encase::private::Metadata<Self::ExtraMetadata> = {
344        let size =
345            encase::private::SizeValue::from(<f32 as encase::private::ShaderSize>::SHADER_SIZE)
346                .mul(4);
347        let alignment = encase::private::AlignmentValue::from_next_power_of_two_size(size);
348
349        encase::private::Metadata {
350            alignment,
351            has_uniform_min_alignment: false,
352            is_pod: true,
353            min_size: size,
354            extra: (),
355        }
356    };
357
358    const UNIFORM_COMPAT_ASSERT: fn() = || {};
359}
360
361#[cfg(feature = "encase")]
362impl encase::private::WriteInto for LinearRgba {
363    fn write_into<B: encase::private::BufferMut>(&self, writer: &mut encase::private::Writer<B>) {
364        for el in &[self.red, self.green, self.blue, self.alpha] {
365            encase::private::WriteInto::write_into(el, writer);
366        }
367    }
368}
369
370#[cfg(feature = "encase")]
371impl encase::private::ReadFrom for LinearRgba {
372    fn read_from<B: encase::private::BufferRef>(
373        &mut self,
374        reader: &mut encase::private::Reader<B>,
375    ) {
376        let mut buffer = [0.0f32; 4];
377        for el in &mut buffer {
378            encase::private::ReadFrom::read_from(el, reader);
379        }
380
381        *self = LinearRgba {
382            red: buffer[0],
383            green: buffer[1],
384            blue: buffer[2],
385            alpha: buffer[3],
386        }
387    }
388}
389
390#[cfg(feature = "encase")]
391impl encase::private::CreateFrom for LinearRgba {
392    fn create_from<B>(reader: &mut encase::private::Reader<B>) -> Self
393    where
394        B: encase::private::BufferRef,
395    {
396        // These are intentionally not inlined in the constructor to make this
397        // resilient to internal Color refactors / implicit type changes.
398        let red: f32 = encase::private::CreateFrom::create_from(reader);
399        let green: f32 = encase::private::CreateFrom::create_from(reader);
400        let blue: f32 = encase::private::CreateFrom::create_from(reader);
401        let alpha: f32 = encase::private::CreateFrom::create_from(reader);
402        LinearRgba {
403            red,
404            green,
405            blue,
406            alpha,
407        }
408    }
409}
410
411#[cfg(feature = "encase")]
412impl encase::ShaderSize for LinearRgba {}
413
414#[cfg(test)]
415mod tests {
416    use super::*;
417
418    #[test]
419    fn euclidean_distance() {
420        // White to black
421        let a = LinearRgba::new(0.0, 0.0, 0.0, 1.0);
422        let b = LinearRgba::new(1.0, 1.0, 1.0, 1.0);
423        assert_eq!(a.distance_squared(&b), 3.0);
424
425        // Alpha shouldn't matter
426        let a = LinearRgba::new(0.0, 0.0, 0.0, 1.0);
427        let b = LinearRgba::new(1.0, 1.0, 1.0, 0.0);
428        assert_eq!(a.distance_squared(&b), 3.0);
429
430        // Red to green
431        let a = LinearRgba::new(0.0, 0.0, 0.0, 1.0);
432        let b = LinearRgba::new(1.0, 0.0, 0.0, 1.0);
433        assert_eq!(a.distance_squared(&b), 1.0);
434    }
435
436    #[test]
437    fn to_and_from_u8() {
438        // from_u8_array
439        let a = LinearRgba::from_u8_array([255, 0, 0, 255]);
440        let b = LinearRgba::new(1.0, 0.0, 0.0, 1.0);
441        assert_eq!(a, b);
442
443        // from_u8_array_no_alpha
444        let a = LinearRgba::from_u8_array_no_alpha([255, 255, 0]);
445        let b = LinearRgba::rgb(1.0, 1.0, 0.0);
446        assert_eq!(a, b);
447
448        // to_u8_array
449        let a = LinearRgba::new(0.0, 0.0, 1.0, 1.0).to_u8_array();
450        let b = [0, 0, 255, 255];
451        assert_eq!(a, b);
452
453        // to_u8_array_no_alpha
454        let a = LinearRgba::rgb(0.0, 1.0, 1.0).to_u8_array_no_alpha();
455        let b = [0, 255, 255];
456        assert_eq!(a, b);
457
458        // clamping
459        let a = LinearRgba::rgb(0.0, 100.0, -100.0).to_u8_array_no_alpha();
460        let b = [0, 255, 0];
461        assert_eq!(a, b);
462    }
463
464    #[test]
465    fn darker_lighter() {
466        // Darker and lighter should be commutative.
467        let color = LinearRgba::new(0.4, 0.5, 0.6, 1.0);
468        let darker1 = color.darker(0.1);
469        let darker2 = darker1.darker(0.1);
470        let twice_as_dark = color.darker(0.2);
471        assert!(darker2.distance_squared(&twice_as_dark) < 0.0001);
472
473        let lighter1 = color.lighter(0.1);
474        let lighter2 = lighter1.lighter(0.1);
475        let twice_as_light = color.lighter(0.2);
476        assert!(lighter2.distance_squared(&twice_as_light) < 0.0001);
477    }
478}