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
78793
    fn as_cairo_argb_mut(&mut self) -> &mut [CairoARGB] {
50
78793
        unsafe { slice::from_raw_parts_mut(self.as_mut_ptr() as *mut _, self.len()) }
51
78793
    }
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
26687609
    fn to_pixel(&self) -> Pixel {
94
26687609
        Pixel {
95
26687609
            r: self.0[0],
96
26687609
            g: self.0[1],
97
26687609
            b: self.0[2],
98
26687609
            a: self.0[3],
99
26687609
        }
100
26687609
    }
101
}
102

            
103
impl ToCairoARGB for Pixel {
104
    #[inline]
105
26687609
    fn to_cairo_argb(&self) -> CairoARGB {
106
26687609
        CairoARGB {
107
26687609
            r: self.r,
108
26687609
            g: self.g,
109
26687609
            b: self.b,
110
26687609
            a: self.a,
111
26687609
        }
112
26687609
    }
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
pub trait PixelOps {
125
    fn premultiply(self) -> Self;
126
    fn unpremultiply(self) -> Self;
127
    fn diff(&self, other: &Self) -> Self;
128
    fn to_luminance_mask(&self) -> Self;
129
    fn to_u32(&self) -> u32;
130
    fn from_u32(x: u32) -> Self;
131
}
132

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

            
152
    /// Returns a premultiplied value of this pixel.
153
    #[inline]
154
28159196
    fn premultiply(self) -> Self {
155
28159196
        let a = self.a as u32;
156
84477588
        self.map_colors(|x| (((x as u32) * a + 127) / 255) as u8)
157
28159196
    }
158

            
159
    #[inline]
160
9175928
    fn diff(&self, other: &Pixel) -> Pixel {
161
9175928
        self.iter()
162
9175928
            .zip(other.iter())
163
36703712
            .map(|(l, r)| (l as i32 - r as i32).unsigned_abs() as u8)
164
9175928
            .collect()
165
9175928
    }
166

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

            
193
621027445
        Self {
194
621027445
            r: 0,
195
621027445
            g: 0,
196
621027445
            b: 0,
197
621027445
            a: (((r * 14042 + g * 47240 + b * 4769) * 255) >> 24) as u8,
198
621027445
        }
199
621027445
    }
200

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

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

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

            
254
#[cfg(test)]
255
mod tests {
256
    use super::*;
257
    use proptest::prelude::*;
258

            
259
    #[test]
260
1
    fn pixel_diff() {
261
1
        let a = Pixel::new(0x10, 0x20, 0xf0, 0x40);
262
1
        assert_eq!(a, a.diff(&Pixel::default()));
263
1
        let b = Pixel::new(0x50, 0xff, 0x20, 0x10);
264
1
        assert_eq!(a.diff(&b), Pixel::new(0x40, 0xdf, 0xd0, 0x30));
265
1
    }
266

            
267
    // Floating-point reference implementation
268
256
    fn premultiply_float(pixel: Pixel) -> Pixel {
269
256
        let alpha = f64::from(pixel.a) / 255.0;
270
768
        pixel.map_colors(|x| ((f64::from(x) * alpha) + 0.5) as u8)
271
256
    }
272

            
273
    prop_compose! {
274
        fn arbitrary_pixel()(a: u8, r: u8, g: u8, b: u8) -> Pixel {
275
            Pixel { r, g, b, a }
276
        }
277
    }
278

            
279
    proptest! {
280
        #[test]
281
        fn pixel_premultiply(pixel in arbitrary_pixel()) {
282
            prop_assert_eq!(pixel.premultiply(), premultiply_float(pixel));
283
        }
284

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

            
298
                prop_assert_eq!(pixel.a, roundtrip.a);
299
            }
300
       }
301
    }
302
}