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

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

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

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

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

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

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

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

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

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

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

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

            
122
pub trait PixelOps {
123
    fn premultiply(self) -> Self;
124
    fn unpremultiply(self) -> Self;
125
    fn diff(&self, other: &Self) -> Self;
126
    fn to_luminance_mask(&self) -> Self;
127
    fn to_u32(&self) -> u32;
128
    fn from_u32(x: u32) -> Self;
129
}
130

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

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

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

            
165
    /// Returns a 'mask' pixel with only the alpha channel
166
    ///
167
    /// Assuming, the pixel is linear RGB (not sRGB)
168
    /// y = luminance
169
    /// Y = 0.2126 R + 0.7152 G + 0.0722 B
170
    /// 1.0 opacity = 255
171
    ///
172
    /// When Y = 1.0, pixel for mask should be 0xFFFFFFFF
173
    /// (you get 1.0 luminance from 255 from R, G and B)
174
    ///
175
    /// r_mult = 0xFFFFFFFF / (255.0 * 255.0) * .2126 = 14042.45  ~= 14042
176
    /// g_mult = 0xFFFFFFFF / (255.0 * 255.0) * .7152 = 47239.69  ~= 47240
177
    /// b_mult = 0xFFFFFFFF / (255.0 * 255.0) * .0722 =  4768.88  ~= 4769
178
    ///
179
    /// This allows for the following expected behaviour:
180
    ///    (we only care about the most significant byte)
181
    /// if pixel = 0x00FFFFFF, pixel' = 0xFF......
182
    /// if pixel = 0x00020202, pixel' = 0x02......
183

            
184
    /// if pixel = 0x00000000, pixel' = 0x00......
185
    #[inline]
186
32450446
    fn to_luminance_mask(&self) -> Self {
187
32450446
        let r = u32::from(self.r);
188
32450446
        let g = u32::from(self.g);
189
32450446
        let b = u32::from(self.b);
190

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

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

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

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

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

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

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

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

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

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

            
296
                prop_assert_eq!(pixel.a, roundtrip.a);
297
            }
298
       }
299
    }
300
}