Skip to main content

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(always)]
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));
66        }
67        out
68    }
69}
70
71impl GammaComponent for u16 {
72    type Lut = [f32; 65536];
73
74    fn max_value() -> usize { 65535 }
75
76    #[inline(always)]
77    fn to_linear(&self, lut: &Self::Lut) -> f32 {
78        lut[*self as usize]
79    }
80
81    #[inline(always)]
82    fn make_lut() -> Self::Lut {
83        let mut out = [0.; 65536];
84        for (i, o) in out.iter_mut().enumerate() {
85            *o = to_linear(i as f32 / f32::from(Self::MAX));
86        }
87        out
88    }
89}
90
91impl<M> GammaPixel for RGBA<M> where M: Clone + Into<f32> + GammaComponent {
92    type Component = M;
93    type Output = RGBAPLU;
94    #[inline]
95    fn to_linear(&self, gamma_lut: &M::Lut) -> RGBAPLU {
96        let a_unit = self.a.clone().into() / M::max_value() as f32;
97        RGBAPLU {
98            r: self.r.to_linear(gamma_lut) * a_unit,
99            g: self.g.to_linear(gamma_lut) * a_unit,
100            b: self.b.to_linear(gamma_lut) * a_unit,
101            a: a_unit,
102        }
103    }
104}
105
106impl<M> GammaPixel for BGRA<M> where M: Clone + Into<f32> + GammaComponent {
107    type Component = M;
108    type Output = RGBAPLU;
109
110    #[inline]
111    fn to_linear(&self, gamma_lut: &M::Lut) -> RGBAPLU {
112        let a_unit = self.a.clone().into() / M::max_value() as f32;
113        RGBAPLU {
114            r: self.r.to_linear(gamma_lut) * a_unit,
115            g: self.g.to_linear(gamma_lut) * a_unit,
116            b: self.b.to_linear(gamma_lut) * a_unit,
117            a: a_unit,
118        }
119    }
120}
121
122impl<M> GammaPixel for RGB<M> where M: GammaComponent {
123    type Component = M;
124    type Output = RGBAPLU;
125    #[inline]
126    fn to_linear(&self, gamma_lut: &M::Lut) -> RGBAPLU {
127        RGBAPLU {
128            r: self.r.to_linear(gamma_lut),
129            g: self.g.to_linear(gamma_lut),
130            b: self.b.to_linear(gamma_lut),
131            a: 1.0,
132        }
133    }
134}
135
136impl<M> GammaPixel for BGR<M> where M: GammaComponent {
137    type Component = M;
138    type Output = RGBAPLU;
139
140    #[inline]
141    fn to_linear(&self, gamma_lut: &M::Lut) -> RGBAPLU {
142        RGBAPLU {
143            r: self.r.to_linear(gamma_lut),
144            g: self.g.to_linear(gamma_lut),
145            b: self.b.to_linear(gamma_lut),
146            a: 1.0,
147        }
148    }
149}
150
151impl<M> GammaPixel for GrayAlpha<M> where M: Copy + Clone + Into<f32> + GammaComponent {
152    type Component = M;
153    type Output = RGBAPLU;
154
155    fn to_linear(&self, gamma_lut: &M::Lut) -> RGBAPLU {
156        let a_unit = self.value().into() / M::max_value() as f32;
157        let g = self.value().to_linear(gamma_lut);
158        RGBAPLU {
159            r: g * a_unit,
160            g: g * a_unit,
161            b: g * a_unit,
162            a: a_unit,
163        }
164    }
165}
166
167impl<M> GammaPixel for M where M: GammaComponent {
168    type Component = M;
169    type Output = f32;
170
171    #[inline(always)]
172    fn to_linear(&self, gamma_lut: &M::Lut) -> f32 {
173        self.to_linear(gamma_lut)
174    }
175}
176
177impl<M> GammaPixel for Gray<M> where M: Copy + GammaComponent {
178    type Component = M;
179    type Output = RGBAPLU;
180
181    #[inline(always)]
182    fn to_linear(&self, gamma_lut: &M::Lut) -> RGBAPLU {
183        let g = self.value().to_linear(gamma_lut);
184        RGBAPLU { r: g, g, b: g, a: 1.0 }
185    }
186}
187
188impl<P> ToRGBAPLU for [P] where P: GammaPixel<Output=RGBAPLU> {
189    fn to_rgbaplu(&self) -> Vec<RGBAPLU> {
190        let gamma_lut = P::make_lut();
191        self.iter().map(|px| px.to_linear(&gamma_lut)).collect()
192    }
193
194    fn to_rgblu(&self) -> Vec<RGBLU> {
195        let gamma_lut = P::make_lut();
196        self.iter().map(|px| px.to_linear(&gamma_lut).rgb()).collect()
197    }
198}