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#[doc = include_str!("../docs/conversion.md")]
14#[doc = include_str!("../docs/diagrams/model_graph.svg")]
16#[derive(Debug, Clone, Copy, PartialEq, Pod, Zeroable)]
18#[cfg_attr(
19 feature = "bevy_reflect",
20 derive(Reflect),
21 reflect(Clone, PartialEq, Default)
22)]
23#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
24#[cfg_attr(
25 all(feature = "serialize", feature = "bevy_reflect"),
26 reflect(Serialize, Deserialize)
27)]
28#[repr(C)]
29pub struct LinearRgba {
30 pub red: f32,
32 pub green: f32,
34 pub blue: f32,
36 pub alpha: f32,
38}
39
40impl StandardColor for LinearRgba {}
41
42impl_componentwise_vector_space!(LinearRgba, [red, green, blue, alpha]);
43
44impl LinearRgba {
45 pub const BLACK: Self = Self {
47 red: 0.0,
48 green: 0.0,
49 blue: 0.0,
50 alpha: 1.0,
51 };
52
53 pub const WHITE: Self = Self {
55 red: 1.0,
56 green: 1.0,
57 blue: 1.0,
58 alpha: 1.0,
59 };
60
61 pub const NONE: Self = Self {
63 red: 0.0,
64 green: 0.0,
65 blue: 0.0,
66 alpha: 0.0,
67 };
68
69 pub const RED: Self = Self {
71 red: 1.0,
72 green: 0.0,
73 blue: 0.0,
74 alpha: 1.0,
75 };
76
77 pub const GREEN: Self = Self {
79 red: 0.0,
80 green: 1.0,
81 blue: 0.0,
82 alpha: 1.0,
83 };
84
85 pub const BLUE: Self = Self {
87 red: 0.0,
88 green: 0.0,
89 blue: 1.0,
90 alpha: 1.0,
91 };
92
93 pub const NAN: Self = Self {
99 red: f32::NAN,
100 green: f32::NAN,
101 blue: f32::NAN,
102 alpha: f32::NAN,
103 };
104
105 pub const fn new(red: f32, green: f32, blue: f32, alpha: f32) -> Self {
107 Self {
108 red,
109 green,
110 blue,
111 alpha,
112 }
113 }
114
115 pub const fn rgb(red: f32, green: f32, blue: f32) -> Self {
123 Self {
124 red,
125 green,
126 blue,
127 alpha: 1.0,
128 }
129 }
130
131 pub const fn with_red(self, red: f32) -> Self {
133 Self { red, ..self }
134 }
135
136 pub const fn with_green(self, green: f32) -> Self {
138 Self { green, ..self }
139 }
140
141 pub const fn with_blue(self, blue: f32) -> Self {
143 Self { blue, ..self }
144 }
145
146 fn adjust_lightness(&mut self, amount: f32) {
148 let luminance = self.luminance();
149 let target_luminance = (luminance + amount).clamp(0.0, 1.0);
150 if target_luminance < luminance {
151 let adjustment = (luminance - target_luminance) / luminance;
152 self.mix_assign(Self::new(0.0, 0.0, 0.0, self.alpha), adjustment);
153 } else if target_luminance > luminance {
154 let adjustment = (target_luminance - luminance) / (1. - luminance);
155 self.mix_assign(Self::new(1.0, 1.0, 1.0, self.alpha), adjustment);
156 }
157 }
158
159 pub fn as_u32(&self) -> u32 {
164 u32::from_le_bytes(self.to_u8_array())
165 }
166}
167
168impl Default for LinearRgba {
169 fn default() -> Self {
171 Self::WHITE
172 }
173}
174
175impl Luminance for LinearRgba {
176 #[inline]
178 fn luminance(&self) -> f32 {
179 self.red * 0.2126 + self.green * 0.7152 + self.blue * 0.0722
180 }
181
182 #[inline]
185 fn with_luminance(&self, luminance: f32) -> Self {
186 let current_luminance = self.luminance();
187 let adjustment = luminance / current_luminance;
188 Self {
189 red: self.red * adjustment,
190 green: self.green * adjustment,
191 blue: self.blue * adjustment,
192 alpha: self.alpha,
193 }
194 }
195
196 #[inline]
199 fn darker(&self, amount: f32) -> Self {
200 let mut result = *self;
201 result.adjust_lightness(-amount);
202 result
203 }
204
205 #[inline]
208 fn lighter(&self, amount: f32) -> Self {
209 let mut result = *self;
210 result.adjust_lightness(amount);
211 result
212 }
213}
214
215impl Mix for LinearRgba {
216 #[inline]
217 fn mix(&self, other: &Self, factor: f32) -> Self {
218 let n_factor = 1.0 - factor;
219 Self {
220 red: self.red * n_factor + other.red * factor,
221 green: self.green * n_factor + other.green * factor,
222 blue: self.blue * n_factor + other.blue * factor,
223 alpha: self.alpha * n_factor + other.alpha * factor,
224 }
225 }
226}
227
228impl Gray for LinearRgba {
229 const BLACK: Self = Self::BLACK;
230 const WHITE: Self = Self::WHITE;
231}
232
233impl Alpha for LinearRgba {
234 #[inline]
235 fn with_alpha(&self, alpha: f32) -> Self {
236 Self { alpha, ..*self }
237 }
238
239 #[inline]
240 fn alpha(&self) -> f32 {
241 self.alpha
242 }
243
244 #[inline]
245 fn set_alpha(&mut self, alpha: f32) {
246 self.alpha = alpha;
247 }
248}
249
250impl EuclideanDistance for LinearRgba {
251 #[inline]
252 fn distance_squared(&self, other: &Self) -> f32 {
253 let dr = self.red - other.red;
254 let dg = self.green - other.green;
255 let db = self.blue - other.blue;
256 dr * dr + dg * dg + db * db
257 }
258}
259
260impl ColorToComponents for LinearRgba {
261 fn to_f32_array(self) -> [f32; 4] {
262 [self.red, self.green, self.blue, self.alpha]
263 }
264
265 fn to_f32_array_no_alpha(self) -> [f32; 3] {
266 [self.red, self.green, self.blue]
267 }
268
269 fn to_vec4(self) -> Vec4 {
270 Vec4::new(self.red, self.green, self.blue, self.alpha)
271 }
272
273 fn to_vec3(self) -> Vec3 {
274 Vec3::new(self.red, self.green, self.blue)
275 }
276
277 fn from_f32_array(color: [f32; 4]) -> Self {
278 Self {
279 red: color[0],
280 green: color[1],
281 blue: color[2],
282 alpha: color[3],
283 }
284 }
285
286 fn from_f32_array_no_alpha(color: [f32; 3]) -> Self {
287 Self {
288 red: color[0],
289 green: color[1],
290 blue: color[2],
291 alpha: 1.0,
292 }
293 }
294
295 fn from_vec4(color: Vec4) -> Self {
296 Self {
297 red: color[0],
298 green: color[1],
299 blue: color[2],
300 alpha: color[3],
301 }
302 }
303
304 fn from_vec3(color: Vec3) -> Self {
305 Self {
306 red: color[0],
307 green: color[1],
308 blue: color[2],
309 alpha: 1.0,
310 }
311 }
312}
313
314impl ColorToPacked for LinearRgba {
315 fn to_u8_array(self) -> [u8; 4] {
316 [self.red, self.green, self.blue, self.alpha]
317 .map(|v| ops::round(v.clamp(0.0, 1.0) * 255.0) as u8)
318 }
319
320 fn to_u8_array_no_alpha(self) -> [u8; 3] {
321 [self.red, self.green, self.blue].map(|v| ops::round(v.clamp(0.0, 1.0) * 255.0) as u8)
322 }
323
324 fn from_u8_array(color: [u8; 4]) -> Self {
325 Self::from_f32_array(color.map(|u| u as f32 / 255.0))
326 }
327
328 fn from_u8_array_no_alpha(color: [u8; 3]) -> Self {
329 Self::from_f32_array_no_alpha(color.map(|u| u as f32 / 255.0))
330 }
331}
332
333#[cfg(feature = "wgpu-types")]
334impl From<LinearRgba> for wgpu_types::Color {
335 fn from(color: LinearRgba) -> Self {
336 wgpu_types::Color {
337 r: color.red as f64,
338 g: color.green as f64,
339 b: color.blue as f64,
340 a: color.alpha as f64,
341 }
342 }
343}
344
345#[cfg(feature = "encase")]
348impl encase::ShaderType for LinearRgba {
349 type ExtraMetadata = ();
350
351 const METADATA: encase::private::Metadata<Self::ExtraMetadata> = {
352 let size =
353 encase::private::SizeValue::from(<f32 as encase::private::ShaderSize>::SHADER_SIZE)
354 .mul(4);
355 let alignment = encase::private::AlignmentValue::from_next_power_of_two_size(size);
356
357 encase::private::Metadata {
358 alignment,
359 has_uniform_min_alignment: false,
360 is_pod: true,
361 min_size: size,
362 extra: (),
363 }
364 };
365
366 const UNIFORM_COMPAT_ASSERT: fn() = || {};
367}
368
369#[cfg(feature = "encase")]
370impl encase::private::WriteInto for LinearRgba {
371 fn write_into<B: encase::private::BufferMut>(&self, writer: &mut encase::private::Writer<B>) {
372 for el in &[self.red, self.green, self.blue, self.alpha] {
373 encase::private::WriteInto::write_into(el, writer);
374 }
375 }
376}
377
378#[cfg(feature = "encase")]
379impl encase::private::ReadFrom for LinearRgba {
380 fn read_from<B: encase::private::BufferRef>(
381 &mut self,
382 reader: &mut encase::private::Reader<B>,
383 ) {
384 let mut buffer = [0.0f32; 4];
385 for el in &mut buffer {
386 encase::private::ReadFrom::read_from(el, reader);
387 }
388
389 *self = LinearRgba {
390 red: buffer[0],
391 green: buffer[1],
392 blue: buffer[2],
393 alpha: buffer[3],
394 }
395 }
396}
397
398#[cfg(feature = "encase")]
399impl encase::private::CreateFrom for LinearRgba {
400 fn create_from<B>(reader: &mut encase::private::Reader<B>) -> Self
401 where
402 B: encase::private::BufferRef,
403 {
404 let red: f32 = encase::private::CreateFrom::create_from(reader);
407 let green: f32 = encase::private::CreateFrom::create_from(reader);
408 let blue: f32 = encase::private::CreateFrom::create_from(reader);
409 let alpha: f32 = encase::private::CreateFrom::create_from(reader);
410 LinearRgba {
411 red,
412 green,
413 blue,
414 alpha,
415 }
416 }
417}
418
419#[cfg(feature = "encase")]
420impl encase::ShaderSize for LinearRgba {}
421
422#[cfg(test)]
423mod tests {
424 use super::*;
425
426 #[test]
427 fn euclidean_distance() {
428 let a = LinearRgba::new(0.0, 0.0, 0.0, 1.0);
430 let b = LinearRgba::new(1.0, 1.0, 1.0, 1.0);
431 assert_eq!(a.distance_squared(&b), 3.0);
432
433 let a = LinearRgba::new(0.0, 0.0, 0.0, 1.0);
435 let b = LinearRgba::new(1.0, 1.0, 1.0, 0.0);
436 assert_eq!(a.distance_squared(&b), 3.0);
437
438 let a = LinearRgba::new(0.0, 0.0, 0.0, 1.0);
440 let b = LinearRgba::new(1.0, 0.0, 0.0, 1.0);
441 assert_eq!(a.distance_squared(&b), 1.0);
442 }
443
444 #[test]
445 fn to_and_from_u8() {
446 let a = LinearRgba::from_u8_array([255, 0, 0, 255]);
448 let b = LinearRgba::new(1.0, 0.0, 0.0, 1.0);
449 assert_eq!(a, b);
450
451 let a = LinearRgba::from_u8_array_no_alpha([255, 255, 0]);
453 let b = LinearRgba::rgb(1.0, 1.0, 0.0);
454 assert_eq!(a, b);
455
456 let a = LinearRgba::new(0.0, 0.0, 1.0, 1.0).to_u8_array();
458 let b = [0, 0, 255, 255];
459 assert_eq!(a, b);
460
461 let a = LinearRgba::rgb(0.0, 1.0, 1.0).to_u8_array_no_alpha();
463 let b = [0, 255, 255];
464 assert_eq!(a, b);
465
466 let a = LinearRgba::rgb(0.0, 100.0, -100.0).to_u8_array_no_alpha();
468 let b = [0, 255, 0];
469 assert_eq!(a, b);
470 }
471
472 #[test]
473 fn with_luminance_scales_unclamped() {
474 use crate::testing::assert_approx_eq;
475
476 let gray = LinearRgba::rgb(0.5, 0.5, 0.5);
478 let bright = gray.with_luminance(2.0);
479 assert_approx_eq!(bright.red, 2.0, 1e-4);
480 assert_approx_eq!(bright.green, 2.0, 1e-4);
481 assert_approx_eq!(bright.blue, 2.0, 1e-4);
482
483 let hdr = LinearRgba::rgb(2.0, 4.0, 8.0);
485 let adjusted = hdr.with_luminance(2.0 * hdr.luminance());
486 assert_approx_eq!(adjusted.red, 4.0, 1e-4);
487 assert_approx_eq!(adjusted.green, 8.0, 1e-4);
488 assert_approx_eq!(adjusted.blue, 16.0, 1e-4);
489
490 let red = LinearRgba::rgb(1.0, 0.0, 0.0);
493 let bright = red.with_luminance(0.9);
494 assert!(bright.red > 4.0);
495 assert_eq!(bright.green, 0.0);
496 assert_eq!(bright.blue, 0.0);
497 assert_approx_eq!(bright.luminance(), 0.9, 1e-4);
498 }
499
500 #[test]
501 fn darker_lighter() {
502 let color = LinearRgba::new(0.4, 0.5, 0.6, 1.0);
504 let darker1 = color.darker(0.1);
505 let darker2 = darker1.darker(0.1);
506 let twice_as_dark = color.darker(0.2);
507 assert!(darker2.distance_squared(&twice_as_dark) < 0.0001);
508
509 let lighter1 = color.lighter(0.1);
510 let lighter2 = lighter1.lighter(0.1);
511 let twice_as_light = color.lighter(0.2);
512 assert!(lighter2.distance_squared(&twice_as_light) < 0.0001);
513 }
514}