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