dssim_core/
linear.rs

1use crate::image::{RGBAPLU, RGBLU};
2use rgb::alt::*;
3use rgb::*;
4
5/// See `GammaPixel` & `ToRGBAPLU`
6#[doc(hidden)]
7pub trait GammaComponent {
8    type Lut;
9    fn max_value() -> usize;
10    fn to_linear(&self, lut: &Self::Lut) -> f32;
11    fn make_lut() -> Self::Lut;
12}
13
14/// Downsampling should be done in linear RGB color space.
15///
16/// Used by `ToRGBAPLU`
17///
18/// This trait provides gamma to linear conversion via lookup table,
19/// and there's implementation for sRGB for common RGB types.
20#[doc(hidden)]
21pub trait GammaPixel {
22    type Component: GammaComponent;
23    type Output;
24
25    fn to_linear(&self, gamma_lut: &<Self::Component as GammaComponent>::Lut) -> Self::Output;
26
27    #[inline(always)]
28    #[must_use]
29    fn make_lut() -> <Self::Component as GammaComponent>::Lut {
30        <Self::Component as GammaComponent>::make_lut()
31    }
32}
33
34#[inline]
35fn to_linear(s: f32) -> f32 {
36    if s <= 0.04045 {
37        s / 12.92
38    } else {
39        ((s + 0.055) / 1.055).powf(2.4)
40    }
41}
42
43/// RGBA Premultiplied Linear-light Unit scale
44///
45/// Convenience function `.to_rgbaplu()` to convert RGBA bitmaps to a format useful for DSSIM.
46pub trait ToRGBAPLU {
47    /// Convert with alpha channel preserved
48    fn to_rgbaplu(&self) -> Vec<RGBAPLU>;
49    /// Discard alpha channel, if any
50    fn to_rgblu(&self) -> Vec<RGBLU>;
51}
52
53impl GammaComponent for u8 {
54    type Lut = [f32; 256];
55    fn max_value() -> usize { 255 }
56    #[inline(always)]
57    fn to_linear(&self, lut: &Self::Lut) -> f32 {
58        lut[*self as usize]
59    }
60
61    #[inline]
62    fn make_lut() -> Self::Lut {
63        let mut out = [0.; 256];
64        for (i, o) in out.iter_mut().enumerate() {
65            *o = to_linear(i as f32 / f32::from(Self::max_value()));
66        }
67        out
68    }
69}
70
71impl GammaComponent for u16 {
72    type Lut = [f32; 65536];
73    fn max_value() -> usize { 65535 }
74    #[inline(always)]
75    fn to_linear(&self, lut: &Self::Lut) -> f32 {
76        lut[*self as usize]
77    }
78
79    #[inline]
80    fn make_lut() -> Self::Lut {
81        let mut out = [0.; 65536];
82        for (i, o) in out.iter_mut().enumerate() {
83            *o = to_linear(i as f32 / f32::from(Self::max_value()));
84        }
85        out
86    }
87}
88
89impl<M> GammaPixel for RGBA<M> where M: Clone + Into<f32> + GammaComponent {
90    type Component = M;
91    type Output = RGBAPLU;
92    #[inline]
93    fn to_linear(&self, gamma_lut: &M::Lut) -> RGBAPLU {
94        let a_unit = self.a.clone().into() / M::max_value() as f32;
95        RGBAPLU {
96            r: self.r.to_linear(gamma_lut) * a_unit,
97            g: self.g.to_linear(gamma_lut) * a_unit,
98            b: self.b.to_linear(gamma_lut) * a_unit,
99            a: a_unit,
100        }
101    }
102}
103
104impl<M> GammaPixel for BGRA<M> where M: Clone + Into<f32> + GammaComponent {
105    type Component = M;
106    type Output = RGBAPLU;
107
108    #[inline]
109    fn to_linear(&self, gamma_lut: &M::Lut) -> RGBAPLU {
110        let a_unit = self.a.clone().into() / M::max_value() as f32;
111        RGBAPLU {
112            r: self.r.to_linear(gamma_lut) * a_unit,
113            g: self.g.to_linear(gamma_lut) * a_unit,
114            b: self.b.to_linear(gamma_lut) * a_unit,
115            a: a_unit,
116        }
117    }
118}
119
120impl<M> GammaPixel for RGB<M> where M: GammaComponent {
121    type Component = M;
122    type Output = RGBAPLU;
123    #[inline]
124    fn to_linear(&self, gamma_lut: &M::Lut) -> RGBAPLU {
125        RGBAPLU {
126            r: self.r.to_linear(gamma_lut),
127            g: self.g.to_linear(gamma_lut),
128            b: self.b.to_linear(gamma_lut),
129            a: 1.0,
130        }
131    }
132}
133
134impl<M> GammaPixel for BGR<M> where M: GammaComponent {
135    type Component = M;
136    type Output = RGBAPLU;
137
138    #[inline]
139    fn to_linear(&self, gamma_lut: &M::Lut) -> RGBAPLU {
140        RGBAPLU {
141            r: self.r.to_linear(gamma_lut),
142            g: self.g.to_linear(gamma_lut),
143            b: self.b.to_linear(gamma_lut),
144            a: 1.0,
145        }
146    }
147}
148
149impl<M> GammaPixel for GrayAlpha<M> where M: Copy + Clone + Into<f32> + GammaComponent {
150    type Component = M;
151    type Output = RGBAPLU;
152
153    fn to_linear(&self, gamma_lut: &M::Lut) -> RGBAPLU {
154        let a_unit = self.1.into() / M::max_value() as f32;
155        let g = self.0.to_linear(gamma_lut);
156        RGBAPLU {
157            r: g * a_unit,
158            g: g * a_unit,
159            b: g * a_unit,
160            a: a_unit,
161        }
162    }
163}
164
165impl<M> GammaPixel for M where M: GammaComponent {
166    type Component = M;
167    type Output = f32;
168
169    #[inline(always)]
170    fn to_linear(&self, gamma_lut: &M::Lut) -> f32 {
171        self.to_linear(gamma_lut)
172    }
173}
174
175impl<M> GammaPixel for Gray<M> where M: Copy + GammaComponent {
176    type Component = M;
177    type Output = RGBAPLU;
178
179    #[inline(always)]
180    fn to_linear(&self, gamma_lut: &M::Lut) -> RGBAPLU {
181        let g = self.0.to_linear(gamma_lut);
182        RGBAPLU { r: g, g, b: g, a: 1.0 }
183    }
184}
185
186impl<P> ToRGBAPLU for [P] where P: GammaPixel<Output=RGBAPLU> {
187    fn to_rgbaplu(&self) -> Vec<RGBAPLU> {
188        let gamma_lut = P::make_lut();
189        self.iter().map(|px| px.to_linear(&gamma_lut)).collect()
190    }
191
192    fn to_rgblu(&self) -> Vec<RGBLU> {
193        let gamma_lut = P::make_lut();
194        self.iter().map(|px| px.to_linear(&gamma_lut).rgb()).collect()
195    }
196}