Skip to main content

cranpose_ui_graphics/
color.rs

1//! Color representation and color space utilities
2
3/// An RGBA color in the sRGB space. The four fields are red, green, blue and alpha, each from 0.0
4/// to 1.0. Compose's `Color`.
5#[derive(Clone, Copy, Debug, PartialEq)]
6pub struct Color(pub f32, pub f32, pub f32, pub f32);
7
8impl Color {
9    /// Creates an opaque color from red, green and blue channels, each from 0.0 to 1.0.
10    pub const fn rgb(r: f32, g: f32, b: f32) -> Self {
11        Self(r, g, b, 1.0)
12    }
13
14    /// Creates a color from red, green, blue and alpha channels, each from 0.0 to 1.0.
15    pub const fn rgba(r: f32, g: f32, b: f32, a: f32) -> Self {
16        Self(r, g, b, a)
17    }
18
19    /// Creates a color from red, green, blue and alpha channels of 0 to 255.
20    pub const fn from_rgba_u8(r: u8, g: u8, b: u8, a: u8) -> Self {
21        Self(
22            r as f32 / 255.0,
23            g as f32 / 255.0,
24            b as f32 / 255.0,
25            a as f32 / 255.0,
26        )
27    }
28
29    /// Creates an opaque color from red, green and blue channels of 0 to 255.
30    pub const fn from_rgb_u8(r: u8, g: u8, b: u8) -> Self {
31        Self::from_rgba_u8(r, g, b, 255)
32    }
33
34    /// Red channel, from 0.0 to 1.0.
35    pub fn r(&self) -> f32 {
36        self.0
37    }
38
39    /// Green channel, from 0.0 to 1.0.
40    pub fn g(&self) -> f32 {
41        self.1
42    }
43
44    /// Blue channel, from 0.0 to 1.0.
45    pub fn b(&self) -> f32 {
46        self.2
47    }
48
49    /// Alpha (opacity) channel, from 0.0 (transparent) to 1.0 (opaque).
50    pub fn a(&self) -> f32 {
51        self.3
52    }
53
54    /// Returns this color with its alpha channel replaced by `alpha`, from 0.0 (transparent) to 1.0
55    /// (opaque).
56    pub fn with_alpha(&self, alpha: f32) -> Self {
57        Self(self.0, self.1, self.2, alpha)
58    }
59
60    /// This colour as the platform's colour type actually holds it: **eight
61    /// bits per channel**.
62    ///
63    /// `androidx.compose.ui.graphics.Color` is a float-shaped API over an
64    /// 8-bit value. Building one in the sRGB space snaps every channel on the
65    /// spot — the bytecode of `ColorKt.Color(float, float, float, float,
66    /// ColorSpace)` is `coerceIn(0f, 1f)`, `* 255.0f`, `+ 0.5f`, `f2i`, packed
67    /// into an ARGB int — so a colour an app computes in float is already a
68    /// whole channel value before anything paints with it, and the rasterizer
69    /// never sees the fraction.
70    ///
71    /// A renderer that carries the fraction to the framebuffer instead leaves
72    /// the rounding to whatever converts float to unorm there. That agrees
73    /// nearly everywhere and disagrees on an exact half. It is not a rare
74    /// shape: a theme that lerps between two byte colours lands on one
75    /// routinely — `mix(rail, background, 0.55)` puts a Wear settings capsule
76    /// at exactly 22.5/255 on green, where this rule gives 23 and a converter
77    /// that breaks ties to even gives 22. One level, and then the row's layer
78    /// alpha multiplies it and keeps it.
79    ///
80    /// Ties go **up**, which is what `(int)(x + 0.5f)`, Skia's
81    /// `SkScalarRoundToInt` and Rust's `f32::round` all give for a channel
82    /// clamped into 0..=1.
83    pub fn srgb_8bit(self) -> Self {
84        Self(
85            srgb_channel_8bit(self.0),
86            srgb_channel_8bit(self.1),
87            srgb_channel_8bit(self.2),
88            srgb_channel_8bit(self.3),
89        )
90    }
91
92    /// Opaque black.
93    pub const BLACK: Color = Color(0.0, 0.0, 0.0, 1.0);
94    /// Opaque white.
95    pub const WHITE: Color = Color(1.0, 1.0, 1.0, 1.0);
96    /// Opaque red.
97    pub const RED: Color = Color(1.0, 0.0, 0.0, 1.0);
98    /// Opaque green.
99    pub const GREEN: Color = Color(0.0, 1.0, 0.0, 1.0);
100    /// Opaque blue.
101    pub const BLUE: Color = Color(0.0, 0.0, 1.0, 1.0);
102    /// Fully transparent black, with every channel 0.0.
103    pub const TRANSPARENT: Color = Color(0.0, 0.0, 0.0, 0.0);
104}
105
106/// One channel snapped to the 8-bit value an sRGB colour holds.
107fn srgb_channel_8bit(channel: f32) -> f32 {
108    (channel.clamp(0.0, 1.0) * 255.0).round() / 255.0
109}
110
111#[cfg(test)]
112#[path = "tests/color_tests.rs"]
113mod tests;