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#[doc = include_str!("../docs/conversion.md")]
11#[doc = include_str!("../docs/diagrams/model_graph.svg")]
13#[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 pub red: f32,
25 pub green: f32,
27 pub blue: f32,
29 pub alpha: f32,
31}
32
33impl StandardColor for LinearRgba {}
34
35impl_componentwise_vector_space!(LinearRgba, [red, green, blue, alpha]);
36
37impl LinearRgba {
38 pub const BLACK: Self = Self {
40 red: 0.0,
41 green: 0.0,
42 blue: 0.0,
43 alpha: 1.0,
44 };
45
46 pub const WHITE: Self = Self {
48 red: 1.0,
49 green: 1.0,
50 blue: 1.0,
51 alpha: 1.0,
52 };
53
54 pub const NONE: Self = Self {
56 red: 0.0,
57 green: 0.0,
58 blue: 0.0,
59 alpha: 0.0,
60 };
61
62 pub const RED: Self = Self {
64 red: 1.0,
65 green: 0.0,
66 blue: 0.0,
67 alpha: 1.0,
68 };
69
70 pub const GREEN: Self = Self {
72 red: 0.0,
73 green: 1.0,
74 blue: 0.0,
75 alpha: 1.0,
76 };
77
78 pub const BLUE: Self = Self {
80 red: 0.0,
81 green: 0.0,
82 blue: 1.0,
83 alpha: 1.0,
84 };
85
86 pub const NAN: Self = Self {
92 red: f32::NAN,
93 green: f32::NAN,
94 blue: f32::NAN,
95 alpha: f32::NAN,
96 };
97
98 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 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 pub const fn with_red(self, red: f32) -> Self {
126 Self { red, ..self }
127 }
128
129 pub const fn with_green(self, green: f32) -> Self {
131 Self { green, ..self }
132 }
133
134 pub const fn with_blue(self, blue: f32) -> Self {
136 Self { blue, ..self }
137 }
138
139 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 pub fn as_u32(&self) -> u32 {
157 u32::from_le_bytes(self.to_u8_array())
158 }
159}
160
161impl Default for LinearRgba {
162 fn default() -> Self {
164 Self::WHITE
165 }
166}
167
168impl Luminance for LinearRgba {
169 #[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
332impl 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 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 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 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 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 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 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 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 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 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 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}