rsvg/surface_utils/
mod.rs

1//! Various utilities for working with Cairo image surfaces.
2
3use std::alloc;
4use std::slice;
5
6pub mod iterators;
7pub mod shared_surface;
8pub mod srgb;
9
10// These two are for Cairo's platform-endian 0xaarrggbb pixels
11
12#[cfg(target_endian = "little")]
13use rgb::alt::BGRA8;
14#[cfg(target_endian = "little")]
15#[allow(clippy::upper_case_acronyms)]
16pub type CairoARGB = BGRA8;
17
18#[cfg(target_endian = "big")]
19use rgb::alt::ARGB8;
20#[cfg(target_endian = "big")]
21#[allow(clippy::upper_case_acronyms)]
22pub type CairoARGB = ARGB8;
23
24use rgb::ColorComponentMap;
25
26/// Analogous to `rgb::FromSlice`, to convert from `[T]` to `[CairoARGB]`
27#[allow(clippy::upper_case_acronyms)]
28pub 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
40impl 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    fn as_cairo_argb_mut(&mut self) -> &mut [CairoARGB] {
50        unsafe { slice::from_raw_parts_mut(self.as_mut_ptr() as *mut _, self.len()) }
51    }
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)]
58pub 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.
70pub trait ToPixel {
71    fn to_pixel(&self) -> Pixel;
72}
73
74/// Trait to convert pixels in various formats to Cairo's endian-dependent 0xaarrggbb.
75pub trait ToCairoARGB {
76    fn to_cairo_argb(&self) -> CairoARGB;
77}
78
79impl 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
91impl ToPixel for image::Rgba<u8> {
92    #[inline]
93    fn to_pixel(&self) -> Pixel {
94        Pixel {
95            r: self.0[0],
96            g: self.0[1],
97            b: self.0[2],
98            a: self.0[3],
99        }
100    }
101}
102
103impl ToCairoARGB for Pixel {
104    #[inline]
105    fn to_cairo_argb(&self) -> CairoARGB {
106        CairoARGB {
107            r: self.r,
108            g: self.g,
109            b: self.b,
110            a: self.a,
111        }
112    }
113}
114
115/// Extension methods for `cairo::ImageSurfaceData`.
116pub 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.
122pub 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.
128pub 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
137impl 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    fn unpremultiply(self) -> Self {
143        if self.a == 0 {
144            Self {
145                r: 0,
146                g: 0,
147                b: 0,
148                a: 0,
149            }
150        } else {
151            let alpha = f32::from(self.a) / 255.0;
152            self.map_colors(|x| ((f32::from(x) / alpha) + 0.5) as u8)
153        }
154    }
155
156    /// Returns a premultiplied value of this pixel.
157    #[inline]
158    fn premultiply(self) -> Self {
159        let a = self.a as u32;
160        self.map_colors(|x| (((x as u32) * a + 127) / 255) as u8)
161    }
162
163    #[inline]
164    fn diff(&self, other: &Pixel) -> Pixel {
165        self.iter()
166            .zip(other.iter())
167            .map(|(l, r)| (l as i32 - r as i32).unsigned_abs() as u8)
168            .collect()
169    }
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    fn to_luminance_mask(&self) -> Self {
193        let r = u32::from(self.r);
194        let g = u32::from(self.g);
195        let b = u32::from(self.b);
196
197        Self {
198            r: 0,
199            g: 0,
200            b: 0,
201            a: (((r * 14042 + g * 47240 + b * 4769) * 255) >> 24) as u8,
202        }
203    }
204
205    /// Returns the pixel value as a `u32`, in the same format as `cairo::Format::ARgb32`.
206    #[inline]
207    fn to_u32(&self) -> u32 {
208        (u32::from(self.a) << 24)
209            | (u32::from(self.r) << 16)
210            | (u32::from(self.g) << 8)
211            | u32::from(self.b)
212    }
213
214    /// Converts a `u32` in the same format as `cairo::Format::ARgb32` into a `Pixel`.
215    #[inline]
216    fn from_u32(x: u32) -> Self {
217        Self {
218            r: ((x >> 16) & 0xFF) as u8,
219            g: ((x >> 8) & 0xFF) as u8,
220            b: (x & 0xFF) as u8,
221            a: ((x >> 24) & 0xFF) as u8,
222        }
223    }
224}
225
226impl<'a> ImageSurfaceDataExt for cairo::ImageSurfaceData<'a> {
227    #[inline]
228    fn set_pixel(&mut self, stride: usize, pixel: Pixel, x: u32, y: u32) {
229        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        let this: &mut [u32] =
240            unsafe { slice::from_raw_parts_mut(this.as_mut_ptr() as *mut u32, this.len() / 4) };
241        this.set_pixel(stride, pixel, x, y);
242    }
243}
244impl ImageSurfaceDataExt for [u8] {
245    #[inline]
246    fn set_pixel(&mut self, stride: usize, pixel: Pixel, x: u32, y: u32) {
247        let this = &mut self[y as usize * stride + x as usize * 4..];
248        this[..4].copy_from_slice(&pixel.to_u32().to_ne_bytes());
249    }
250}
251impl ImageSurfaceDataExt for [u32] {
252    #[inline]
253    fn set_pixel(&mut self, stride: usize, pixel: Pixel, x: u32, y: u32) {
254        self[(y as usize * stride + x as usize * 4) / 4] = pixel.to_u32();
255    }
256}
257
258#[cfg(test)]
259mod tests {
260    use super::*;
261    use proptest::prelude::*;
262
263    #[test]
264    fn pixel_diff() {
265        let a = Pixel::new(0x10, 0x20, 0xf0, 0x40);
266        assert_eq!(a, a.diff(&Pixel::default()));
267        let b = Pixel::new(0x50, 0xff, 0x20, 0x10);
268        assert_eq!(a.diff(&b), Pixel::new(0x40, 0xdf, 0xd0, 0x30));
269    }
270
271    // Floating-point reference implementation
272    fn premultiply_float(pixel: Pixel) -> Pixel {
273        let alpha = f64::from(pixel.a) / 255.0;
274        pixel.map_colors(|x| ((f64::from(x) * alpha) + 0.5) as u8)
275    }
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}