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
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
use num::Float;

use {Alpha, Rgb, Rgba, clamp, flt};

use pixel::RgbPixel;

///A gamma encoded color.
///
///Gamma encoding or gamma correction is used to transform the intensity
///values to either match a non-linear display, like CRT, or to prevent
///banding among the darker colors. `GammaRgb` represents a gamma corrected
///RGB color, where the intensities are encoded using the following power-law
///expression: _V ^γ_ (where _V_ is the intensity value an _γ_ is the encoding
///gamma).
///
///This particular implementation is based on the ITU-R BT.709 primaries (same
///as in sRGB, HDTV, etc.), so decoding it will basically result in decoded
///sRGB.
///
///```
///use palette::Rgb;
///use palette::pixel::GammaRgb;
///
///let c: Rgb = GammaRgb::new_u8(128, 64, 32, 2.2).into();
///assert_eq!((128, 64, 32), GammaRgb::linear_to_pixel(c, 2.2));
///```
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct GammaRgb<T: Float = f32> {
    ///The red component, where 0.0 is no red light and 1.0 is the
    ///highest displayable amount.
    pub red: T,

    ///The green component, where 0.0 is no red light and 1.0 is the
    ///highest displayable amount.
    pub green: T,

    ///The blue component, where 0.0 is no red light and 1.0 is the
    ///highest displayable amount.
    pub blue: T,

    ///The transparency of the color. 0.0 is completely transparent and 1.0 is
    ///completely opaque.
    pub alpha: T,

    ///The decoding gamma value. Commonly 2.2.
    pub gamma: T,
}

impl<T: Float> GammaRgb<T> {
    ///Create a new opaque gamma encoded color.
    pub fn new(red: T, green: T, blue: T, gamma: T) -> GammaRgb<T> {
        GammaRgb::with_alpha(red, green, blue, T::one(), gamma)
    }

    ///Create a new gamma encoded color with transparency.
    pub fn with_alpha(red: T, green: T, blue: T, alpha: T, gamma: T) -> GammaRgb<T> {
        GammaRgb {
            red: red,
            green: green,
            blue: blue,
            alpha: alpha,
            gamma: gamma,
        }
    }

    ///Create a new opaque gamma encoded color from `u8` values.
    pub fn new_u8(red: u8, green: u8, blue: u8, gamma: T) -> GammaRgb<T> {
        GammaRgb::with_alpha_u8(red, green, blue, 255, gamma)
    }

    ///Create a new gamma encoded color, with transparency, from `u8` values.
    pub fn with_alpha_u8(red: u8, green: u8, blue: u8, alpha: u8, gamma: T) -> GammaRgb<T> {
        GammaRgb {
            red: flt::<T,_>(red) / flt(255.0),
            green: flt::<T,_>(green) / flt(255.0),
            blue: flt::<T,_>(blue) / flt(255.0),
            alpha: flt::<T,_>(alpha) / flt(255.0),
            gamma: gamma,
        }
    }

    ///Create a new gamma encoded color from a pixel value.
    pub fn from_pixel<P: RgbPixel<T>>(pixel: &P, gamma: T) -> GammaRgb<T> {
        let (r, g, b, a) = pixel.to_rgba();
        GammaRgb::with_alpha(r, g, b, a, gamma)
    }

    ///Transform this color into a pixel representation.
    pub fn to_pixel<P: RgbPixel<T>>(&self) -> P {
        P::from_rgba(
            clamp(self.red, T::zero(), T::one()),
            clamp(self.green, T::zero(), T::one()),
            clamp(self.blue, T::zero(), T::one()),
            clamp(self.alpha, T::zero(), T::one()),
        )
    }

    ///Convert linear color components to gamma encoding.
    pub fn from_linear<C: Into<Rgba<T>>>(color: C, gamma: T) -> GammaRgb<T> {
        let rgb = color.into();
        GammaRgb {
            red: to_gamma(rgb.red, gamma),
            green: to_gamma(rgb.green, gamma),
            blue: to_gamma(rgb.blue, gamma),
            alpha: rgb.alpha,
            gamma: gamma,
        }
    }

    ///Decode this color to a linear representation.
    pub fn to_linear(&self) -> Rgba<T> {
        Alpha {
            color: Rgb {
                red: from_gamma(self.red, self.gamma),
                green: from_gamma(self.green, self.gamma),
                blue: from_gamma(self.blue, self.gamma),
            },
            alpha: self.alpha,
        }
    }

    ///Shortcut to convert a linear color to a gamma encoded pixel.
    pub fn linear_to_pixel<C: Into<Rgba<T>>, P: RgbPixel<T>>(color: C, gamma: T) -> P {
        GammaRgb::from_linear(color, gamma).to_pixel()
    }
}

fn from_gamma<T: Float>(x: T, gamma: T) -> T {
    x.powf(T::one() / gamma)
}

fn to_gamma<T: Float>(x: T, gamma: T) -> T {
    x.powf(gamma)
}