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}