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