context.rs 23.1 KB
Newer Older
1
use std::cell::UnsafeCell;
Ivan Molodetskikh's avatar
Ivan Molodetskikh committed
2
use std::collections::HashMap;
3
use std::f64;
Ivan Molodetskikh's avatar
Ivan Molodetskikh committed
4

5
use cairo::{self, MatrixTrait};
Ivan Molodetskikh's avatar
Ivan Molodetskikh committed
6

7
use bbox::BoundingBox;
8
use coord_units::CoordUnits;
9
use drawing_ctx::DrawingCtx;
10
use length::Length;
11
use node::RsvgNode;
12
use paint_server::{self, PaintServer};
13
use state::ComputedValues;
14
use surface_utils::shared_surface::{SharedImageSurface, SurfaceType};
15
use unitinterval::UnitInterval;
Ivan Molodetskikh's avatar
Ivan Molodetskikh committed
16

17
use super::error::FilterError;
18
use super::input::Input;
19
use super::node::NodeFilter;
20

21 22 23 24 25 26 27 28
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct IRect {
    pub x0: i32,
    pub y0: i32,
    pub x1: i32,
    pub y1: i32,
}

29
/// A filter primitive output.
30
#[derive(Debug, Clone)]
31
pub struct FilterOutput {
32
    /// The surface after the filter primitive was applied.
33
    pub surface: SharedImageSurface,
34 35

    /// The filter primitive subregion.
36
    pub bounds: IRect,
Ivan Molodetskikh's avatar
Ivan Molodetskikh committed
37 38
}

39 40 41 42 43 44 45 46 47 48
/// A filter primitive result.
#[derive(Debug, Clone)]
pub struct FilterResult {
    /// The name of this result: the value of the `result` attribute.
    pub name: Option<String>,

    /// The output.
    pub output: FilterOutput,
}

49 50 51 52
/// An input to a filter primitive.
#[derive(Debug, Clone)]
pub enum FilterInput {
    /// One of the standard inputs.
53
    StandardInput(SharedImageSurface),
54 55 56 57
    /// Output of another filter primitive.
    PrimitiveOutput(FilterOutput),
}

Ivan Molodetskikh's avatar
Ivan Molodetskikh committed
58
/// The filter rendering context.
59
pub struct FilterContext {
60
    /// The <filter> node.
61
    node: RsvgNode,
62 63
    /// Values from the node which referenced this filter.
    computed_from_node_being_filtered: ComputedValues,
Ivan Molodetskikh's avatar
Ivan Molodetskikh committed
64
    /// The source graphic surface.
65
    source_surface: SharedImageSurface,
Ivan Molodetskikh's avatar
Ivan Molodetskikh committed
66
    /// Output of the last filter primitive.
67
    last_result: Option<FilterOutput>,
Ivan Molodetskikh's avatar
Ivan Molodetskikh committed
68
    /// Surfaces of the previous filter primitives by name.
69
    previous_results: HashMap<String, FilterOutput>,
70
    /// The background surface. Computed lazily.
71
    background_surface: UnsafeCell<Option<Result<SharedImageSurface, FilterError>>>,
72 73
    /// The filter effects region.
    effects_region: BoundingBox,
74 75 76 77 78
    /// Whether the currently rendered filter primitive uses linear RGB for color operations.
    ///
    /// This affects `get_input()` and `store_result()` which should perform linearization and
    /// unlinearization respectively when this is set to `true`.
    processing_linear_rgb: bool,
79

80 81 82 83 84 85 86 87 88 89 90 91 92 93 94
    /// The filter element affine matrix.
    ///
    /// If `filterUnits == userSpaceOnUse`, equal to the drawing context matrix, so, for example,
    /// if the target node is in a group with `transform="translate(30, 20)"`, this will be equal
    /// to a matrix that translates to 30, 20 (and does not scale). Note that the target node
    /// bounding box isn't included in the computations in this case.
    ///
    /// If `filterUnits == objectBoundingBox`, equal to the target node bounding box matrix
    /// multiplied by the drawing context matrix, so, for example, if the target node is in a group
    /// with `transform="translate(30, 20)"` and also has `x="1", y="1", width="50", height="50"`,
    /// this will be equal to a matrix that translates to 31, 21 and scales to 50, 50.
    ///
    /// This is to be used in conjunction with setting the viewbox size to account for the scaling.
    /// For `filterUnits == userSpaceOnUse`, the viewbox will have the actual resolution size, and
    /// for `filterUnits == objectBoundingBox`, the viewbox will have the size of 1, 1.
95
    _affine: cairo::Matrix,
96 97 98

    /// The filter primitive affine matrix.
    ///
99
    /// See the comments for `_affine`, they largely apply here.
100
    paffine: cairo::Matrix,
Ivan Molodetskikh's avatar
Ivan Molodetskikh committed
101 102
}

103 104 105
/// Computes and returns the filter effects region.
fn compute_effects_region(
    filter_node: &RsvgNode,
106
    computed_from_target_node: &ComputedValues,
107
    draw_ctx: &mut DrawingCtx,
108 109 110 111
    affine: cairo::Matrix,
    width: f64,
    height: f64,
) -> BoundingBox {
112
    // Filters use the properties of the target node.
113
    let values = computed_from_target_node;
114 115 116 117 118 119 120 121 122 123 124 125 126 127

    let filter = filter_node.get_impl::<NodeFilter>().unwrap();

    let mut bbox = BoundingBox::new(&cairo::Matrix::identity());

    // affine is set up in FilterContext::new() in such a way that for
    // filterunits == ObjectBoundingBox affine includes scaling to correct width, height and this
    // is why width and height are set to 1, 1 (and for filterunits == UserSpaceOnUse affine
    // doesn't include scaling because in this case the correct width, height already happens to be
    // the viewbox width, height).
    //
    // It's done this way because with ObjectBoundingBox, non-percentage values are supposed to
    // represent the fractions of the referenced node, and with width and height = 1, 1 this
    // works out exactly like that.
128 129 130 131 132
    let params = if filter.filterunits.get() == CoordUnits::ObjectBoundingBox {
        draw_ctx.push_view_box(1.0, 1.0)
    } else {
        draw_ctx.get_view_params()
    };
133 134

    // With filterunits == ObjectBoundingBox, lengths represent fractions or percentages of the
135
    // referencing node. No units are allowed (it's checked during attribute parsing).
136 137
    let rect = if filter.filterunits.get() == CoordUnits::ObjectBoundingBox {
        cairo::Rectangle {
138 139 140 141
            x: filter.x.get().get_unitless(),
            y: filter.y.get().get_unitless(),
            width: filter.width.get().get_unitless(),
            height: filter.height.get().get_unitless(),
142 143 144
        }
    } else {
        cairo::Rectangle {
145 146 147 148
            x: filter.x.get().normalize(values, &params),
            y: filter.y.get().normalize(values, &params),
            width: filter.width.get().normalize(values, &params),
            height: filter.height.get().normalize(values, &params),
149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170
        }
    };

    let other_bbox = BoundingBox::new(&affine).with_rect(Some(rect));

    // At this point all of the previous viewbox and matrix business gets converted to pixel
    // coordinates in the final surface, because bbox is created with an identity affine.
    bbox.insert(&other_bbox);

    // Finally, clip to the width and height of our surface.
    let rect = cairo::Rectangle {
        x: 0f64,
        y: 0f64,
        width,
        height,
    };
    let other_bbox = BoundingBox::new(&cairo::Matrix::identity()).with_rect(Some(rect));
    bbox.clip(&other_bbox);

    bbox
}

171 172 173 174 175 176
impl IRect {
    /// Returns true if the `IRect` contains the given coordinates.
    #[inline]
    pub fn contains(self, x: i32, y: i32) -> bool {
        x >= self.x0 && x < self.x1 && y >= self.y0 && y < self.y1
    }
Ivan Molodetskikh's avatar
Ivan Molodetskikh committed
177 178 179 180 181 182 183 184 185 186 187 188 189

    /// Returns an `IRect` scaled by the given amounts.
    ///
    /// The returned `IRect` encompasses all, even partially covered, pixels after the scaling.
    #[inline]
    pub fn scale(self, x: f64, y: f64) -> IRect {
        IRect {
            x0: (f64::from(self.x0) * x).floor() as i32,
            y0: (f64::from(self.y0) * y).floor() as i32,
            x1: (f64::from(self.x1) * x).ceil() as i32,
            y1: (f64::from(self.y1) * y).ceil() as i32,
        }
    }
190 191
}

192
impl FilterContext {
Ivan Molodetskikh's avatar
Ivan Molodetskikh committed
193 194
    /// Creates a new `FilterContext`.
    pub fn new(
195
        filter_node: &RsvgNode,
196
        computed_from_node_being_filtered: &ComputedValues,
197
        source_surface: SharedImageSurface,
198
        draw_ctx: &mut DrawingCtx,
Ivan Molodetskikh's avatar
Ivan Molodetskikh committed
199
    ) -> Self {
200
        let cr_affine = draw_ctx.get_cairo_context().get_matrix();
201
        let bbox = draw_ctx.get_bbox().clone();
Ivan Molodetskikh's avatar
Ivan Molodetskikh committed
202 203 204 205 206 207 208 209 210

        // The rect can be empty (for example, if the filter is applied to an empty group).
        // However, with userSpaceOnUse it's still possible to create images with a filter.
        let bbox_rect = bbox.rect.unwrap_or(cairo::Rectangle {
            x: 0.0,
            y: 0.0,
            width: 0.0,
            height: 0.0,
        });
Ivan Molodetskikh's avatar
Ivan Molodetskikh committed
211

212 213 214
        let filter = filter_node.get_impl::<NodeFilter>().unwrap();

        let affine = match filter.filterunits.get() {
215
            CoordUnits::UserSpaceOnUse => cr_affine,
216 217
            CoordUnits::ObjectBoundingBox => {
                let affine = cairo::Matrix::new(
218
                    bbox_rect.width,
219 220
                    0f64,
                    0f64,
221 222 223
                    bbox_rect.height,
                    bbox_rect.x,
                    bbox_rect.y,
224
                );
225
                cairo::Matrix::multiply(&affine, &cr_affine)
226 227 228
            }
        };

229
        let paffine = match filter.primitiveunits.get() {
230
            CoordUnits::UserSpaceOnUse => cr_affine,
231 232
            CoordUnits::ObjectBoundingBox => {
                let affine = cairo::Matrix::new(
233
                    bbox_rect.width,
234 235
                    0f64,
                    0f64,
236 237 238
                    bbox_rect.height,
                    bbox_rect.x,
                    bbox_rect.y,
239
                );
240
                cairo::Matrix::multiply(&affine, &cr_affine)
241 242 243
            }
        };

244 245
        let width = source_surface.width();
        let height = source_surface.height();
246

247
        Self {
248
            node: filter_node.clone(),
249
            computed_from_node_being_filtered: computed_from_node_being_filtered.clone(),
Ivan Molodetskikh's avatar
Ivan Molodetskikh committed
250 251 252
            source_surface,
            last_result: None,
            previous_results: HashMap::new(),
253
            background_surface: UnsafeCell::new(None),
254 255
            effects_region: compute_effects_region(
                filter_node,
256
                computed_from_node_being_filtered,
257 258 259 260 261
                draw_ctx,
                affine,
                f64::from(width),
                f64::from(height),
            ),
262
            processing_linear_rgb: false,
263
            _affine: affine,
264
            paffine,
265
        }
Ivan Molodetskikh's avatar
Ivan Molodetskikh committed
266 267
    }

268
    /// Returns the computed values from the node that referenced this filter.
269
    #[inline]
270 271
    pub fn get_computed_values_from_node_being_filtered(&self) -> &ComputedValues {
        &self.computed_from_node_being_filtered
272 273
    }

Ivan Molodetskikh's avatar
Ivan Molodetskikh committed
274 275
    /// Returns the surface corresponding to the last filter primitive's result.
    #[inline]
276
    pub fn last_result(&self) -> Option<&FilterOutput> {
277
        self.last_result.as_ref()
Ivan Molodetskikh's avatar
Ivan Molodetskikh committed
278 279 280 281
    }

    /// Returns the surface corresponding to the source graphic.
    #[inline]
282
    pub fn source_graphic(&self) -> &SharedImageSurface {
283
        &self.source_surface
Ivan Molodetskikh's avatar
Ivan Molodetskikh committed
284 285
    }

286 287
    /// Returns the surface containing the source graphic alpha.
    #[inline]
288
    pub fn source_alpha(&self, bounds: IRect) -> Result<SharedImageSurface, FilterError> {
289 290
        self.source_surface
            .extract_alpha(bounds)
291
            .map_err(FilterError::CairoError)
292 293
    }

294
    /// Computes and returns the background image snapshot.
295 296
    fn compute_background_image(
        &self,
297
        draw_ctx: &DrawingCtx,
298
    ) -> Result<cairo::ImageSurface, cairo::Status> {
299 300
        let surface = cairo::ImageSurface::create(
            cairo::Format::ARgb32,
301 302
            self.source_surface.width(),
            self.source_surface.height(),
303 304
        )?;

305 306
        let (x, y) = draw_ctx.get_raw_offset();
        let stack = draw_ctx.get_cr_stack();
307

Ivan Molodetskikh's avatar
Ivan Molodetskikh committed
308 309 310
        // TODO: as far as I can tell this should not render elements past the last (topmost) one
        // with enable-background: new (because technically we shouldn't have been caching them).
        // Right now there are no enable-background checks whatsoever.
311
        let cr = cairo::Context::new(&surface);
Ivan Molodetskikh's avatar
Ivan Molodetskikh committed
312
        for draw in stack.into_iter() {
313
            let nested = draw_ctx.is_cairo_context_nested(&draw);
314 315 316 317 318 319 320 321 322 323 324
            cr.set_source_surface(
                &draw.get_target(),
                if nested { 0f64 } else { -x },
                if nested { 0f64 } else { -y },
            );
            cr.paint();
        }

        Ok(surface)
    }

Ivan Molodetskikh's avatar
Ivan Molodetskikh committed
325
    /// Returns the surface corresponding to the background image snapshot.
326 327
    pub fn background_image(
        &self,
328
        draw_ctx: &DrawingCtx,
329
    ) -> Result<&SharedImageSurface, FilterError> {
330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345
        {
            // At this point either no, or only immutable references to background_surface exist, so
            // it's ok to make an immutable reference.
            let bg = unsafe { &*self.background_surface.get() };

            // If background_surface was already computed, return the immutable reference. It will
            // get bound to the &self lifetime by the function return type.
            if let Some(result) = bg.as_ref() {
                return result.as_ref().map_err(|&s| s);
            }
        }

        // If we got here, then background_surface hasn't been computed yet. This means there are
        // no references to it and we can create a mutable reference.
        let bg = unsafe { &mut *self.background_surface.get() };

346
        *bg = Some(
347
            self.compute_background_image(draw_ctx)
348
                .map_err(FilterError::CairoError)
349
                .and_then(|surface| {
350 351
                    SharedImageSurface::new(surface, SurfaceType::SRgb)
                        .map_err(FilterError::CairoError)
352
                }),
353
        );
354 355 356 357 358 359

        // Return the only existing reference as immutable.
        bg.as_ref().unwrap().as_ref().map_err(|&s| s)
    }

    /// Returns the surface containing the background image snapshot alpha.
Ivan Molodetskikh's avatar
Ivan Molodetskikh committed
360
    #[inline]
361 362
    pub fn background_alpha(
        &self,
363
        draw_ctx: &DrawingCtx,
364
        bounds: IRect,
365
    ) -> Result<SharedImageSurface, FilterError> {
366 367
        self.background_image(draw_ctx)?
            .extract_alpha(bounds)
368
            .map_err(FilterError::CairoError)
Ivan Molodetskikh's avatar
Ivan Molodetskikh committed
369 370
    }

371
    /// Returns the output of the filter primitive by its result name.
Ivan Molodetskikh's avatar
Ivan Molodetskikh committed
372
    #[inline]
373
    pub fn filter_output(&self, name: &str) -> Option<&FilterOutput> {
374
        self.previous_results.get(name)
Ivan Molodetskikh's avatar
Ivan Molodetskikh committed
375 376
    }

377
    /// Converts this `FilterContext` into the surface corresponding to the output of the filter
Ivan Molodetskikh's avatar
Ivan Molodetskikh committed
378
    /// chain.
379 380 381
    ///
    /// The returned surface is in the sRGB color space.
    // TODO: sRGB conversion should probably be done by the caller.
Ivan Molodetskikh's avatar
Ivan Molodetskikh committed
382
    #[inline]
383
    pub fn into_output(self) -> Result<SharedImageSurface, cairo::Status> {
384
        match self.last_result {
385
            Some(FilterOutput { surface, bounds }) => surface.to_srgb(bounds),
386 387 388 389 390
            None => {
                let empty_surface = cairo::ImageSurface::create(
                    cairo::Format::ARgb32,
                    self.source_surface.width(),
                    self.source_surface.height(),
391
                )?;
392

Federico Mena Quintero's avatar
Federico Mena Quintero committed
393 394 395 396
                Ok(SharedImageSurface::new(
                    empty_surface,
                    SurfaceType::AlphaOnly,
                )?)
397 398
            }
        }
Ivan Molodetskikh's avatar
Ivan Molodetskikh committed
399 400
    }

401
    /// Stores a filter primitive result into the context.
402
    #[inline]
403
    pub fn store_result(&mut self, result: FilterResult) -> Result<(), FilterError> {
404 405
        if let Some(name) = result.name {
            self.previous_results.insert(name, result.output.clone());
Ivan Molodetskikh's avatar
Ivan Molodetskikh committed
406
        }
407

408
        self.last_result = Some(result.output);
409
        Ok(())
410
    }
411 412 413 414 415 416

    /// Returns the paffine matrix.
    #[inline]
    pub fn paffine(&self) -> cairo::Matrix {
        self.paffine
    }
417

418 419 420 421
    /// Returns the filter effects region.
    #[inline]
    pub fn effects_region(&self) -> BoundingBox {
        self.effects_region
422 423 424
    }

    /// Calls the given function with correct behavior for the value of `primitiveUnits`.
425
    pub fn with_primitive_units<F, T>(&self, draw_ctx: &mut DrawingCtx, f: F) -> T
426 427
    // TODO: Get rid of this Box? Can't just impl Trait because Rust cannot do higher-ranked types.
    where
428
        for<'b> F: FnOnce(Box<Fn(&Length) -> f64 + 'b>) -> T,
429 430 431 432 433
    {
        let filter = self.node.get_impl::<NodeFilter>().unwrap();

        // See comments in compute_effects_region() for how this works.
        if filter.primitiveunits.get() == CoordUnits::ObjectBoundingBox {
434
            let _params = draw_ctx.push_view_box(1.0, 1.0);
435
            let rv = f(Box::new(Length::get_unitless));
436 437 438

            rv
        } else {
439
            f(Box::new(|length: &Length| {
440 441 442 443 444
                // Filters use the properties of the target node.
                length.normalize(
                    &self.computed_from_node_being_filtered,
                    &draw_ctx.get_view_params(),
                )
445
            }))
446 447
        }
    }
448

449 450
    /// Computes and returns a surface corresponding to the given paint server.
    fn get_paint_server_surface(
451
        &self,
452
        draw_ctx: &mut DrawingCtx,
453 454 455 456 457
        paint_server: &PaintServer,
        opacity: UnitInterval,
    ) -> Result<cairo::ImageSurface, cairo::Status> {
        let surface = cairo::ImageSurface::create(
            cairo::Format::ARgb32,
458 459
            self.source_surface.width(),
            self.source_surface.height(),
460 461
        )?;

462
        let cr_save = draw_ctx.get_cairo_context();
463
        let cr = cairo::Context::new(&surface);
464
        draw_ctx.set_cairo_context(&cr);
465

466
        let bbox = draw_ctx.get_bbox().clone();
467

468 469
        // FIXME: we are ignoring the following error; propagate it upstream
        let _ = paint_server::set_source_paint_server(
470
            draw_ctx,
471 472
            paint_server,
            &opacity,
473
            &bbox,
474
            &self.computed_from_node_being_filtered.color.0,
Federico Mena Quintero's avatar
Federico Mena Quintero committed
475 476
        )
        .and_then(|had_paint_server| {
477 478 479 480 481
            if had_paint_server {
                cr.paint();
            }
            Ok(())
        });
482

483
        draw_ctx.set_cairo_context(&cr_save);
484 485 486
        Ok(surface)
    }

487
    /// Retrieves the filter input surface according to the SVG rules.
488 489
    ///
    /// Does not take `processing_linear_rgb` into account.
490 491
    fn get_input_raw(
        &self,
492
        draw_ctx: &mut DrawingCtx,
493 494
        in_: Option<&Input>,
    ) -> Result<FilterInput, FilterError> {
495 496 497 498
        if in_.is_none() {
            // No value => use the last result.
            // As per the SVG spec, if the filter primitive is the first in the chain, return the
            // source graphic.
499
            if let Some(output) = self.last_result().cloned() {
500
                return Ok(FilterInput::PrimitiveOutput(output));
501
            } else {
502
                return Ok(FilterInput::StandardInput(self.source_graphic().clone()));
503
            }
504 505
        }

506
        let values = &self.computed_from_node_being_filtered;
507

508
        match *in_.unwrap() {
509
            Input::SourceGraphic => Ok(FilterInput::StandardInput(self.source_graphic().clone())),
510
            Input::SourceAlpha => self
511
                .source_alpha(self.effects_region().rect.unwrap().into())
512 513
                .map(FilterInput::StandardInput),
            Input::BackgroundImage => self
514
                .background_image(draw_ctx)
515
                .map(Clone::clone)
516 517
                .map(FilterInput::StandardInput),
            Input::BackgroundAlpha => self
518
                .background_alpha(draw_ctx, self.effects_region().rect.unwrap().into())
519
                .map(FilterInput::StandardInput),
520

521
            Input::FillPaint => self
522
                .get_paint_server_surface(draw_ctx, &values.fill.0, values.fill_opacity.0)
523
                .map_err(FilterError::CairoError)
524
                .and_then(|surface| {
525 526
                    SharedImageSurface::new(surface, SurfaceType::SRgb)
                        .map_err(FilterError::CairoError)
Federico Mena Quintero's avatar
Federico Mena Quintero committed
527 528
                })
                .map(FilterInput::StandardInput),
529
            Input::StrokePaint => self
530
                .get_paint_server_surface(draw_ctx, &values.stroke.0, values.stroke_opacity.0)
531
                .map_err(FilterError::CairoError)
532
                .and_then(|surface| {
533 534
                    SharedImageSurface::new(surface, SurfaceType::SRgb)
                        .map_err(FilterError::CairoError)
Federico Mena Quintero's avatar
Federico Mena Quintero committed
535 536
                })
                .map(FilterInput::StandardInput),
537

538 539 540
            Input::FilterOutput(ref name) => self
                .filter_output(name)
                .cloned()
541 542
                .map(FilterInput::PrimitiveOutput)
                .ok_or(FilterError::InvalidInput),
543 544
        }
    }
545 546

    /// Retrieves the filter input surface according to the SVG rules.
547 548
    pub fn get_input(
        &self,
549
        draw_ctx: &mut DrawingCtx,
550 551 552
        in_: Option<&Input>,
    ) -> Result<FilterInput, FilterError> {
        let raw = self.get_input_raw(draw_ctx, in_)?;
553

554 555 556 557 558 559 560 561 562 563 564 565 566
        // Convert the input surface to the desired format.
        let (surface, bounds) = match raw {
            FilterInput::StandardInput(ref surface) => {
                (surface, self.effects_region().rect.unwrap().into())
            }
            FilterInput::PrimitiveOutput(FilterOutput {
                ref surface,
                ref bounds,
            }) => (surface, *bounds),
        };

        let surface = if self.processing_linear_rgb {
            surface.to_linear_rgb(bounds)
567
        } else {
568 569 570 571 572 573 574 575 576 577
            surface.to_srgb(bounds)
        };
        surface
            .map_err(FilterError::CairoError)
            .map(|surface| match raw {
                FilterInput::StandardInput(_) => FilterInput::StandardInput(surface),
                FilterInput::PrimitiveOutput(ref output) => {
                    FilterInput::PrimitiveOutput(FilterOutput { surface, ..*output })
                }
            })
578 579 580 581 582 583 584 585 586 587
    }

    /// Calls the given closure with linear RGB processing enabled.
    #[inline]
    pub fn with_linear_rgb<T, F: FnOnce(&mut FilterContext) -> T>(&mut self, f: F) -> T {
        self.processing_linear_rgb = true;
        let rv = f(self);
        self.processing_linear_rgb = false;
        rv
    }
588 589 590 591 592 593

    /// Applies the `primitiveUnits` coordinate transformation to a non-x or y distance.
    #[inline]
    pub fn transform_dist(&self, d: f64) -> f64 {
        d * (self.paffine.xx.powi(2) + self.paffine.yy.powi(2)).sqrt() / f64::consts::SQRT_2
    }
594 595 596 597 598
}

impl FilterInput {
    /// Retrieves the surface from `FilterInput`.
    #[inline]
599
    pub fn surface(&self) -> &SharedImageSurface {
600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621
        match *self {
            FilterInput::StandardInput(ref surface) => surface,
            FilterInput::PrimitiveOutput(FilterOutput { ref surface, .. }) => surface,
        }
    }
}

impl From<cairo::Rectangle> for IRect {
    #[inline]
    fn from(
        cairo::Rectangle {
            x,
            y,
            width,
            height,
        }: cairo::Rectangle,
    ) -> Self {
        Self {
            x0: x.floor() as i32,
            y0: y.floor() as i32,
            x1: (x + width).ceil() as i32,
            y1: (y + height).ceil() as i32,
622 623
        }
    }
624 625
}

626 627 628
#[cfg(test)]
mod tests {
    use super::*;
629
    use surface_utils::iterators::Pixels;
630 631 632

    #[test]
    fn test_extract_alpha() {
633 634
        const WIDTH: i32 = 32;
        const HEIGHT: i32 = 64;
635 636 637 638 639 640 641 642
        const BOUNDS: IRect = IRect {
            x0: 8,
            x1: 24,
            y0: 16,
            y1: 48,
        };
        const FULL_BOUNDS: IRect = IRect {
            x0: 0,
643
            x1: WIDTH,
644
            y0: 0,
645
            y1: HEIGHT,
646 647 648
        };

        let mut surface =
649
            cairo::ImageSurface::create(cairo::Format::ARgb32, WIDTH, HEIGHT).unwrap();
650 651 652 653 654 655 656 657 658 659 660 661

        // Fill the surface with some data.
        {
            let mut data = surface.get_data().unwrap();

            let mut counter = 0u16;
            for x in data.iter_mut() {
                *x = counter as u8;
                counter = (counter + 1) % 256;
            }
        }

662
        let surface = SharedImageSurface::new(surface, SurfaceType::SRgb).unwrap();
663
        let alpha = surface.extract_alpha(BOUNDS).unwrap();
664 665

        for (x, y, p, pa) in
666
            Pixels::new(&surface, FULL_BOUNDS).map(|(x, y, p)| (x, y, p, alpha.get_pixel(x, y)))
667 668 669 670 671 672 673 674 675 676 677 678
        {
            assert_eq!(pa.r, 0);
            assert_eq!(pa.g, 0);
            assert_eq!(pa.b, 0);

            if !BOUNDS.contains(x as i32, y as i32) {
                assert_eq!(pa.a, 0);
            } else {
                assert_eq!(pa.a, p.a);
            }
        }
    }
679
}