1
//! Various utilities for working with Cairo image surfaces.
2

            
3
use std::alloc;
4
use std::slice;
5

            
6
pub mod iterators;
7
pub mod shared_surface;
8
pub mod srgb;
9

            
10
// These two are for Cairo's platform-endian 0xaarrggbb pixels
11

            
12
#[cfg(target_endian = "little")]
13
use rgb::alt::BGRA8;
14
#[cfg(target_endian = "little")]
15
#[allow(clippy::upper_case_acronyms)]
16
pub type CairoARGB = BGRA8;
17

            
18
#[cfg(target_endian = "big")]
19
use rgb::alt::ARGB8;
20
#[cfg(target_endian = "big")]
21
#[allow(clippy::upper_case_acronyms)]
22
pub type CairoARGB = ARGB8;
23

            
24
use rgb::ColorComponentMap;
25

            
26
/// Analogous to `rgb::FromSlice`, to convert from `[T]` to `[CairoARGB]`
27
#[allow(clippy::upper_case_acronyms)]
28
pub trait AsCairoARGB {
29
    /// Reinterpret slice as `CairoARGB` pixels.
30
    fn as_cairo_argb(&self) -> &[CairoARGB];
31

            
32
    /// Reinterpret mutable slice as `CairoARGB` pixels.
33
    fn as_cairo_argb_mut(&mut self) -> &mut [CairoARGB];
34
}
35

            
36
// SAFETY: transmuting from u32 to CairoRGB is based on the following assumptions:
37
//  * there are no invalid bit representations for ARGB
38
//  * u32 and ARGB are the same size
39
//  * u32 is sufficiently aligned
40
impl AsCairoARGB for [u32] {
41
    fn as_cairo_argb(&self) -> &[CairoARGB] {
42
        const LAYOUT_U32: alloc::Layout = alloc::Layout::new::<u32>();
43
        const LAYOUT_ARGB: alloc::Layout = alloc::Layout::new::<CairoARGB>();
44
        let _: [(); LAYOUT_U32.size()] = [(); LAYOUT_ARGB.size()];
45
        let _: [(); 0] = [(); LAYOUT_U32.align() % LAYOUT_ARGB.align()];
46
        unsafe { slice::from_raw_parts(self.as_ptr() as *const _, self.len()) }
47
    }
48

            
49
82940
    fn as_cairo_argb_mut(&mut self) -> &mut [CairoARGB] {
50
82940
        unsafe { slice::from_raw_parts_mut(self.as_mut_ptr() as *mut _, self.len()) }
51
82940
    }
52
}
53

            
54
/// Modes which specify how the values of out of bounds pixels are computed.
55
///
56
/// <https://www.w3.org/TR/filter-effects/#element-attrdef-fegaussianblur-edgemode>
57
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
58
pub enum EdgeMode {
59
    /// The nearest inbounds pixel value is returned.
60
    Duplicate,
61
    /// The image is extended by taking the color values from the opposite of the image.
62
    ///
63
    /// Imagine the image being tiled infinitely, with the original image at the origin.
64
    Wrap,
65
    /// Zero RGBA values are returned.
66
    None,
67
}
68

            
69
/// Trait to convert pixels in various formats to our own Pixel layout.
70
pub trait ToPixel {
71
    fn to_pixel(&self) -> Pixel;
72
}
73

            
74
/// Trait to convert pixels in various formats to Cairo's endian-dependent 0xaarrggbb.
75
pub trait ToCairoARGB {
76
    fn to_cairo_argb(&self) -> CairoARGB;
77
}
78

            
79
impl ToPixel for CairoARGB {
80
    #[inline]
81
    fn to_pixel(&self) -> Pixel {
82
        Pixel {
83
            r: self.r,
84
            g: self.g,
85
            b: self.b,
86
            a: self.a,
87
        }
88
    }
89
}
90

            
91
impl ToPixel for image::Rgba<u8> {
92
    #[inline]
93
28092220
    fn to_pixel(&self) -> Pixel {
94
28092220
        Pixel {
95
28092220
            r: self.0[0],
96
28092220
            g: self.0[1],
97
28092220
            b: self.0[2],
98
28092220
            a: self.0[3],
99
28092220
        }
100
28092220
    }
101
}
102

            
103
impl ToCairoARGB for Pixel {
104
    #[inline]
105
28092220
    fn to_cairo_argb(&self) -> CairoARGB {
106
28092220
        CairoARGB {
107
28092220
            r: self.r,
108
28092220
            g: self.g,
109
28092220
            b: self.b,
110
28092220
            a: self.a,
111
28092220
        }
112
28092220
    }
113
}
114

            
115
/// Extension methods for `cairo::ImageSurfaceData`.
116
pub trait ImageSurfaceDataExt {
117
    /// Sets the pixel at the given coordinates. Assumes the `ARgb32` format.
118
    fn set_pixel(&mut self, stride: usize, pixel: Pixel, x: u32, y: u32);
119
}
120

            
121
/// A pixel consisting of R, G, B and A values.
122
pub type Pixel = rgb::RGBA8;
123

            
124
/// Various operations on pixel values.
125
///
126
/// This is done as a trait so that we can implement additional methods on [rgb::RGBA8],
127
/// which is aliased to the [Pixel] type.
128
pub trait PixelOps {
129
    fn premultiply(self) -> Self;
130
    fn unpremultiply(self) -> Self;
131
    fn diff(&self, other: &Self) -> Self;
132
    fn to_luminance_mask(&self) -> Self;
133
    fn to_u32(&self) -> u32;
134
    fn from_u32(x: u32) -> Self;
135
}
136

            
137
impl PixelOps for Pixel {
138
    /// Returns an unpremultiplied value of this pixel.
139
    ///
140
    /// For a fully transparent pixel, a transparent black pixel will be returned.
141
    #[inline]
142
1874112
    fn unpremultiply(self) -> Self {
143
1874112
        if self.a == 0 {
144
502882
            Self {
145
502882
                r: 0,
146
502882
                g: 0,
147
502882
                b: 0,
148
502882
                a: 0,
149
502882
            }
150
        } else {
151
1371230
            let alpha = f32::from(self.a) / 255.0;
152
4113690
            self.map_colors(|x| ((f32::from(x) / alpha) + 0.5) as u8)
153
        }
154
1874112
    }
155

            
156
    /// Returns a premultiplied value of this pixel.
157
    #[inline]
158
29641744
    fn premultiply(self) -> Self {
159
29641744
        let a = self.a as u32;
160
88925232
        self.map_colors(|x| (((x as u32) * a + 127) / 255) as u8)
161
29641744
    }
162

            
163
    #[inline]
164
9659154
    fn diff(&self, other: &Pixel) -> Pixel {
165
9659154
        self.iter()
166
9659154
            .zip(other.iter())
167
38636616
            .map(|(l, r)| (l as i32 - r as i32).unsigned_abs() as u8)
168
9659154
            .collect()
169
9659154
    }
170

            
171
    /// Returns a 'mask' pixel with only the alpha channel
172
    ///
173
    /// Assuming, the pixel is linear RGB (not sRGB)
174
    /// y = luminance
175
    /// Y = 0.2126 R + 0.7152 G + 0.0722 B
176
    /// 1.0 opacity = 255
177
    ///
178
    /// When Y = 1.0, pixel for mask should be 0xFFFFFFFF
179
    /// (you get 1.0 luminance from 255 from R, G and B)
180
    ///
181
    /// r_mult = 0xFFFFFFFF / (255.0 * 255.0) * .2126 = 14042.45  ~= 14042
182
    /// g_mult = 0xFFFFFFFF / (255.0 * 255.0) * .7152 = 47239.69  ~= 47240
183
    /// b_mult = 0xFFFFFFFF / (255.0 * 255.0) * .0722 =  4768.88  ~= 4769
184
    ///
185
    /// This allows for the following expected behaviour:
186
    ///    (we only care about the most significant byte)
187
    /// if pixel = 0x00FFFFFF, pixel' = 0xFF......
188
    /// if pixel = 0x00020202, pixel' = 0x02......
189
    ///
190
    /// if pixel = 0x00000000, pixel' = 0x00......
191
    #[inline]
192
653713100
    fn to_luminance_mask(&self) -> Self {
193
653713100
        let r = u32::from(self.r);
194
653713100
        let g = u32::from(self.g);
195
653713100
        let b = u32::from(self.b);
196

            
197
653713100
        Self {
198
653713100
            r: 0,
199
653713100
            g: 0,
200
653713100
            b: 0,
201
653713100
            a: (((r * 14042 + g * 47240 + b * 4769) * 255) >> 24) as u8,
202
653713100
        }
203
653713100
    }
204

            
205
    /// Returns the pixel value as a `u32`, in the same format as `cairo::Format::ARgb32`.
206
    #[inline]
207
1998228524
    fn to_u32(&self) -> u32 {
208
1998228524
        (u32::from(self.a) << 24)
209
1998228524
            | (u32::from(self.r) << 16)
210
1998228524
            | (u32::from(self.g) << 8)
211
1998228524
            | u32::from(self.b)
212
1998228524
    }
213

            
214
    /// Converts a `u32` in the same format as `cairo::Format::ARgb32` into a `Pixel`.
215
    #[inline]
216
3396383714
    fn from_u32(x: u32) -> Self {
217
3396383714
        Self {
218
3396383714
            r: ((x >> 16) & 0xFF) as u8,
219
3396383714
            g: ((x >> 8) & 0xFF) as u8,
220
3396383714
            b: (x & 0xFF) as u8,
221
3396383714
            a: ((x >> 24) & 0xFF) as u8,
222
3396383714
        }
223
3396383714
    }
224
}
225

            
226
impl<'a> ImageSurfaceDataExt for cairo::ImageSurfaceData<'a> {
227
    #[inline]
228
1827704684
    fn set_pixel(&mut self, stride: usize, pixel: Pixel, x: u32, y: u32) {
229
1827704684
        let this: &mut [u8] = &mut *self;
230
        // SAFETY: this code assumes that cairo image surface data is correctly
231
        // aligned for u32. This assumption is justified by the Cairo docs,
232
        // which say this:
233
        //
234
        // https://cairographics.org/manual/cairo-Image-Surfaces.html#cairo-image-surface-create-for-data
235
        //
236
        // > This pointer must be suitably aligned for any kind of variable,
237
        // > (for example, a pointer returned by malloc).
238
        #[allow(clippy::cast_ptr_alignment)]
239
1827704684
        let this: &mut [u32] =
240
1827704684
            unsafe { slice::from_raw_parts_mut(this.as_mut_ptr() as *mut u32, this.len() / 4) };
241
1827704684
        this.set_pixel(stride, pixel, x, y);
242
1827704684
    }
243
}
244
impl ImageSurfaceDataExt for [u8] {
245
    #[inline]
246
4269540
    fn set_pixel(&mut self, stride: usize, pixel: Pixel, x: u32, y: u32) {
247
4269540
        let this = &mut self[y as usize * stride + x as usize * 4..];
248
4269540
        this[..4].copy_from_slice(&pixel.to_u32().to_ne_bytes());
249
4269540
    }
250
}
251
impl ImageSurfaceDataExt for [u32] {
252
    #[inline]
253
1827704684
    fn set_pixel(&mut self, stride: usize, pixel: Pixel, x: u32, y: u32) {
254
1827704684
        self[(y as usize * stride + x as usize * 4) / 4] = pixel.to_u32();
255
1827704684
    }
256
}
257

            
258
#[cfg(test)]
259
mod tests {
260
    use super::*;
261
    use proptest::prelude::*;
262

            
263
    #[test]
264
2
    fn pixel_diff() {
265
2
        let a = Pixel::new(0x10, 0x20, 0xf0, 0x40);
266
2
        assert_eq!(a, a.diff(&Pixel::default()));
267
2
        let b = Pixel::new(0x50, 0xff, 0x20, 0x10);
268
2
        assert_eq!(a.diff(&b), Pixel::new(0x40, 0xdf, 0xd0, 0x30));
269
2
    }
270

            
271
    // Floating-point reference implementation
272
512
    fn premultiply_float(pixel: Pixel) -> Pixel {
273
512
        let alpha = f64::from(pixel.a) / 255.0;
274
1536
        pixel.map_colors(|x| ((f64::from(x) * alpha) + 0.5) as u8)
275
512
    }
276

            
277
    prop_compose! {
278
        fn arbitrary_pixel()(a: u8, r: u8, g: u8, b: u8) -> Pixel {
279
            Pixel { r, g, b, a }
280
        }
281
    }
282

            
283
    proptest! {
284
        #[test]
285
        fn pixel_premultiply(pixel in arbitrary_pixel()) {
286
            prop_assert_eq!(pixel.premultiply(), premultiply_float(pixel));
287
        }
288

            
289
        #[test]
290
        fn pixel_unpremultiply(pixel in arbitrary_pixel()) {
291
            let roundtrip = pixel.premultiply().unpremultiply();
292
            if pixel.a == 0 {
293
                prop_assert_eq!(roundtrip, Pixel::default());
294
            } else {
295
                // roundtrip can't be perfect, the accepted error depends on alpha
296
                let tolerance = 0xff / pixel.a;
297
                let diff = roundtrip.diff(&pixel);
298
                prop_assert!(diff.r <= tolerance, "red component value differs by more than {}: {:?}", tolerance, roundtrip);
299
                prop_assert!(diff.g <= tolerance, "green component value differs by more than {}: {:?}", tolerance, roundtrip);
300
                prop_assert!(diff.b <= tolerance, "blue component value differs by more than {}: {:?}", tolerance, roundtrip);
301

            
302
                prop_assert_eq!(pixel.a, roundtrip.a);
303
            }
304
       }
305
    }
306
}