relative_luminance/
lib.rs

1//! This crate helps calculate [*relative* luminance][relative-luminance] values for
2//! colors. This can help determine if a color *looks* light or dark, which may be
3//! different from if a color is mathematically light or dark. For example, even bright
4//! blue (`#0000FF`) can appear to be dark to many people.
5//!
6//! # Examples
7//!
8//! ```
9//! use relative_luminance::{Luminance, Rgb};
10//!
11//! let black: Rgb<f32> = Rgb { r: 0.0, g: 0.0, b: 0.0 };
12//! let white: Rgb<f32> = Rgb { r: 1.0, g: 1.0, b: 1.0 };
13//! let green: Rgb<f32> = Rgb { r: 0.0, g: 1.0, b: 0.0 };
14//! let blue: Rgb<f32> = Rgb { r: 0.0, g: 0.0, b: 1.0 };
15//!
16//! // If luminance is above 0.5, it can be considered bright.
17//! assert_eq!(black.relative_luminance(), 0.0);
18//! assert_eq!(white.relative_luminance(), 1.0);
19//! assert!(green.relative_luminance() > 0.5);
20//! assert!(blue.relative_luminance() < 0.5);
21//! ```
22//!
23//! ```
24//! use relative_luminance::{Luminance, Rgb};
25//!
26//! /// RGB channels in the range [0, 255].
27//! struct MyRgb {
28//!     r: u8,
29//!     g: u8,
30//!     b: u8,
31//! }
32//!
33//! impl Luminance<f32> for MyRgb {
34//!     fn luminance_rgb(&self) -> Rgb<f32> {
35//!         Rgb {
36//!             // Normalizing color channels to the range [0.0, 1.0]
37//!             r: f32::from(self.r) / 255.0,
38//!             g: f32::from(self.g) / 255.0,
39//!             b: f32::from(self.b) / 255.0,
40//!         }
41//!     }
42//! }
43//!
44//! let black = MyRgb { r: 0, g: 0, b: 0 };
45//! let white = MyRgb { r: 255, g: 255, b: 255 };
46//!
47//! assert_eq!(black.relative_luminance(), 0.0);
48//! assert_eq!(white.relative_luminance(), 1.0);
49//! ```
50//!
51//! [relative-luminance]: https://en.wikipedia.org/wiki/Relative_luminance
52use core::ops::{Add, Mul};
53/// This trait is used to define numerical types that can be used to calculate relative
54/// luminance values.
55///
56/// ```ignore
57/// impl LuminanceValue for MyFloatLikeType {
58///     type Channel = MyFloatLikeType;
59///     type Weight = MyFloatLikeType;
60///     type Weighted = MyFloatLikeType;
61///
62///     const RED_WEIGHT: MyFloatLikeType = 0.2126;
63///     const GREEN_WEIGHT: MyFloatLikeType = 0.7152;
64///     const BLUE_WEIGHT: MyFloatLikeType = 0.0722;
65/// }
66/// ```
67///
68/// The associated types can help with custom implementations where you need to mix
69/// different types for precision.
70pub trait LuminanceValue: Copy + Clone {
71    /// The type used for RGB channels.
72    ///
73    /// ```ignore
74    /// let r: Self::Channel = 1.0;
75    ///
76    /// let weighted_r = r * RED_WEIGHT;
77    /// ```
78    type Channel: Copy + Mul<Self::Weight, Output = Self::Weighted>;
79    /// The type used for modifying the channel's value.
80    ///
81    /// ```ignore
82    /// const RED_WEIGHT: Weight = 0.2126;
83    ///
84    /// let weighted_r = r * RED_WEIGHT;
85    /// ```
86    type Weight;
87    /// The numerical type of the weighted channel.
88    ///
89    /// ```ignore
90    /// let weighted: Weighted = r * RED_WEIGHT;
91    /// ```
92    type Weighted: Add<Self::Weighted, Output = Self::Weighted>;
93    /// The modifier for the red channel. If the channel is within [0.0, 1.0], this
94    /// value should be 0.2126.
95    const RED_WEIGHT: Self::Weight;
96    /// The modifier for the green channel. If the channel is within [0.0, 1.0], this
97    /// value should be 0.7152.
98    const GREEN_WEIGHT: Self::Weight;
99    /// The modifier for the blue channel. If the channel is within [0.0, 1.0], this
100    /// value should be 0.0722.
101    const BLUE_WEIGHT: Self::Weight;
102}
103
104/// Gets the relative luminance of RGB channels.
105fn relative_luminance<T: LuminanceValue>(
106    r: T::Channel,
107    g: T::Channel,
108    b: T::Channel,
109) -> T::Weighted {
110    (r * T::RED_WEIGHT) + (g * T::GREEN_WEIGHT) + (b * T::BLUE_WEIGHT)
111}
112
113impl LuminanceValue for f32 {
114    type Channel = f32;
115    type Weight = f32;
116    type Weighted = f32;
117    const RED_WEIGHT: f32 = 0.2126;
118    const GREEN_WEIGHT: f32 = 0.7152;
119    const BLUE_WEIGHT: f32 = 0.0722;
120}
121
122impl LuminanceValue for f64 {
123    type Channel = f64;
124    type Weight = f64;
125    type Weighted = f64;
126    const RED_WEIGHT: f64 = 0.2126;
127    const GREEN_WEIGHT: f64 = 0.7152;
128    const BLUE_WEIGHT: f64 = 0.0722;
129}
130
131/// Struct for containing RGB channels that can be used for calculating luminance.
132///
133/// ```
134/// # use relative_luminance::Rgb;
135/// let green: Rgb<f32> = Rgb {
136///     r: 0.0,
137///     g: 1.0,
138///     b: 0.0,
139/// };
140/// ```
141#[derive(Clone, Copy, Debug)]
142pub struct Rgb<T: LuminanceValue> {
143    pub r: T::Channel,
144    pub g: T::Channel,
145    pub b: T::Channel,
146}
147
148impl<T: LuminanceValue> Rgb<T> {
149    /// Creates a new `Rgb<T>`
150    pub fn new(r: T::Channel, g: T::Channel, b: T::Channel) -> Self {
151        Rgb { r, g, b }
152    }
153    /// Gets the relative luminance of the color.
154    fn relative_luminance(&self) -> T::Weighted {
155        relative_luminance::<T>(self.r, self.g, self.b)
156    }
157}
158
159/// Implement this trait on your color type to provide relative luminance calculations.
160///
161/// ```
162/// # use relative_luminance::Luminance;
163///
164/// /// RGB channels in the range [0, 255].
165/// struct MyRgb {
166///     r: u8,
167///     g: u8,
168///     b: u8,
169/// }
170///
171/// impl Luminance<f32> for MyRgb {
172///     fn luminance_rgb(&self) -> relative_luminance::Rgb<f32> {
173///         relative_luminance::Rgb {
174///             r: f32::from(self.r) / 255.0,
175///             g: f32::from(self.g) / 255.0,
176///             b: f32::from(self.b) / 255.0,
177///         }
178///     }
179/// }
180///
181/// let black = MyRgb { r: 0, g: 0, b: 0 };
182/// let white = MyRgb { r: 255, g: 255, b: 255 };
183///
184/// assert_eq!(black.relative_luminance(), 0.0);
185/// assert_eq!(white.relative_luminance(), 1.0);
186/// ```
187pub trait Luminance<T: LuminanceValue> {
188    fn luminance_rgb(&self) -> Rgb<T>;
189
190    fn relative_luminance(&self) -> T::Weighted {
191        self.luminance_rgb().relative_luminance()
192    }
193}
194
195impl<T: LuminanceValue> Luminance<T> for Rgb<T> {
196    fn luminance_rgb(&self) -> Rgb<T> {
197        *self
198    }
199
200    fn relative_luminance(&self) -> T::Weighted {
201        // NOTE Small optimization to avoid cloning
202        Rgb::relative_luminance(self)
203    }
204}
205
206#[cfg(test)]
207mod tests {
208    use super::*;
209
210    #[test]
211    fn test_rgb_trail_impl_equals_struct_impl() {
212        let rgb = Rgb::<f32>::new(0.5, 0.5, 0.5);
213        assert_eq!(
214            Rgb::relative_luminance(&rgb),
215            Luminance::relative_luminance(&rgb)
216        );
217    }
218}