Skip to main content

prisma/
rgb.rs

1//! The RGB device-dependent color model.
2//!
3//! Provides the [Rgb<T>](struct.Rgb.html) type.
4
5use crate::channel::{
6    AngularChannelScalar, ChannelCast, ChannelFormatCast, ColorChannel, PosNormalBoundedChannel,
7    PosNormalChannelScalar,
8};
9use crate::chromaticity::ChromaticityCoordinates;
10use crate::color;
11use crate::color::{Broadcast, Color, FromTuple, HomogeneousColor};
12use crate::convert;
13use crate::encoding::EncodableColor;
14use crate::hsl;
15use crate::hsv;
16use crate::hwb;
17use crate::tags::RgbTag;
18use angle;
19#[cfg(feature = "approx")]
20use approx;
21use num_traits;
22use num_traits::cast;
23use std::fmt;
24use std::mem;
25use std::slice;
26
27#[repr(C)]
28#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash)]
29/// The `Rgb` device-dependent cartesian color model.
30///
31/// `Rgb<T>` has three primaries: red, green blue, which are always positive and in the normalized
32/// range `[0, 1]`. `Rgb<T>` accepts both integer and float components.
33///
34/// It is made to be efficient and easy to use in many different applications, and can be
35/// transmuted directly to a `&[T; N]`.
36///
37/// `Rgb` is the base device dependent color space from which all others go through to convert,
38/// which can be converted to the other
39/// device dependent spaces or to the device independent CIE spaces directly. The color space
40/// of `Rgb` is not specified or assumed, it is up to you to not mix color spaces improperly or use
41/// an appropriate wrapper.
42///
43/// ## Examples:
44///
45/// ```rust
46/// use prisma::{Broadcast, HomogeneousColor, Lerp, Rgb};
47///
48/// let black = Rgb::broadcast(0.0f32);
49/// let blue = Rgb::new(0, 0, 255u8);
50/// // Convert blue to have float channels and compute the color halfway between blue and black
51/// let blended = black.lerp(&blue.color_cast(), 0.5);
52///
53/// assert_eq!(blended, Rgb::new(0.0, 0.0, 0.5));
54/// ```
55pub struct Rgb<T> {
56    red: PosNormalBoundedChannel<T>,
57    green: PosNormalBoundedChannel<T>,
58    blue: PosNormalBoundedChannel<T>,
59}
60
61impl<T> Rgb<T>
62where
63    T: PosNormalChannelScalar,
64{
65    /// Construct a new `Rgb` instance with the given channel values
66    pub fn new(red: T, green: T, blue: T) -> Self {
67        Rgb {
68            red: PosNormalBoundedChannel::new(red),
69            green: PosNormalBoundedChannel::new(green),
70            blue: PosNormalBoundedChannel::new(blue),
71        }
72    }
73
74    impl_color_color_cast_square!(
75        Rgb { red, green, blue },
76        chan_traits = { PosNormalChannelScalar }
77    );
78
79    /// Returns the red channel scalar
80    pub fn red(&self) -> T {
81        self.red.0.clone()
82    }
83    /// Returns the green channel scalar
84    pub fn green(&self) -> T {
85        self.green.0.clone()
86    }
87    /// Returns the blue channel scalar
88    pub fn blue(&self) -> T {
89        self.blue.0.clone()
90    }
91    /// Returns a mutable reference to the red channel scalar
92    pub fn red_mut(&mut self) -> &mut T {
93        &mut self.red.0
94    }
95    /// Returns a mutable reference to the green channel scalar
96    pub fn green_mut(&mut self) -> &mut T {
97        &mut self.green.0
98    }
99    /// Returns a mutable reference to the blue channel scalar
100    pub fn blue_mut(&mut self) -> &mut T {
101        &mut self.blue.0
102    }
103    /// Set the red channel value
104    pub fn set_red(&mut self, val: T) {
105        self.red.0 = val;
106    }
107    /// Set the green channel value
108    pub fn set_green(&mut self, val: T) {
109        self.green.0 = val;
110    }
111    /// Set the blue channel value
112    pub fn set_blue(&mut self, val: T) {
113        self.blue.0 = val;
114    }
115}
116
117impl<T> Rgb<T>
118where
119    T: PosNormalChannelScalar + num_traits::Float,
120{
121    /// Compute the [`ChromaticityCooridinates`](../chromaticity/struct.ChromaticityCoordinates.html)
122    /// for an `Rgb` instance
123    pub fn chromaticity_coordinates(&self) -> ChromaticityCoordinates<T> {
124        let alpha = cast::<_, T>(0.5).unwrap()
125            * (cast::<_, T>(2.0).unwrap() * self.red() - self.green() - self.blue());
126
127        let beta = cast::<_, T>(3.0).unwrap().sqrt()
128            * cast::<_, T>(0.5).unwrap()
129            * (self.green() - self.blue());
130
131        ChromaticityCoordinates { alpha, beta }
132    }
133}
134
135impl<T> Color for Rgb<T>
136where
137    T: PosNormalChannelScalar,
138{
139    type Tag = RgbTag;
140    type ChannelsTuple = (T, T, T);
141
142    #[inline]
143    fn num_channels() -> u32 {
144        3
145    }
146
147    fn to_tuple(self) -> Self::ChannelsTuple {
148        (self.red.0, self.green.0, self.blue.0)
149    }
150}
151
152impl<T> FromTuple for Rgb<T>
153where
154    T: PosNormalChannelScalar,
155{
156    fn from_tuple(values: Self::ChannelsTuple) -> Self {
157        Rgb::new(values.0, values.1, values.2)
158    }
159}
160
161impl<T> HomogeneousColor for Rgb<T>
162where
163    T: PosNormalChannelScalar,
164{
165    type ChannelFormat = T;
166
167    impl_color_homogeneous_color_square!(Rgb<T> {red, green, blue});
168}
169
170impl<T> Broadcast for Rgb<T>
171where
172    T: PosNormalChannelScalar,
173{
174    impl_color_broadcast!(Rgb<T> {red, green, blue}, chan=PosNormalBoundedChannel);
175}
176
177impl<T> color::Color3 for Rgb<T> where T: PosNormalChannelScalar {}
178
179impl<T> color::Invert for Rgb<T>
180where
181    T: PosNormalChannelScalar,
182{
183    impl_color_invert!(Rgb { red, green, blue });
184}
185
186impl<T> color::Bounded for Rgb<T>
187where
188    T: PosNormalChannelScalar,
189{
190    impl_color_bounded!(Rgb { red, green, blue });
191}
192
193impl<T> color::Lerp for Rgb<T>
194where
195    T: PosNormalChannelScalar + color::Lerp,
196{
197    type Position = <T as color::Lerp>::Position;
198    impl_color_lerp_square!(Rgb { red, green, blue });
199}
200
201impl<T> color::Flatten for Rgb<T>
202where
203    T: PosNormalChannelScalar,
204{
205    impl_color_as_slice!(T);
206    impl_color_from_slice_square!(Rgb<T> {red:PosNormalBoundedChannel - 0, 
207        green:PosNormalBoundedChannel - 1, blue:PosNormalBoundedChannel - 2});
208}
209
210impl<T> EncodableColor for Rgb<T> where T: PosNormalChannelScalar {}
211
212#[cfg(feature = "approx")]
213impl<T> approx::AbsDiffEq for Rgb<T>
214where
215    T: PosNormalChannelScalar + approx::AbsDiffEq,
216    T::Epsilon: Clone,
217{
218    impl_abs_diff_eq!({red, green, blue});
219}
220#[cfg(feature = "approx")]
221impl<T> approx::RelativeEq for Rgb<T>
222where
223    T: PosNormalChannelScalar + approx::RelativeEq,
224    T::Epsilon: Clone,
225{
226    impl_rel_eq!({red, green, blue});
227}
228#[cfg(feature = "approx")]
229impl<T> approx::UlpsEq for Rgb<T>
230where
231    T: PosNormalChannelScalar + approx::UlpsEq,
232    T::Epsilon: Clone,
233{
234    impl_ulps_eq!({red, green, blue});
235}
236
237impl<T> Default for Rgb<T>
238where
239    T: PosNormalChannelScalar + num_traits::Zero,
240{
241    impl_color_default!(Rgb {
242        red: PosNormalBoundedChannel,
243        green: PosNormalBoundedChannel,
244        blue: PosNormalBoundedChannel
245    });
246}
247
248impl<T> fmt::Display for Rgb<T>
249where
250    T: PosNormalChannelScalar + fmt::Display,
251{
252    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
253        write!(f, "Rgb({}, {}, {})", self.red, self.green, self.blue)
254    }
255}
256
257fn get_hue_factor_and_ordered_chans<T>(color: &Rgb<T>) -> (T, T, T, T, T)
258where
259    T: PosNormalChannelScalar + num_traits::Float,
260{
261    let mut scaling_factor = T::zero();
262    let (mut c1, mut c2, mut c3) = color.clone().to_tuple();
263
264    if c2 < c3 {
265        mem::swap(&mut c2, &mut c3);
266        scaling_factor = cast(-1.0).unwrap();
267    }
268    let min_chan = if c1 < c2 {
269        mem::swap(&mut c1, &mut c2);
270        scaling_factor = cast::<_, T>(-1.0 / 3.0).unwrap() - scaling_factor;
271        c2.min(c3)
272    } else {
273        c3
274    };
275
276    (scaling_factor, c1, c2, c3, min_chan)
277}
278
279fn make_hue_from_factor_and_ordered_chans<T>(
280    c1: &T,
281    c2: &T,
282    c3: &T,
283    min_chan: &T,
284    scale_factor: &T,
285) -> T
286where
287    T: PosNormalChannelScalar + num_traits::Float,
288{
289    let epsilon = cast(1e-10).unwrap();
290    let hue_scalar =
291        *scale_factor + (*c2 - *c3) / (cast::<_, T>(6.0).unwrap() * (*c1 - *min_chan) + epsilon);
292
293    hue_scalar.abs()
294}
295
296impl<T> convert::GetChroma for Rgb<T>
297where
298    T: PosNormalChannelScalar,
299{
300    type ChromaType = T;
301    fn get_chroma(&self) -> T {
302        let (mut c1, mut c2, mut c3) = self.clone().to_tuple();
303        if c2 < c3 {
304            mem::swap(&mut c2, &mut c3);
305        }
306        if c1 < c2 {
307            mem::swap(&mut c1, &mut c3);
308        }
309        if c2 < c3 {
310            mem::swap(&mut c2, &mut c3);
311        }
312        c1 - c3
313    }
314}
315
316impl<T> convert::GetHue for Rgb<T>
317where
318    T: PosNormalChannelScalar + num_traits::Float,
319{
320    type InternalAngle = angle::Turns<T>;
321    fn get_hue<U>(&self) -> U
322    where
323        U: angle::Angle<Scalar = <Self::InternalAngle as angle::Angle>::Scalar>
324            + angle::FromAngle<angle::Turns<T>>,
325    {
326        let (scale_factor, c1, c2, c3, min_chan) = get_hue_factor_and_ordered_chans(self);
327        let hue_scalar =
328            make_hue_from_factor_and_ordered_chans(&c1, &c2, &c3, &min_chan, &scale_factor);
329
330        U::from_angle(angle::Turns(hue_scalar.abs()))
331    }
332}
333
334impl<T, A> convert::FromColor<Rgb<T>> for hsv::Hsv<T, A>
335where
336    T: PosNormalChannelScalar + num_traits::Float,
337    A: AngularChannelScalar + angle::FromAngle<angle::Turns<T>>,
338{
339    fn from_color(from: &Rgb<T>) -> Self {
340        let epsilon = cast(1e-10).unwrap();
341        let (scaling_factor, c1, c2, c3, min_chan) = get_hue_factor_and_ordered_chans(from);
342        let max_chan = c1;
343        let chroma = c1 - min_chan;
344        let hue = make_hue_from_factor_and_ordered_chans(&c1, &c2, &c3, &min_chan, &scaling_factor);
345        let value = max_chan;
346        let saturation = chroma / (value + epsilon);
347
348        hsv::Hsv::new(A::from_angle(angle::Turns(hue)), saturation, value)
349    }
350}
351
352impl<T, A> convert::FromColor<Rgb<T>> for hsl::Hsl<T, A>
353where
354    T: PosNormalChannelScalar + num_traits::Float,
355    A: AngularChannelScalar + angle::FromAngle<angle::Turns<T>>,
356{
357    fn from_color(from: &Rgb<T>) -> Self {
358        let epsilon = cast(1e-10).unwrap();
359        let (scaling_factor, c1, c2, c3, min_channel) = get_hue_factor_and_ordered_chans(from);
360        let max_channel = c1;
361        let chroma = max_channel - min_channel;
362        let hue =
363            make_hue_from_factor_and_ordered_chans(&c1, &c2, &c3, &min_channel, &scaling_factor);
364        let lightness = cast::<_, T>(0.5).unwrap() * (max_channel + min_channel);
365        let one: T = cast(1.0).unwrap();
366        let sat_denom = one - (cast::<_, T>(2.0).unwrap() * lightness - one).abs() + epsilon;
367
368        let saturation = chroma / sat_denom;
369
370        hsl::Hsl::new(A::from_angle(angle::Turns(hue)), saturation, lightness)
371    }
372}
373
374impl<T, A> convert::FromColor<Rgb<T>> for hwb::Hwb<T, A>
375where
376    T: PosNormalChannelScalar + num_traits::Float,
377    A: AngularChannelScalar + angle::FromAngle<angle::Turns<T>>,
378{
379    fn from_color(from: &Rgb<T>) -> Self {
380        let (scaling_factor, c1, c2, c3, min_channel) = get_hue_factor_and_ordered_chans(from);
381        let max_channel = c1;
382        let chroma = max_channel - min_channel;
383        let hue =
384            make_hue_from_factor_and_ordered_chans(&c1, &c2, &c3, &min_channel, &scaling_factor);
385
386        let blackness = cast::<_, T>(1.0).unwrap() - max_channel;
387        let whiteness = cast::<_, T>(1.0).unwrap() - (blackness + chroma);
388
389        hwb::Hwb::new(A::from_angle(angle::Turns(hue)), whiteness, blackness)
390    }
391}
392
393#[cfg(test)]
394mod test {
395    use super::*;
396    use crate::color::*;
397    use crate::convert::*;
398    use crate::hsl::Hsl;
399    use crate::hsv::Hsv;
400    use crate::test;
401    use angle::*;
402    use approx::*;
403
404    #[test]
405    fn test_construct() {
406        {
407            let color = Rgb::new(0u8, 0, 0);
408            assert_eq!(color.red(), 0u8);
409            assert_eq!(color.green(), 0u8);
410            assert_eq!(color.blue(), 0u8);
411
412            let c2 = color.clone();
413            assert_eq!(color, c2);
414
415            let c3 = Rgb::new(120u8, 100u8, 255u8);
416            assert_eq!(c3.red(), 120u8);
417            assert_eq!(c3.green(), 100u8);
418            assert_eq!(c3.blue(), 255u8);
419            assert_eq!(c3.as_slice(), &[120u8, 100, 255]);
420        }
421        {
422            let color: Rgb<u8> = Rgb::default();
423            assert_eq!(color.red(), 0u8);
424            assert_eq!(color.green(), 0u8);
425            assert_eq!(color.blue(), 0u8);
426        }
427        {
428            let color = Rgb::broadcast(0.5_f32);
429            assert_ulps_eq!(color, Rgb::new(0.5_f32, 0.5, 0.5));
430        }
431        {
432            let color = Rgb::from_slice(&[120u8, 240, 10]);
433            assert_eq!(color, Rgb::new(120u8, 240, 10));
434            assert_eq!(color.to_tuple(), (120u8, 240, 10));
435        }
436        {
437            let c1 = Rgb::from_tuple((0.8f32, 0.5, 0.3));
438            assert_ulps_eq!(c1, Rgb::new(0.8f32, 0.5, 0.3));
439        }
440    }
441
442    #[test]
443    fn test_lerp_int() {
444        let c1 = Rgb::new(100u8, 200u8, 0u8);
445        let c2 = Rgb::new(200u8, 0u8, 255u8);
446
447        assert_eq!(c1.lerp(&c2, 0.5_f64), Rgb::new(150u8, 100, 127));
448        assert_eq!(c1.lerp(&c2, 0.0_f64), c1);
449        assert_eq!(c1.lerp(&c2, 1.0_f64), c2);
450    }
451
452    #[test]
453    fn test_lerp_float() {
454        let c1 = Rgb::new(0.2_f32, 0.5, 1.0);
455        let c2 = Rgb::new(0.8_f32, 0.5, 0.1);
456
457        assert_ulps_eq!(c1.lerp(&c2, 0.5_f32), Rgb::new(0.5_f32, 0.5, 0.55));
458        assert_ulps_eq!(c1.lerp(&c2, 0.0_f32), Rgb::new(0.2_f32, 0.5, 1.0));
459        assert_ulps_eq!(c1.lerp(&c2, 1.0_f32), Rgb::new(0.8_f32, 0.5, 0.1));
460    }
461
462    #[test]
463    fn test_invert() {
464        let c = Rgb::new(200u8, 0, 255);
465        let c2 = Rgb::new(0.8_f32, 0.0, 0.25);
466
467        assert_eq!(c.invert(), Rgb::new(55u8, 255, 0));
468        assert_ulps_eq!(c2.invert(), Rgb::new(0.2_f32, 1.0, 0.75));
469    }
470
471    #[test]
472    fn test_chroma() {
473        let c = Rgb::new(200u8, 150, 100);
474        assert_eq!(c.get_chroma(), 100u8);
475
476        let c2 = Rgb::new(1.0_f32, 0.0, 0.25);
477        assert_ulps_eq!(c2.get_chroma(), 1.0_f32);
478
479        let c3 = Rgb::new(0.5_f32, 0.5, 0.5);
480        assert_ulps_eq!(c3.get_chroma(), 0.0_f32);
481    }
482
483    #[test]
484    fn test_hue() {
485        let c1 = Rgb::new(1.0_f32, 0.0, 0.0);
486        assert_ulps_eq!(c1.get_hue(), Deg(0.0));
487        assert_ulps_eq!(Rgb::new(0.0, 1.0_f32, 0.0).get_hue(), Deg(120.0));
488        assert_ulps_eq!(Rgb::new(0.0, 0.0_f32, 1.0).get_hue(), Deg(240.0));
489        assert_relative_eq!(Rgb::new(0.5, 0.5, 0.0).get_hue(), Deg(60.0), epsilon = 1e-6);
490        assert_relative_eq!(
491            Rgb::new(0.5, 0.0, 0.5).get_hue(),
492            Deg(300.0),
493            epsilon = 1e-6
494        );
495    }
496
497    #[test]
498    fn hsv_from_rgb() {
499        let test_data = test::build_hs_test_data();
500
501        for item in test_data.iter() {
502            let hsv: Hsv<_, Deg<_>> = Hsv::from_color(&item.rgb);
503            assert_relative_eq!(hsv, item.hsv, epsilon = 1e-3);
504        }
505
506        let c1 = Rgb::new(0.2, 0.2, 0.2);
507        assert_relative_eq!(Hsv::from_color(&c1), Hsv::new(Deg(0.0), 0.0, 0.2));
508    }
509
510    #[test]
511    fn hsl_from_rgb() {
512        let test_data = test::build_hs_test_data();
513
514        for item in test_data.iter() {
515            let hsl: Hsl<_, Deg<_>> = Hsl::from_color(&item.rgb);
516            assert_relative_eq!(hsl, item.hsl, epsilon = 1e-3);
517        }
518    }
519
520    #[test]
521    fn color_cast() {
522        let c1 = Rgb::new(0u8, 0, 0);
523        assert_eq!(c1.color_cast(), c1);
524        assert_eq!(c1.color_cast(), Rgb::new(0u16, 0, 0));
525        assert_eq!(c1.color_cast(), Rgb::new(0u32, 0, 0));
526        assert_relative_eq!(c1.color_cast(), Rgb::new(0.0f32, 0.0, 0.0));
527        assert_relative_eq!(c1.color_cast(), Rgb::new(0.0f64, 0.0, 0.0));
528
529        let c2 = Rgb::new(255u8, 127, 255);
530        assert_eq!(c2.color_cast(), c2);
531        assert_relative_eq!(
532            c2.color_cast(),
533            Rgb::new(1.0f32, 0.4980392, 1.0),
534            epsilon = 1e-6
535        );
536
537        let c3 = Rgb::new(65535u16, 0, 20000);
538        assert_eq!(c3.color_cast(), c3);
539        assert_relative_eq!(
540            c3.color_cast(),
541            Rgb::new(1.0f64, 0.0, 0.3051804),
542            epsilon = 1e-6
543        );
544        assert_eq!(c3.color_cast::<f32>().color_cast(), c3);
545
546        let c4 = Rgb::new(1.0f32, 0.25, 0.0);
547        assert_eq!(c4.color_cast(), c4);
548        assert_eq!(c4.color_cast(), Rgb::new(255u8, 63, 0));
549        assert_eq!(c4.color_cast(), Rgb::new(0xffffu16, 0x3fff, 0));
550
551        let c5 = Rgb::new(0.33f64, 0.50, 0.80);
552        assert_eq!(c5.color_cast(), c5);
553        assert_relative_eq!(
554            c5.color_cast(),
555            Rgb::new(0.33f32, 0.50, 0.80),
556            epsilon = 1e-6
557        );
558        assert_relative_eq!(c5.color_cast::<f64>().color_cast(), c5, epsilon = 1e-6);
559
560        let c6 = Rgb::new(0.60f32, 0.01, 0.99);
561        assert_eq!(c6.color_cast(), c6);
562        assert_eq!(c6.color_cast(), Rgb::new(153u8, 2, 253));
563        assert_relative_eq!(
564            c6.color_cast::<u16>()
565                .color_cast::<u32>()
566                .color_cast::<f32>()
567                .color_cast::<f64>(),
568            Rgb::new(0.60f64, 0.01, 0.99),
569            epsilon = 1e-4
570        );
571    }
572}