1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
use bytemuck::{Pod, Zeroable};
/// A red, green, blue, and opacity color, each a linear value the engine
/// encodes to sRGB on its way to the window.
///
/// Nothing clamps a channel: a value past `1.0` is light the frame holds
/// and the tone map brings down, which is what an emissive material or a
/// bright light is written with. `0.0..=1.0` is what the window can show.
///
/// Values are linear; the engine writes sRGB-encoded values to the screen.
/// A color channel past `1.0` holds light no screen draws, which
/// [`FrameContext::set_bloom`](crate::FrameContext::set_bloom) spreads.
#[repr(C)]
#[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable)]
pub struct Color {
/// Red.
pub red: f32,
/// Green.
pub green: f32,
/// Blue.
pub blue: f32,
/// Opacity: `1.0` is fully opaque, `0.0` is empty.
pub alpha: f32,
}
impl Color {
/// Opaque black.
pub const BLACK: Self = Self::rgb(0.0, 0.0, 0.0);
/// Opaque white.
pub const WHITE: Self = Self::rgb(1.0, 1.0, 1.0);
/// An opaque color, each channel a fraction of `1.0`.
pub const fn rgb(red: f32, green: f32, blue: f32) -> Self {
Self::rgba(red, green, blue, 1.0)
}
/// A color with opacity `alpha`, each channel and `alpha` a fraction of
/// `1.0`.
pub const fn rgba(red: f32, green: f32, blue: f32, alpha: f32) -> Self {
Self {
red,
green,
blue,
alpha,
}
}
/// The same channels at opacity `alpha`, a fraction of `1.0`.
pub const fn with_alpha(self, alpha: f32) -> Self {
Self { alpha, ..self }
}
/// The color three sRGB-encoded bytes hold, red, green and blue, fully
/// opaque: what a texture's own texels are read back as.
pub(crate) fn of_srgb(texel: [u8; 3]) -> Self {
let linear = |byte: u8| {
let encoded = f32::from(byte) / 255.0;
match encoded <= 0.040_45 {
true => encoded / 12.92,
false => ((encoded + 0.055) / 1.055).powf(2.4),
}
};
Self::rgb(linear(texel[0]), linear(texel[1]), linear(texel[2]))
}
/// Its red, green, and blue scaled by `factor`, a fraction of each
/// channel's own value, keeping its opacity.
pub const fn dimmed(self, factor: f32) -> Self {
Self {
red: self.red * factor,
green: self.green * factor,
blue: self.blue * factor,
alpha: self.alpha,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_color_keeps_its_channels_through_an_opacity_change_and_its_opacity_through_a_dim() {
let color = Color::rgba(0.4, 0.6, 0.8, 0.5);
assert_eq!(color.with_alpha(0.25), Color::rgba(0.4, 0.6, 0.8, 0.25));
assert_eq!(color.dimmed(0.5), Color::rgba(0.2, 0.3, 0.4, 0.5));
}
}