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#[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(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 pub red: f32,
26 pub green: f32,
28 pub blue: f32,
30 pub alpha: f32,
32}
33
34impl StandardColor for LinearRgba {}
35
36impl_componentwise_vector_space!(LinearRgba, [red, green, blue, alpha]);
37
38impl LinearRgba {
39 pub const BLACK: Self = Self {
41 red: 0.0,
42 green: 0.0,
43 blue: 0.0,
44 alpha: 1.0,
45 };
46
47 pub const WHITE: Self = Self {
49 red: 1.0,
50 green: 1.0,
51 blue: 1.0,
52 alpha: 1.0,
53 };
54
55 pub const NONE: Self = Self {
57 red: 0.0,
58 green: 0.0,
59 blue: 0.0,
60 alpha: 0.0,
61 };
62
63 pub const RED: Self = Self {
65 red: 1.0,
66 green: 0.0,
67 blue: 0.0,
68 alpha: 1.0,
69 };
70
71 pub const GREEN: Self = Self {
73 red: 0.0,
74 green: 1.0,
75 blue: 0.0,
76 alpha: 1.0,
77 };
78
79 pub const BLUE: Self = Self {
81 red: 0.0,
82 green: 0.0,
83 blue: 1.0,
84 alpha: 1.0,
85 };
86
87 pub const NAN: Self = Self {
93 red: f32::NAN,
94 green: f32::NAN,
95 blue: f32::NAN,
96 alpha: f32::NAN,
97 };
98
99 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 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 pub const fn with_red(self, red: f32) -> Self {
127 Self { red, ..self }
128 }
129
130 pub const fn with_green(self, green: f32) -> Self {
132 Self { green, ..self }
133 }
134
135 pub const fn with_blue(self, blue: f32) -> Self {
137 Self { blue, ..self }
138 }
139
140 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 pub fn as_u32(&self) -> u32 {
158 u32::from_le_bytes(self.to_u8_array())
159 }
160}
161
162impl Default for LinearRgba {
163 fn default() -> Self {
165 Self::WHITE
166 }
167}
168
169impl Luminance for LinearRgba {
170 #[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
333impl 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 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 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 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 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 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 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 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 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 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 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}