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")]
12#[doc = include_str!("../docs/diagrams/model_graph.svg")]
14#[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 pub red: f32,
30 pub green: f32,
32 pub blue: f32,
34 pub alpha: f32,
36}
37
38impl StandardColor for LinearRgba {}
39
40impl_componentwise_vector_space!(LinearRgba, [red, green, blue, alpha]);
41
42impl LinearRgba {
43 pub const BLACK: Self = Self {
45 red: 0.0,
46 green: 0.0,
47 blue: 0.0,
48 alpha: 1.0,
49 };
50
51 pub const WHITE: Self = Self {
53 red: 1.0,
54 green: 1.0,
55 blue: 1.0,
56 alpha: 1.0,
57 };
58
59 pub const NONE: Self = Self {
61 red: 0.0,
62 green: 0.0,
63 blue: 0.0,
64 alpha: 0.0,
65 };
66
67 pub const RED: Self = Self {
69 red: 1.0,
70 green: 0.0,
71 blue: 0.0,
72 alpha: 1.0,
73 };
74
75 pub const GREEN: Self = Self {
77 red: 0.0,
78 green: 1.0,
79 blue: 0.0,
80 alpha: 1.0,
81 };
82
83 pub const BLUE: Self = Self {
85 red: 0.0,
86 green: 0.0,
87 blue: 1.0,
88 alpha: 1.0,
89 };
90
91 pub const NAN: Self = Self {
97 red: f32::NAN,
98 green: f32::NAN,
99 blue: f32::NAN,
100 alpha: f32::NAN,
101 };
102
103 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 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 pub const fn with_red(self, red: f32) -> Self {
131 Self { red, ..self }
132 }
133
134 pub const fn with_green(self, green: f32) -> Self {
136 Self { green, ..self }
137 }
138
139 pub const fn with_blue(self, blue: f32) -> Self {
141 Self { blue, ..self }
142 }
143
144 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 pub fn as_u32(&self) -> u32 {
162 u32::from_le_bytes(self.to_u8_array())
163 }
164}
165
166impl Default for LinearRgba {
167 fn default() -> Self {
169 Self::WHITE
170 }
171}
172
173impl Luminance for LinearRgba {
174 #[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#[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 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 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 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 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 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 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 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 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 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 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}