rsvg/surface_utils/
shared_surface.rs

1//! Shared access to Cairo image surfaces.
2use std::cmp::min;
3use std::marker::PhantomData;
4use std::ptr::NonNull;
5use std::slice;
6
7use cast::i32;
8use nalgebra::{storage::Storage, Dim, Matrix};
9
10use crate::color::{color_to_rgba, Color};
11use crate::drawing_ctx::set_source_color_on_cairo;
12use crate::error::*;
13use crate::rect::{IRect, Rect};
14use crate::surface_utils::srgb;
15use crate::util::clamp;
16
17use super::{
18    iterators::{PixelRectangle, Pixels},
19    AsCairoARGB, CairoARGB, EdgeMode, ImageSurfaceDataExt, Pixel, PixelOps, ToCairoARGB, ToPixel,
20};
21
22/// Interpolation when scaling images.
23///
24/// This is meant to be translated from the `ImageRendering` property.  We don't use
25/// `ImageRendering` directly here, because this module is supposed to be lower-level
26/// than the main part of librsvg.  Here, we take `Interpolation` and translate it
27/// to Cairo's own values for pattern filtering.
28///
29/// This enum can be expanded to use more of Cairo's filtering modes.
30pub enum Interpolation {
31    Nearest,
32    Smooth,
33}
34
35impl From<Interpolation> for cairo::Filter {
36    fn from(i: Interpolation) -> cairo::Filter {
37        // Cairo's default for interpolation is CAIRO_FILTER_GOOD.  This happens in Cairo's internals, as
38        // CAIRO_FILTER_DEFAULT is an internal macro that expands to CAIRO_FILTER_GOOD.
39        match i {
40            Interpolation::Nearest => cairo::Filter::Nearest,
41            Interpolation::Smooth => cairo::Filter::Good,
42        }
43    }
44}
45
46/// Types of pixel data in a `ImageSurface`.
47#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
48pub enum SurfaceType {
49    /// The pixel data is in the sRGB color space.
50    SRgb,
51    /// The pixel data is in the linear sRGB color space.
52    LinearRgb,
53    /// The pixel data is alpha-only (contains meaningful data only in the alpha channel).
54    ///
55    /// A number of methods are optimized for alpha-only surfaces. For example, linearization and
56    /// unlinearization have no effect for alpha-only surfaces.
57    AlphaOnly,
58}
59
60impl SurfaceType {
61    /// Combines surface types
62    ///
63    /// If combining two alpha-only surfaces, the result is alpha-only.
64    /// If one is alpha-only, the result is the other.
65    /// If none is alpha-only, the types should be the same.
66    ///
67    /// # Panics
68    /// Panics if the surface types are not alpha-only and differ.
69    pub fn combine(self, other: SurfaceType) -> SurfaceType {
70        match (self, other) {
71            (SurfaceType::AlphaOnly, t) => t,
72            (t, SurfaceType::AlphaOnly) => t,
73            (t1, t2) if t1 == t2 => t1,
74            _ => panic!(),
75        }
76    }
77}
78
79/// Operators supported by `ImageSurface<Shared>::compose`.
80pub enum Operator {
81    Over,
82    In,
83    Out,
84    Atop,
85    Xor,
86    Multiply,
87    Screen,
88    Darken,
89    Lighten,
90    Overlay,
91    ColorDodge,
92    ColorBurn,
93    HardLight,
94    SoftLight,
95    Difference,
96    Exclusion,
97    HslHue,
98    HslSaturation,
99    HslColor,
100    HslLuminosity,
101}
102
103/// Wrapper for a Cairo image surface that enforces exclusive access when modifying it.
104///
105/// Shared access to `cairo::ImageSurface` is tricky since a read-only borrowed reference
106/// can still be cloned and then modified. We can't simply use `cairo::ImageSurface::data()`
107/// because in the filter code we have surfaces referenced from multiple places and it would
108/// probably add more complexity to remove that and start passing around references.
109///
110/// This wrapper asserts the uniqueness of its image surface.
111///
112/// It uses the typestate pattern to ensure that the surface can be modified only when
113/// it is in the `Exclusive` state, while in the `Shared` state it only allows read-only access.
114#[derive(Debug, Clone)]
115pub struct ImageSurface<T> {
116    surface: cairo::ImageSurface,
117
118    data_ptr: NonNull<u8>, // *const.
119    width: i32,
120    height: i32,
121    stride: isize,
122
123    surface_type: SurfaceType,
124
125    _state: PhantomData<T>,
126}
127
128#[derive(Debug, Clone)]
129pub struct Shared;
130
131/// Shared state of `ImageSurface`
132pub type SharedImageSurface = ImageSurface<Shared>;
133
134#[derive(Debug, Clone)]
135pub struct Exclusive;
136
137/// Exclusive state of `ImageSurface`
138pub type ExclusiveImageSurface = ImageSurface<Exclusive>;
139
140// The access is read-only, the ref-counting on an `cairo::ImageSurface` is atomic.
141unsafe impl Sync for SharedImageSurface {}
142
143/// A compile-time blur direction variable.
144pub trait BlurDirection {
145    const IS_VERTICAL: bool;
146}
147
148/// Vertical blur direction.
149pub enum Vertical {}
150/// Horizontal blur direction.
151pub enum Horizontal {}
152
153impl BlurDirection for Vertical {
154    const IS_VERTICAL: bool = true;
155}
156
157impl BlurDirection for Horizontal {
158    const IS_VERTICAL: bool = false;
159}
160
161/// A compile-time alpha-only marker variable.
162pub trait IsAlphaOnly {
163    const IS_ALPHA_ONLY: bool;
164}
165
166/// Alpha-only.
167pub enum AlphaOnly {}
168/// Not alpha-only.
169pub enum NotAlphaOnly {}
170
171/// Iterator over the rows of a `SharedImageSurface`.
172pub struct Rows<'a> {
173    surface: &'a SharedImageSurface,
174    next_row: i32,
175}
176
177/// Iterator over the mutable rows of an `ExclusiveImageSurface`.
178pub struct RowsMut<'a> {
179    // Keep an ImageSurfaceData here instead of a raw mutable pointer to the bytes,
180    // so that the ImageSurfaceData will mark the surface as dirty when it is dropped.
181    data: cairo::ImageSurfaceData<'a>,
182
183    width: i32,
184    height: i32,
185    stride: i32,
186
187    next_row: i32,
188}
189
190impl IsAlphaOnly for AlphaOnly {
191    const IS_ALPHA_ONLY: bool = true;
192}
193
194impl IsAlphaOnly for NotAlphaOnly {
195    const IS_ALPHA_ONLY: bool = false;
196}
197
198impl<T> ImageSurface<T> {
199    /// Returns the surface width.
200    #[inline]
201    pub fn width(&self) -> i32 {
202        self.width
203    }
204
205    /// Returns the surface height.
206    #[inline]
207    pub fn height(&self) -> i32 {
208        self.height
209    }
210
211    /// Returns the surface stride.
212    #[inline]
213    pub fn stride(&self) -> isize {
214        self.stride
215    }
216}
217
218impl ImageSurface<Shared> {
219    /// Creates a `SharedImageSurface` from a unique `cairo::ImageSurface`.
220    ///
221    /// # Panics
222    /// Panics if the surface format isn't `ARgb32` and if the surface is not unique, that is, its
223    /// reference count isn't 1.
224    #[inline]
225    pub fn wrap(
226        surface: cairo::ImageSurface,
227        surface_type: SurfaceType,
228    ) -> Result<SharedImageSurface, cairo::Error> {
229        // get_pixel() assumes ARgb32.
230        assert_eq!(surface.format(), cairo::Format::ARgb32);
231
232        let reference_count =
233            unsafe { cairo::ffi::cairo_surface_get_reference_count(surface.to_raw_none()) };
234        assert_eq!(reference_count, 1);
235
236        let (width, height) = (surface.width(), surface.height());
237
238        // Cairo allows zero-sized surfaces, but it does malloc(0), whose result
239        // is implementation-defined.  So, we can't assume NonNull below.  This is
240        // why we disallow zero-sized surfaces here.
241        if !(width > 0 && height > 0) {
242            return Err(cairo::Error::InvalidSize);
243        }
244
245        surface.flush();
246
247        let data_ptr = NonNull::new(unsafe {
248            cairo::ffi::cairo_image_surface_get_data(surface.to_raw_none())
249        })
250        .unwrap();
251
252        let stride = surface.stride() as isize;
253
254        Ok(SharedImageSurface {
255            surface,
256            data_ptr,
257            width,
258            height,
259            stride,
260            surface_type,
261            _state: PhantomData,
262        })
263    }
264
265    /// Creates a `SharedImageSurface` copying from a `cairo::ImageSurface`, even if it
266    /// does not have a reference count of 1.
267    #[inline]
268    pub fn copy_from_surface(surface: &cairo::ImageSurface) -> Result<Self, cairo::Error> {
269        let copy =
270            cairo::ImageSurface::create(cairo::Format::ARgb32, surface.width(), surface.height())?;
271
272        {
273            let cr = cairo::Context::new(&copy)?;
274            cr.set_source_surface(surface, 0f64, 0f64)?;
275            cr.paint()?;
276        }
277
278        SharedImageSurface::wrap(copy, SurfaceType::SRgb)
279    }
280
281    /// Creates an empty `SharedImageSurface` of the given size and `type`.
282    #[inline]
283    pub fn empty(width: i32, height: i32, surface_type: SurfaceType) -> Result<Self, cairo::Error> {
284        let s = cairo::ImageSurface::create(cairo::Format::ARgb32, width, height)?;
285
286        SharedImageSurface::wrap(s, surface_type)
287    }
288
289    /// Converts this `SharedImageSurface` back into a Cairo image surface.
290    #[inline]
291    pub fn into_image_surface(self) -> Result<cairo::ImageSurface, cairo::Error> {
292        let reference_count =
293            unsafe { cairo::ffi::cairo_surface_get_reference_count(self.surface.to_raw_none()) };
294
295        if reference_count == 1 {
296            Ok(self.surface)
297        } else {
298            // If there are any other references, copy the underlying surface.
299            self.copy_surface(IRect::from_size(self.width, self.height))
300        }
301    }
302
303    pub fn from_image(
304        image: &image::DynamicImage,
305        content_type: Option<&str>,
306        mime_data: Option<Vec<u8>>,
307    ) -> Result<SharedImageSurface, cairo::Error> {
308        let rgba_image = image.to_rgba8();
309
310        let width = i32(rgba_image.width()).map_err(|_| cairo::Error::InvalidSize)?;
311        let height = i32(rgba_image.height()).map_err(|_| cairo::Error::InvalidSize)?;
312
313        let mut surf = ExclusiveImageSurface::new(width, height, SurfaceType::SRgb)?;
314
315        rgba_image
316            .rows()
317            .zip(surf.rows_mut())
318            .flat_map(|(src_row, dest_row)| src_row.zip(dest_row.iter_mut()))
319            .for_each(|(src, dest)| *dest = src.to_pixel().premultiply().to_cairo_argb());
320
321        if let (Some(content_type), Some(bytes)) = (content_type, mime_data) {
322            surf.surface.set_mime_data(content_type, bytes)?;
323        }
324
325        surf.share()
326    }
327
328    /// Returns `true` if the surface contains meaningful data only in the alpha channel.
329    #[inline]
330    fn is_alpha_only(&self) -> bool {
331        self.surface_type == SurfaceType::AlphaOnly
332    }
333
334    /// Returns the type of this surface.
335    #[inline]
336    pub fn surface_type(&self) -> SurfaceType {
337        self.surface_type
338    }
339
340    /// Retrieves the pixel value at the given coordinates.
341    #[inline]
342    pub fn get_pixel(&self, x: u32, y: u32) -> Pixel {
343        assert!(x < self.width as u32);
344        assert!(y < self.height as u32);
345
346        #[allow(clippy::cast_ptr_alignment)]
347        let value = unsafe {
348            *(self
349                .data_ptr
350                .as_ptr()
351                .offset(y as isize * self.stride + x as isize * 4) as *const u32)
352        };
353
354        Pixel::from_u32(value)
355    }
356
357    /// Retrieves the pixel value by offset into the pixel data array.
358    #[inline]
359    pub fn get_pixel_by_offset(&self, offset: isize) -> Pixel {
360        assert!(offset < self.stride * self.height as isize);
361
362        #[allow(clippy::cast_ptr_alignment)]
363        let value = unsafe { *(self.data_ptr.as_ptr().offset(offset) as *const u32) };
364        Pixel::from_u32(value)
365    }
366
367    /// Calls `set_source_surface()` on the given Cairo context.
368    #[inline]
369    pub fn set_as_source_surface(
370        &self,
371        cr: &cairo::Context,
372        x: f64,
373        y: f64,
374    ) -> Result<(), cairo::Error> {
375        cr.set_source_surface(&self.surface, x, y)
376    }
377
378    /// Creates a Cairo surface pattern from the surface
379    pub fn to_cairo_pattern(&self) -> cairo::SurfacePattern {
380        cairo::SurfacePattern::create(&self.surface)
381    }
382
383    /// Returns a new `cairo::ImageSurface` with the same contents as the one stored in this
384    /// `SharedImageSurface` within the given bounds.
385    fn copy_surface(&self, bounds: IRect) -> Result<cairo::ImageSurface, cairo::Error> {
386        let output_surface =
387            cairo::ImageSurface::create(cairo::Format::ARgb32, self.width, self.height)?;
388
389        let cr = cairo::Context::new(&output_surface)?;
390        let r = cairo::Rectangle::from(bounds);
391        cr.rectangle(r.x(), r.y(), r.width(), r.height());
392        cr.clip();
393
394        cr.set_source_surface(&self.surface, 0f64, 0f64)?;
395        cr.paint()?;
396
397        Ok(output_surface)
398    }
399
400    /// Scales the given surface by `x` and `y` into a surface `width`×`height` in size, clipped by
401    /// `bounds`.
402    pub fn scale_to(
403        &self,
404        width: i32,
405        height: i32,
406        bounds: IRect,
407        x: f64,
408        y: f64,
409    ) -> Result<SharedImageSurface, cairo::Error> {
410        let output_surface = cairo::ImageSurface::create(cairo::Format::ARgb32, width, height)?;
411
412        {
413            let cr = cairo::Context::new(&output_surface)?;
414            let r = cairo::Rectangle::from(bounds);
415            cr.rectangle(r.x(), r.y(), r.width(), r.height());
416            cr.clip();
417
418            cr.scale(x, y);
419            self.set_as_source_surface(&cr, 0.0, 0.0)?;
420            cr.paint()?;
421        }
422
423        SharedImageSurface::wrap(output_surface, self.surface_type)
424    }
425
426    /// Returns a scaled version of a surface and bounds.
427    #[inline]
428    pub fn scale(
429        &self,
430        bounds: IRect,
431        x: f64,
432        y: f64,
433    ) -> Result<(SharedImageSurface, IRect), cairo::Error> {
434        let new_width = (f64::from(self.width) * x).ceil() as i32;
435        let new_height = (f64::from(self.height) * y).ceil() as i32;
436        let new_bounds = bounds.scale(x, y);
437
438        Ok((
439            self.scale_to(new_width, new_height, new_bounds, x, y)?,
440            new_bounds,
441        ))
442    }
443
444    /// Returns a surface with black background and alpha channel matching this surface.
445    pub fn extract_alpha(&self, bounds: IRect) -> Result<SharedImageSurface, cairo::Error> {
446        let mut output_surface =
447            cairo::ImageSurface::create(cairo::Format::ARgb32, self.width, self.height)?;
448
449        let output_stride = output_surface.stride() as usize;
450        {
451            let mut output_data = output_surface.data().unwrap();
452
453            for (x, y, Pixel { a, .. }) in Pixels::within(self, bounds) {
454                let output_pixel = Pixel {
455                    r: 0,
456                    g: 0,
457                    b: 0,
458                    a,
459                };
460                output_data.set_pixel(output_stride, output_pixel, x, y);
461            }
462        }
463
464        SharedImageSurface::wrap(output_surface, SurfaceType::AlphaOnly)
465    }
466
467    /// Returns a surface whose alpha channel for each pixel is equal to the
468    /// luminance of that pixel's unpremultiplied RGB values.  The resulting
469    /// surface's RGB values are not meanignful; only the alpha channel has
470    /// useful luminance data.
471    ///
472    /// This is to get a mask suitable for use with cairo_mask_surface().
473    pub fn to_luminance_mask(&self) -> Result<SharedImageSurface, cairo::Error> {
474        let bounds = IRect::from_size(self.width, self.height);
475
476        let mut output_surface =
477            cairo::ImageSurface::create(cairo::Format::ARgb32, self.width, self.height)?;
478
479        let stride = output_surface.stride() as usize;
480        {
481            let mut data = output_surface.data().unwrap();
482
483            for (x, y, pixel) in Pixels::within(self, bounds) {
484                data.set_pixel(stride, pixel.to_luminance_mask(), x, y);
485            }
486        }
487
488        SharedImageSurface::wrap(output_surface, self.surface_type)
489    }
490
491    /// Returns a surface with pre-multiplication of color values undone.
492    ///
493    /// HACK: this is storing unpremultiplied pixels in an ARGB32 image surface (which is supposed
494    /// to be premultiplied pixels).
495    pub fn unpremultiply(&self, bounds: IRect) -> Result<SharedImageSurface, cairo::Error> {
496        // Unpremultiplication doesn't affect the alpha channel.
497        if self.is_alpha_only() {
498            return Ok(self.clone());
499        }
500
501        let mut output_surface =
502            cairo::ImageSurface::create(cairo::Format::ARgb32, self.width, self.height)?;
503
504        let stride = output_surface.stride() as usize;
505        {
506            let mut data = output_surface.data().unwrap();
507
508            for (x, y, pixel) in Pixels::within(self, bounds) {
509                data.set_pixel(stride, pixel.unpremultiply(), x, y);
510            }
511        }
512
513        SharedImageSurface::wrap(output_surface, self.surface_type)
514    }
515
516    /// Converts the surface to the linear sRGB color space.
517    #[inline]
518    pub fn to_linear_rgb(&self, bounds: IRect) -> Result<SharedImageSurface, cairo::Error> {
519        match self.surface_type {
520            SurfaceType::LinearRgb | SurfaceType::AlphaOnly => Ok(self.clone()),
521            _ => srgb::linearize_surface(self, bounds),
522        }
523    }
524
525    /// Converts the surface to the sRGB color space.
526    #[inline]
527    pub fn to_srgb(&self, bounds: IRect) -> Result<SharedImageSurface, cairo::Error> {
528        match self.surface_type {
529            SurfaceType::SRgb | SurfaceType::AlphaOnly => Ok(self.clone()),
530            _ => srgb::unlinearize_surface(self, bounds),
531        }
532    }
533
534    /// Performs a convolution.
535    ///
536    /// Note that `kernel` is rotated 180 degrees.
537    ///
538    /// The `target` parameter determines the position of the kernel relative to each pixel of the
539    /// image. The value of `(0, 0)` indicates that the top left pixel of the (180-degrees-rotated)
540    /// kernel corresponds to the current pixel, and the rest of the kernel is to the right and
541    /// bottom of the pixel. The value of `(cols / 2, rows / 2)` centers a kernel with an odd
542    /// number of rows and columns.
543    ///
544    /// # Panics
545    /// Panics if `kernel` has zero rows or columns.
546    pub fn convolve<R: Dim, C: Dim, S: Storage<f64, R, C>>(
547        &self,
548        bounds: IRect,
549        target: (i32, i32),
550        kernel: &Matrix<f64, R, C, S>,
551        edge_mode: EdgeMode,
552    ) -> Result<SharedImageSurface, cairo::Error> {
553        assert!(kernel.nrows() >= 1);
554        assert!(kernel.ncols() >= 1);
555
556        let mut output_surface =
557            cairo::ImageSurface::create(cairo::Format::ARgb32, self.width, self.height)?;
558
559        let output_stride = output_surface.stride() as usize;
560        {
561            let mut output_data = output_surface.data().unwrap();
562
563            if self.is_alpha_only() {
564                for (x, y, _pixel) in Pixels::within(self, bounds) {
565                    let kernel_bounds = IRect::new(
566                        x as i32 - target.0,
567                        y as i32 - target.1,
568                        x as i32 - target.0 + kernel.ncols() as i32,
569                        y as i32 - target.1 + kernel.nrows() as i32,
570                    );
571
572                    let mut a = 0.0;
573
574                    for (x, y, pixel) in
575                        PixelRectangle::within(self, bounds, kernel_bounds, edge_mode)
576                    {
577                        let kernel_x = (kernel_bounds.x1 - x - 1) as usize;
578                        let kernel_y = (kernel_bounds.y1 - y - 1) as usize;
579                        let factor = kernel[(kernel_y, kernel_x)];
580
581                        a += f64::from(pixel.a) * factor;
582                    }
583
584                    let convert = |x: f64| (clamp(x, 0.0, 255.0) + 0.5) as u8;
585
586                    let output_pixel = Pixel {
587                        r: 0,
588                        g: 0,
589                        b: 0,
590                        a: convert(a),
591                    };
592
593                    output_data.set_pixel(output_stride, output_pixel, x, y);
594                }
595            } else {
596                for (x, y, _pixel) in Pixels::within(self, bounds) {
597                    let kernel_bounds = IRect::new(
598                        x as i32 - target.0,
599                        y as i32 - target.1,
600                        x as i32 - target.0 + kernel.ncols() as i32,
601                        y as i32 - target.1 + kernel.nrows() as i32,
602                    );
603
604                    let mut r = 0.0;
605                    let mut g = 0.0;
606                    let mut b = 0.0;
607                    let mut a = 0.0;
608
609                    for (x, y, pixel) in
610                        PixelRectangle::within(self, bounds, kernel_bounds, edge_mode)
611                    {
612                        let kernel_x = (kernel_bounds.x1 - x - 1) as usize;
613                        let kernel_y = (kernel_bounds.y1 - y - 1) as usize;
614                        let factor = kernel[(kernel_y, kernel_x)];
615
616                        r += f64::from(pixel.r) * factor;
617                        g += f64::from(pixel.g) * factor;
618                        b += f64::from(pixel.b) * factor;
619                        a += f64::from(pixel.a) * factor;
620                    }
621
622                    let convert = |x: f64| (clamp(x, 0.0, 255.0) + 0.5) as u8;
623
624                    let output_pixel = Pixel {
625                        r: convert(r),
626                        g: convert(g),
627                        b: convert(b),
628                        a: convert(a),
629                    };
630
631                    output_data.set_pixel(output_stride, output_pixel, x, y);
632                }
633            }
634        }
635
636        SharedImageSurface::wrap(output_surface, self.surface_type)
637    }
638
639    /// Performs a horizontal or vertical box blur.
640    ///
641    /// The `target` parameter determines the position of the kernel relative to each pixel of the
642    /// image. The value of `0` indicates that the first pixel of the kernel corresponds to the
643    /// current pixel, and the rest of the kernel is to the right or bottom of the pixel. The value
644    /// of `kernel_size / 2` centers a kernel with an odd size.
645    ///
646    /// # Panics
647    /// Panics if `kernel_size` is `0` or if `target >= kernel_size`.
648    // This is public (and not inlined into box_blur()) for the purpose of accessing it from the
649    // benchmarks.
650    pub fn box_blur_loop<B: BlurDirection, A: IsAlphaOnly>(
651        &self,
652        output_surface: &mut cairo::ImageSurface,
653        bounds: IRect,
654        kernel_size: usize,
655        target: usize,
656    ) {
657        assert_ne!(kernel_size, 0);
658        assert!(target < kernel_size);
659        assert_eq!(self.is_alpha_only(), A::IS_ALPHA_ONLY);
660
661        {
662            // The following code is needed for a parallel implementation of the blur loop. The
663            // blurring is done either for each row or for each column of pixels, depending on the
664            // value of `vertical`, independently of the others. Naturally, we want to run the
665            // outer loop on a thread pool.
666            //
667            // The case of `vertical == false` is simple since the input image slice can be
668            // partitioned into chunks for each row of pixels and processed in parallel with rayon.
669            // The case of `vertical == true`, however, is more involved because we can't just make
670            // mutable slices for all pixel columns (they would be overlapping which is forbidden
671            // by the aliasing rules).
672            //
673            // This is where the following struct comes into play: it stores a sub-slice of the
674            // pixel data and can be split at any row or column into two parts (similar to
675            // slice::split_at_mut()).
676            struct UnsafeSendPixelData<'a> {
677                width: u32,
678                height: u32,
679                stride: isize,
680                ptr: NonNull<u8>,
681                _marker: PhantomData<&'a mut ()>,
682            }
683
684            unsafe impl<'a> Send for UnsafeSendPixelData<'a> {}
685
686            impl<'a> UnsafeSendPixelData<'a> {
687                /// Creates a new `UnsafeSendPixelData`.
688                ///
689                /// # Safety
690                /// You must call `cairo_surface_mark_dirty()` on the surface once all instances of
691                /// `UnsafeSendPixelData` are dropped to make sure the pixel changes are committed
692                /// to Cairo.
693                #[inline]
694                unsafe fn new(surface: &mut cairo::ImageSurface) -> Self {
695                    assert_eq!(surface.format(), cairo::Format::ARgb32);
696                    let ptr = surface.data().unwrap().as_mut_ptr();
697
698                    Self {
699                        width: surface.width() as u32,
700                        height: surface.height() as u32,
701                        stride: surface.stride() as isize,
702                        ptr: NonNull::new(ptr).unwrap(),
703                        _marker: PhantomData,
704                    }
705                }
706
707                /// Sets a pixel value at the given coordinates.
708                #[inline]
709                fn set_pixel(&mut self, pixel: Pixel, x: u32, y: u32) {
710                    assert!(x < self.width);
711                    assert!(y < self.height);
712
713                    let value = pixel.to_u32();
714
715                    #[allow(clippy::cast_ptr_alignment)]
716                    unsafe {
717                        let ptr = self
718                            .ptr
719                            .as_ptr()
720                            .offset(y as isize * self.stride + x as isize * 4)
721                            as *mut u32;
722                        *ptr = value;
723                    }
724                }
725
726                /// Splits this `UnsafeSendPixelData` into two at the given row.
727                ///
728                /// The first one contains rows `0..index` (`index` not included) and the second one
729                /// contains rows `index..height`.
730                #[inline]
731                fn split_at_row(self, index: u32) -> (Self, Self) {
732                    assert!(index <= self.height);
733
734                    (
735                        UnsafeSendPixelData {
736                            width: self.width,
737                            height: index,
738                            stride: self.stride,
739                            ptr: self.ptr,
740                            _marker: PhantomData,
741                        },
742                        UnsafeSendPixelData {
743                            width: self.width,
744                            height: self.height - index,
745                            stride: self.stride,
746                            ptr: NonNull::new(unsafe {
747                                self.ptr.as_ptr().offset(index as isize * self.stride)
748                            })
749                            .unwrap(),
750                            _marker: PhantomData,
751                        },
752                    )
753                }
754
755                /// Splits this `UnsafeSendPixelData` into two at the given column.
756                ///
757                /// The first one contains columns `0..index` (`index` not included) and the second
758                /// one contains columns `index..width`.
759                #[inline]
760                fn split_at_column(self, index: u32) -> (Self, Self) {
761                    assert!(index <= self.width);
762
763                    (
764                        UnsafeSendPixelData {
765                            width: index,
766                            height: self.height,
767                            stride: self.stride,
768                            ptr: self.ptr,
769                            _marker: PhantomData,
770                        },
771                        UnsafeSendPixelData {
772                            width: self.width - index,
773                            height: self.height,
774                            stride: self.stride,
775                            ptr: NonNull::new(unsafe {
776                                self.ptr.as_ptr().offset(index as isize * 4)
777                            })
778                            .unwrap(),
779                            _marker: PhantomData,
780                        },
781                    )
782                }
783            }
784
785            let output_data = unsafe { UnsafeSendPixelData::new(output_surface) };
786
787            // Shift is target into the opposite direction.
788            let shift = (kernel_size - target) as i32;
789            let target = target as i32;
790
791            // Convert to f64 once since we divide by it.
792            let kernel_size_f64 = kernel_size as f64;
793            let compute = |x: u32| (f64::from(x) / kernel_size_f64 + 0.5) as u8;
794
795            // Depending on `vertical`, we're blurring either horizontally line-by-line, or
796            // vertically column-by-column. In the code below, the main axis is the axis along
797            // which the blurring happens (so if `vertical` is false, the main axis is the
798            // horizontal axis). The other axis is the outer loop axis. The code uses `i` and `j`
799            // for the other axis and main axis coordinates, respectively.
800            let (main_axis_min, main_axis_max, other_axis_min, other_axis_max) = if B::IS_VERTICAL {
801                (bounds.y0, bounds.y1, bounds.x0, bounds.x1)
802            } else {
803                (bounds.x0, bounds.x1, bounds.y0, bounds.y1)
804            };
805
806            // Helper function for getting the pixels.
807            let pixel = |i, j| {
808                let (x, y) = if B::IS_VERTICAL { (i, j) } else { (j, i) };
809
810                self.get_pixel(x as u32, y as u32)
811            };
812
813            // The following loop assumes the first row or column of `output_data` is the first row
814            // or column inside `bounds`.
815            let mut output_data = if B::IS_VERTICAL {
816                output_data.split_at_column(bounds.x0 as u32).1
817            } else {
818                output_data.split_at_row(bounds.y0 as u32).1
819            };
820
821            rayon::scope(|s| {
822                for i in other_axis_min..other_axis_max {
823                    // Split off one row or column and launch its processing on another thread.
824                    // Thanks to the initial split before the loop, there's no special case for the
825                    // very first split.
826                    let (mut current, remaining) = if B::IS_VERTICAL {
827                        output_data.split_at_column(1)
828                    } else {
829                        output_data.split_at_row(1)
830                    };
831
832                    output_data = remaining;
833
834                    s.spawn(move |_| {
835                        // Helper function for setting the pixels.
836                        let mut set_pixel = |j, pixel| {
837                            // We're processing rows or columns one-by-one, so the other coordinate
838                            // is always 0.
839                            let (x, y) = if B::IS_VERTICAL { (0, j) } else { (j, 0) };
840                            current.set_pixel(pixel, x, y);
841                        };
842
843                        // The idea is that since all weights of the box blur kernel are equal, for
844                        // each step along the main axis, instead of recomputing the full sum, we
845                        // can take the previous sum, subtract the "oldest" pixel value and add the
846                        // "newest" pixel value.
847                        //
848                        // The sum is u32 so that it can fit MAXIMUM_KERNEL_SIZE * 255.
849                        let mut sum_r = 0;
850                        let mut sum_g = 0;
851                        let mut sum_b = 0;
852                        let mut sum_a = 0;
853
854                        // The whole sum needs to be computed for the first pixel. However, we know
855                        // that values outside of bounds are transparent, so the loop starts on the
856                        // first pixel in bounds.
857                        for j in main_axis_min..min(main_axis_max, main_axis_min + shift) {
858                            let Pixel { r, g, b, a } = pixel(i, j);
859
860                            if !A::IS_ALPHA_ONLY {
861                                sum_r += u32::from(r);
862                                sum_g += u32::from(g);
863                                sum_b += u32::from(b);
864                            }
865
866                            sum_a += u32::from(a);
867                        }
868
869                        set_pixel(
870                            main_axis_min as u32,
871                            Pixel {
872                                r: compute(sum_r),
873                                g: compute(sum_g),
874                                b: compute(sum_b),
875                                a: compute(sum_a),
876                            },
877                        );
878
879                        // Now, go through all the other pixels.
880                        //
881                        // j - target - 1 >= main_axis_min
882                        // j >= main_axis_min + target + 1
883                        let start_subtracting_at = main_axis_min + target + 1;
884
885                        // j + shift - 1 < main_axis_max
886                        // j < main_axis_max - shift + 1
887                        let stop_adding_at = main_axis_max - shift + 1;
888
889                        for j in main_axis_min + 1..main_axis_max {
890                            if j >= start_subtracting_at {
891                                let old_pixel = pixel(i, j - target - 1);
892
893                                if !A::IS_ALPHA_ONLY {
894                                    sum_r -= u32::from(old_pixel.r);
895                                    sum_g -= u32::from(old_pixel.g);
896                                    sum_b -= u32::from(old_pixel.b);
897                                }
898
899                                sum_a -= u32::from(old_pixel.a);
900                            }
901
902                            if j < stop_adding_at {
903                                let new_pixel = pixel(i, j + shift - 1);
904
905                                if !A::IS_ALPHA_ONLY {
906                                    sum_r += u32::from(new_pixel.r);
907                                    sum_g += u32::from(new_pixel.g);
908                                    sum_b += u32::from(new_pixel.b);
909                                }
910
911                                sum_a += u32::from(new_pixel.a);
912                            }
913
914                            set_pixel(
915                                j as u32,
916                                Pixel {
917                                    r: compute(sum_r),
918                                    g: compute(sum_g),
919                                    b: compute(sum_b),
920                                    a: compute(sum_a),
921                                },
922                            );
923                        }
924                    });
925                }
926            });
927        }
928
929        // Don't forget to manually mark the surface as dirty (due to usage of
930        // `UnsafeSendPixelData`).
931        unsafe { cairo::ffi::cairo_surface_mark_dirty(output_surface.to_raw_none()) }
932    }
933
934    /// Performs a horizontal or vertical box blur.
935    ///
936    /// The `target` parameter determines the position of the kernel relative to each pixel of the
937    /// image. The value of `0` indicates that the first pixel of the kernel corresponds to the
938    /// current pixel, and the rest of the kernel is to the right or bottom of the pixel. The value
939    /// of `kernel_size / 2` centers a kernel with an odd size.
940    ///
941    /// # Panics
942    /// Panics if `kernel_size` is `0` or if `target >= kernel_size`.
943    #[inline]
944    pub fn box_blur<B: BlurDirection>(
945        &self,
946        bounds: IRect,
947        kernel_size: usize,
948        target: usize,
949    ) -> Result<SharedImageSurface, cairo::Error> {
950        let mut output_surface =
951            cairo::ImageSurface::create(cairo::Format::ARgb32, self.width, self.height)?;
952
953        if self.is_alpha_only() {
954            self.box_blur_loop::<B, AlphaOnly>(&mut output_surface, bounds, kernel_size, target);
955        } else {
956            self.box_blur_loop::<B, NotAlphaOnly>(&mut output_surface, bounds, kernel_size, target);
957        }
958
959        SharedImageSurface::wrap(output_surface, self.surface_type)
960    }
961
962    /// Fills the with a specified color.
963    #[inline]
964    pub fn flood(&self, bounds: IRect, color: Color) -> Result<SharedImageSurface, cairo::Error> {
965        let output_surface =
966            cairo::ImageSurface::create(cairo::Format::ARgb32, self.width, self.height)?;
967
968        let rgba = color_to_rgba(&color);
969
970        if rgba.alpha > 0.0 {
971            let cr = cairo::Context::new(&output_surface)?;
972            let r = cairo::Rectangle::from(bounds);
973            cr.rectangle(r.x(), r.y(), r.width(), r.height());
974            cr.clip();
975
976            set_source_color_on_cairo(&cr, &color);
977            cr.paint()?;
978        }
979
980        SharedImageSurface::wrap(output_surface, self.surface_type)
981    }
982
983    /// Offsets the image of the specified amount.
984    #[inline]
985    pub fn offset(
986        &self,
987        bounds: Rect,
988        dx: f64,
989        dy: f64,
990    ) -> Result<SharedImageSurface, cairo::Error> {
991        let output_surface =
992            cairo::ImageSurface::create(cairo::Format::ARgb32, self.width, self.height)?;
993
994        // output_bounds contains all pixels within bounds,
995        // for which (x - ox) and (y - oy) also lie within bounds.
996        if let Some(output_bounds) = bounds.translate((dx, dy)).intersection(&bounds) {
997            let cr = cairo::Context::new(&output_surface)?;
998            let r = cairo::Rectangle::from(output_bounds);
999            cr.rectangle(r.x(), r.y(), r.width(), r.height());
1000            cr.clip();
1001
1002            self.set_as_source_surface(&cr, dx, dy)?;
1003            cr.paint()?;
1004        }
1005
1006        SharedImageSurface::wrap(output_surface, self.surface_type)
1007    }
1008
1009    /// Returns a new surface of the same size, with the contents of the
1010    /// specified image, optionally transformed to match a given box
1011    #[inline]
1012    pub fn paint_image(
1013        &self,
1014        bounds: Rect,
1015        image: &SharedImageSurface,
1016        rect: Option<Rect>,
1017        interpolation: Interpolation,
1018    ) -> Result<SharedImageSurface, cairo::Error> {
1019        let output_surface =
1020            cairo::ImageSurface::create(cairo::Format::ARgb32, self.width, self.height)?;
1021
1022        if rect.is_none() || !rect.unwrap().is_empty() {
1023            let cr = cairo::Context::new(&output_surface)?;
1024            let r = cairo::Rectangle::from(bounds);
1025            cr.rectangle(r.x(), r.y(), r.width(), r.height());
1026            cr.clip();
1027
1028            image.set_as_source_surface(&cr, 0f64, 0f64)?;
1029
1030            if let Some(rect) = rect {
1031                let mut matrix = cairo::Matrix::new(
1032                    rect.width() / f64::from(image.width()),
1033                    0.0,
1034                    0.0,
1035                    rect.height() / f64::from(image.height()),
1036                    rect.x0,
1037                    rect.y0,
1038                );
1039                matrix.invert();
1040
1041                cr.source().set_matrix(matrix);
1042                cr.source().set_filter(cairo::Filter::from(interpolation));
1043            }
1044
1045            cr.paint()?;
1046        }
1047
1048        SharedImageSurface::wrap(output_surface, image.surface_type)
1049    }
1050
1051    /// Creates a new surface with the size and content specified in `bounds`
1052    ///
1053    /// # Panics
1054    /// Panics if `bounds` is an empty rectangle, since `SharedImageSurface` cannot
1055    /// represent zero-sized images.
1056    #[inline]
1057    pub fn tile(&self, bounds: IRect) -> Result<SharedImageSurface, cairo::Error> {
1058        // Cairo lets us create zero-sized surfaces, but the call to SharedImageSurface::wrap()
1059        // below will panic in that case.  So, disallow requesting a zero-sized subregion.
1060        assert!(!bounds.is_empty());
1061
1062        let output_surface =
1063            cairo::ImageSurface::create(cairo::Format::ARgb32, bounds.width(), bounds.height())?;
1064
1065        {
1066            let cr = cairo::Context::new(&output_surface)?;
1067            self.set_as_source_surface(&cr, f64::from(-bounds.x0), f64::from(-bounds.y0))?;
1068            cr.paint()?;
1069        }
1070
1071        SharedImageSurface::wrap(output_surface, self.surface_type)
1072    }
1073
1074    /// Returns a new surface of the same size, with the contents of the specified
1075    /// image repeated to fill the bounds and starting from the given position.
1076    #[inline]
1077    pub fn paint_image_tiled(
1078        &self,
1079        bounds: IRect,
1080        image: &SharedImageSurface,
1081        x: i32,
1082        y: i32,
1083    ) -> Result<SharedImageSurface, cairo::Error> {
1084        let output_surface =
1085            cairo::ImageSurface::create(cairo::Format::ARgb32, self.width, self.height)?;
1086
1087        {
1088            let cr = cairo::Context::new(&output_surface)?;
1089
1090            let ptn = image.to_cairo_pattern();
1091            ptn.set_extend(cairo::Extend::Repeat);
1092            let mut mat = cairo::Matrix::identity();
1093            mat.translate(f64::from(-x), f64::from(-y));
1094            ptn.set_matrix(mat);
1095
1096            let r = cairo::Rectangle::from(bounds);
1097            cr.rectangle(r.x(), r.y(), r.width(), r.height());
1098            cr.clip();
1099
1100            cr.set_source(&ptn)?;
1101            cr.paint()?;
1102        }
1103
1104        SharedImageSurface::wrap(output_surface, image.surface_type)
1105    }
1106
1107    /// Performs the combination of two input surfaces using Porter-Duff
1108    /// compositing operators.
1109    ///
1110    /// # Panics
1111    /// Panics if the two surface types are not compatible.
1112    #[inline]
1113    pub fn compose(
1114        &self,
1115        other: &SharedImageSurface,
1116        bounds: IRect,
1117        operator: Operator,
1118    ) -> Result<SharedImageSurface, cairo::Error> {
1119        let output_surface = other.copy_surface(bounds)?;
1120
1121        {
1122            let cr = cairo::Context::new(&output_surface)?;
1123            let r = cairo::Rectangle::from(bounds);
1124            cr.rectangle(r.x(), r.y(), r.width(), r.height());
1125            cr.clip();
1126
1127            self.set_as_source_surface(&cr, 0.0, 0.0)?;
1128            cr.set_operator(operator.into());
1129            cr.paint()?;
1130        }
1131
1132        SharedImageSurface::wrap(
1133            output_surface,
1134            self.surface_type.combine(other.surface_type),
1135        )
1136    }
1137
1138    /// Performs the combination of two input surfaces.
1139    ///
1140    /// Each pixel of the resulting image is computed using the following formula:
1141    /// `res = k1*i1*i2 + k2*i1 + k3*i2 + k4`
1142    ///
1143    /// # Panics
1144    /// Panics if the two surface types are not compatible.
1145    #[inline]
1146    pub fn compose_arithmetic(
1147        &self,
1148        other: &SharedImageSurface,
1149        bounds: IRect,
1150        k1: f64,
1151        k2: f64,
1152        k3: f64,
1153        k4: f64,
1154    ) -> Result<SharedImageSurface, cairo::Error> {
1155        let mut output_surface = ExclusiveImageSurface::new(
1156            self.width,
1157            self.height,
1158            self.surface_type.combine(other.surface_type),
1159        )?;
1160
1161        composite_arithmetic(self, other, &mut output_surface, bounds, k1, k2, k3, k4);
1162
1163        output_surface.share()
1164    }
1165
1166    pub fn rows(&self) -> Rows<'_> {
1167        Rows {
1168            surface: self,
1169            next_row: 0,
1170        }
1171    }
1172}
1173
1174impl<'a> Iterator for Rows<'a> {
1175    type Item = &'a [CairoARGB];
1176
1177    fn next(&mut self) -> Option<Self::Item> {
1178        if self.next_row == self.surface.height {
1179            return None;
1180        }
1181
1182        let row = self.next_row;
1183
1184        self.next_row += 1;
1185
1186        // SAFETY: this code assumes that cairo image surface data is correctly
1187        // aligned for u32. This assumption is justified by the Cairo docs,
1188        // which say this:
1189        //
1190        // https://cairographics.org/manual/cairo-Image-Surfaces.html#cairo-image-surface-create-for-data
1191        //
1192        // > This pointer must be suitably aligned for any kind of variable,
1193        // > (for example, a pointer returned by malloc).
1194        unsafe {
1195            let row_ptr: *const u8 = self
1196                .surface
1197                .data_ptr
1198                .as_ptr()
1199                .offset(row as isize * self.surface.stride);
1200            let row_of_u32: &[u32] =
1201                slice::from_raw_parts(row_ptr as *const u32, self.surface.width as usize);
1202            let pixels = row_of_u32.as_cairo_argb();
1203            assert!(pixels.len() == self.surface.width as usize);
1204            Some(pixels)
1205        }
1206    }
1207}
1208
1209impl<'a> Iterator for RowsMut<'a> {
1210    type Item = &'a mut [CairoARGB];
1211
1212    fn next(&mut self) -> Option<Self::Item> {
1213        if self.next_row == self.height {
1214            return None;
1215        }
1216
1217        let row = self.next_row as usize;
1218
1219        self.next_row += 1;
1220
1221        // SAFETY: this code assumes that cairo image surface data is correctly
1222        // aligned for u32. This assumption is justified by the Cairo docs,
1223        // which say this:
1224        //
1225        // https://cairographics.org/manual/cairo-Image-Surfaces.html#cairo-image-surface-create-for-data
1226        //
1227        // > This pointer must be suitably aligned for any kind of variable,
1228        // > (for example, a pointer returned by malloc).
1229        unsafe {
1230            // We do this with raw pointers, instead of re-slicing the &mut self.data[....],
1231            // because with the latter we can't synthesize an appropriate lifetime for
1232            // the return value.
1233
1234            let data_ptr = self.data.as_mut_ptr();
1235            let row_ptr: *mut u8 = data_ptr.offset(row as isize * self.stride as isize);
1236            let row_of_u32: &mut [u32] =
1237                slice::from_raw_parts_mut(row_ptr as *mut u32, self.width as usize);
1238            let pixels = row_of_u32.as_cairo_argb_mut();
1239            assert!(pixels.len() == self.width as usize);
1240            Some(pixels)
1241        }
1242    }
1243}
1244
1245/// Performs the arithmetic composite operation. Public for benchmarking.
1246#[inline]
1247pub fn composite_arithmetic(
1248    surface1: &SharedImageSurface,
1249    surface2: &SharedImageSurface,
1250    output_surface: &mut ExclusiveImageSurface,
1251    bounds: IRect,
1252    k1: f64,
1253    k2: f64,
1254    k3: f64,
1255    k4: f64,
1256) {
1257    output_surface.modify(&mut |data, stride| {
1258        for (x, y, pixel, pixel_2) in
1259            Pixels::within(surface1, bounds).map(|(x, y, p)| (x, y, p, surface2.get_pixel(x, y)))
1260        {
1261            let i1a = f64::from(pixel.a) / 255f64;
1262            let i2a = f64::from(pixel_2.a) / 255f64;
1263            let oa = k1 * i1a * i2a + k2 * i1a + k3 * i2a + k4;
1264            let oa = clamp(oa, 0f64, 1f64);
1265
1266            // Contents of image surfaces are transparent by default, so if the resulting pixel is
1267            // transparent there's no need to do anything.
1268            if oa > 0f64 {
1269                let compute = |i1, i2| {
1270                    let i1 = f64::from(i1) / 255f64;
1271                    let i2 = f64::from(i2) / 255f64;
1272
1273                    let o = k1 * i1 * i2 + k2 * i1 + k3 * i2 + k4;
1274                    let o = clamp(o, 0f64, oa);
1275
1276                    ((o * 255f64) + 0.5) as u8
1277                };
1278
1279                let output_pixel = Pixel {
1280                    r: compute(pixel.r, pixel_2.r),
1281                    g: compute(pixel.g, pixel_2.g),
1282                    b: compute(pixel.b, pixel_2.b),
1283                    a: ((oa * 255f64) + 0.5) as u8,
1284                };
1285
1286                data.set_pixel(stride, output_pixel, x, y);
1287            }
1288        }
1289    });
1290}
1291
1292impl ImageSurface<Exclusive> {
1293    #[inline]
1294    pub fn new(
1295        width: i32,
1296        height: i32,
1297        surface_type: SurfaceType,
1298    ) -> Result<ExclusiveImageSurface, cairo::Error> {
1299        let surface = cairo::ImageSurface::create(cairo::Format::ARgb32, width, height)?;
1300
1301        let (width, height) = (surface.width(), surface.height());
1302
1303        // Cairo allows zero-sized surfaces, but it does malloc(0), whose result
1304        // is implementation-defined.  So, we can't assume NonNull below.  This is
1305        // why we disallow zero-sized surfaces here.
1306        if !(width > 0 && height > 0) {
1307            return Err(cairo::Error::InvalidSize);
1308        }
1309
1310        let data_ptr = NonNull::new(unsafe {
1311            cairo::ffi::cairo_image_surface_get_data(surface.to_raw_none())
1312        })
1313        .unwrap();
1314
1315        let stride = surface.stride() as isize;
1316
1317        Ok(ExclusiveImageSurface {
1318            surface,
1319            data_ptr,
1320            width,
1321            height,
1322            stride,
1323            surface_type,
1324            _state: PhantomData,
1325        })
1326    }
1327
1328    #[inline]
1329    pub fn share(self) -> Result<SharedImageSurface, cairo::Error> {
1330        SharedImageSurface::wrap(self.surface, self.surface_type)
1331    }
1332
1333    /// Raw access to the image data as a slice
1334    #[inline]
1335    pub fn data(&mut self) -> cairo::ImageSurfaceData<'_> {
1336        self.surface.data().unwrap()
1337    }
1338
1339    /// Modify the image data
1340    #[inline]
1341    pub fn modify(&mut self, draw_fn: &mut dyn FnMut(&mut cairo::ImageSurfaceData<'_>, usize)) {
1342        let stride = self.stride() as usize;
1343        let mut data = self.data();
1344
1345        draw_fn(&mut data, stride)
1346    }
1347
1348    /// Draw on the surface using cairo
1349    #[inline]
1350    pub fn draw(
1351        &mut self,
1352        draw_fn: &mut dyn FnMut(cairo::Context) -> Result<(), Box<InternalRenderingError>>,
1353    ) -> Result<(), Box<InternalRenderingError>> {
1354        let cr = cairo::Context::new(&self.surface)?;
1355        draw_fn(cr)
1356    }
1357
1358    pub fn rows_mut(&mut self) -> RowsMut<'_> {
1359        let width = self.surface.width();
1360        let height = self.surface.height();
1361        let stride = self.surface.stride();
1362
1363        let data = self.surface.data().unwrap();
1364
1365        RowsMut {
1366            width,
1367            height,
1368            stride,
1369            data,
1370            next_row: 0,
1371        }
1372    }
1373}
1374
1375impl From<Operator> for cairo::Operator {
1376    fn from(op: Operator) -> cairo::Operator {
1377        use cairo::Operator as Cairo;
1378        use Operator::*;
1379
1380        match op {
1381            Over => Cairo::Over,
1382            In => Cairo::In,
1383            Out => Cairo::Out,
1384            Atop => Cairo::Atop,
1385            Xor => Cairo::Xor,
1386            Multiply => Cairo::Multiply,
1387            Screen => Cairo::Screen,
1388            Darken => Cairo::Darken,
1389            Lighten => Cairo::Lighten,
1390            Overlay => Cairo::Overlay,
1391            ColorDodge => Cairo::ColorDodge,
1392            ColorBurn => Cairo::ColorBurn,
1393            HardLight => Cairo::HardLight,
1394            SoftLight => Cairo::SoftLight,
1395            Difference => Cairo::Difference,
1396            Exclusion => Cairo::Exclusion,
1397            HslHue => Cairo::HslHue,
1398            HslSaturation => Cairo::HslSaturation,
1399            HslColor => Cairo::HslColor,
1400            HslLuminosity => Cairo::HslLuminosity,
1401        }
1402    }
1403}
1404
1405#[cfg(test)]
1406mod tests {
1407    use super::*;
1408    use crate::surface_utils::iterators::Pixels;
1409
1410    #[test]
1411    fn test_extract_alpha() {
1412        const WIDTH: i32 = 32;
1413        const HEIGHT: i32 = 64;
1414
1415        let bounds = IRect::new(8, 24, 16, 48);
1416        let full_bounds = IRect::from_size(WIDTH, HEIGHT);
1417
1418        let mut surface = ExclusiveImageSurface::new(WIDTH, HEIGHT, SurfaceType::SRgb).unwrap();
1419
1420        // Fill the surface with some data.
1421        {
1422            let mut data = surface.data();
1423
1424            let mut counter = 0u16;
1425            for x in data.iter_mut() {
1426                *x = counter as u8;
1427                counter = (counter + 1) % 256;
1428            }
1429        }
1430
1431        let surface = surface.share().unwrap();
1432        let alpha = surface.extract_alpha(bounds).unwrap();
1433
1434        for (x, y, p, pa) in
1435            Pixels::within(&surface, full_bounds).map(|(x, y, p)| (x, y, p, alpha.get_pixel(x, y)))
1436        {
1437            assert_eq!(pa.r, 0);
1438            assert_eq!(pa.g, 0);
1439            assert_eq!(pa.b, 0);
1440
1441            if !bounds.contains(x as i32, y as i32) {
1442                assert_eq!(pa.a, 0);
1443            } else {
1444                assert_eq!(pa.a, p.a);
1445            }
1446        }
1447    }
1448}