image.rs 7.63 KB
Newer Older
1 2 3
use std::cell::{Cell, RefCell};
use std::ptr;

Jordan Petridis's avatar
Jordan Petridis committed
4
use cairo::{self, ImageSurface, MatrixTrait, PatternTrait};
5

6
use allowed_url::AllowedUrl;
7 8
use aspect_ratio::AspectRatio;
use attributes::Attribute;
9
use defs::{Fragment, Href};
10
use drawing_ctx::DrawingCtx;
11
use error::RenderingError;
12
use handle::{self, RsvgHandle};
13
use node::{CascadedValues, NodeResult, NodeTrait, RsvgNode};
14 15
use parsers::parse;
use property_bag::PropertyBag;
16
use surface_utils::shared_surface::{SharedImageSurface, SurfaceType};
17

18
use super::bounds::BoundsBuilder;
19
use super::context::{FilterContext, FilterOutput, FilterResult, IRect};
20
use super::{Filter, FilterError, Primitive};
21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45

/// The `feImage` filter primitive.
pub struct Image {
    base: Primitive,
    aspect: Cell<AspectRatio>,
    href: RefCell<Option<String>>,

    // Storing this here seems hack-ish... It's required by rsvg_cairo_surface_new_from_href(). The
    // <image> element calls it in set_atts() but I don't think it belongs there.
    handle: Cell<*const RsvgHandle>,
}

impl Image {
    /// Constructs a new `Image` with empty properties.
    #[inline]
    pub fn new() -> Image {
        Image {
            base: Primitive::new::<Self>(),
            aspect: Cell::new(AspectRatio::default()),
            href: RefCell::new(None),

            handle: Cell::new(ptr::null()),
        }
    }

46 47
    /// Renders the filter if the source is an existing node.
    fn render_node(
48
        &self,
49
        ctx: &FilterContext,
50
        draw_ctx: &mut DrawingCtx<'_>,
51
        bounds: IRect,
52
        fragment: &Fragment,
53
    ) -> Result<ImageSurface, FilterError> {
54
        let acquired_drawable = draw_ctx
55
            .get_acquired_node(fragment)
56
            .ok_or(FilterError::InvalidInput)?;
57
        let drawable = acquired_drawable.get();
58 59 60

        let surface = ImageSurface::create(
            cairo::Format::ARgb32,
61 62
            ctx.source_graphic().width(),
            ctx.source_graphic().height(),
63
        )?;
64

65 66
        draw_ctx.get_cairo_context().set_matrix(ctx.paffine());

67
        let node_being_filtered_values = ctx.get_computed_values_from_node_being_filtered();
68 69

        let cascaded = CascadedValues::new_from_values(&drawable, node_being_filtered_values);
70

71 72 73 74 75 76 77
        draw_ctx
            .draw_node_on_surface(
                &drawable,
                &cascaded,
                &surface,
                f64::from(ctx.source_graphic().width()),
                f64::from(ctx.source_graphic().height()),
Federico Mena Quintero's avatar
Federico Mena Quintero committed
78 79
            )
            .map_err(|e| {
80 81 82 83 84 85 86 87
                if let RenderingError::Cairo(status) = e {
                    FilterError::CairoError(status)
                } else {
                    // FIXME: this is just a dummy value; we should probably have a way to indicate
                    // an error in the underlying drawing process.
                    FilterError::CairoError(cairo::Status::InvalidStatus)
                }
            })?;
88

89 90 91
        // Clip the output to bounds.
        let output_surface = ImageSurface::create(
            cairo::Format::ARgb32,
92 93
            ctx.source_graphic().width(),
            ctx.source_graphic().height(),
94
        )?;
95

96 97 98 99 100 101 102 103 104 105
        let cr = cairo::Context::new(&output_surface);
        cr.rectangle(
            f64::from(bounds.x0),
            f64::from(bounds.y0),
            f64::from(bounds.x1 - bounds.x0),
            f64::from(bounds.y1 - bounds.y0),
        );
        cr.clip();
        cr.set_source_surface(&surface, 0f64, 0f64);
        cr.paint();
106

107
        Ok(output_surface)
108 109
    }

110 111 112 113
    /// Renders the filter if the source is an external image.
    fn render_external_image(
        &self,
        ctx: &FilterContext,
114 115
        draw_ctx: &mut DrawingCtx<'_>,
        bounds_builder: BoundsBuilder<'_>,
116
        href: &Href,
117
    ) -> Result<ImageSurface, FilterError> {
118 119 120 121 122 123
        let url = if let Href::PlainUri(ref url) = *href {
            url
        } else {
            unreachable!();
        };

124 125 126
        let aurl = AllowedUrl::from_href(url, handle::get_base_url(self.handle.get()).as_ref())
            .map_err(|_| FilterError::InvalidInput)?;

127
        // FIXME: translate the error better here
128
        let surface = handle::load_image_to_surface(self.handle.get() as *mut _, &aurl)
129
            .map_err(|_| FilterError::InvalidInput)?;
130 131 132

        let output_surface = ImageSurface::create(
            cairo::Format::ARgb32,
133 134
            ctx.source_graphic().width(),
            ctx.source_graphic().height(),
135
        )?;
136

137
        // TODO: this goes through a f64->i32->f64 conversion.
138
        let render_bounds = bounds_builder.into_irect_without_clipping(draw_ctx);
139 140 141 142
        let aspect = self.aspect.get();
        let (x, y, w, h) = aspect.compute(
            f64::from(surface.get_width()),
            f64::from(surface.get_height()),
143 144 145 146
            f64::from(render_bounds.x0),
            f64::from(render_bounds.y0),
            f64::from(render_bounds.x1 - render_bounds.x0),
            f64::from(render_bounds.y1 - render_bounds.y0),
147 148 149 150 151 152 153 154 155 156 157 158 159 160 161
        );

        if w != 0f64 && h != 0f64 {
            let ptn = cairo::SurfacePattern::create(&surface);
            let mut matrix = cairo::Matrix::new(
                w / f64::from(surface.get_width()),
                0f64,
                0f64,
                h / f64::from(surface.get_height()),
                x,
                y,
            );
            matrix.invert();
            ptn.set_matrix(matrix);

162
            let bounds = bounds_builder.into_irect(draw_ctx);
163 164 165 166 167 168 169 170
            let cr = cairo::Context::new(&output_surface);
            cr.rectangle(
                f64::from(bounds.x0),
                f64::from(bounds.y0),
                f64::from(bounds.x1 - bounds.x0),
                f64::from(bounds.y1 - bounds.y0),
            );
            cr.clip();
Jordan Petridis's avatar
Jordan Petridis committed
171
            cr.set_source(&cairo::Pattern::SurfacePattern(ptn));
172 173 174
            cr.paint();
        }

175 176 177 178 179 180 181 182 183
        Ok(output_surface)
    }
}

impl NodeTrait for Image {
    fn set_atts(
        &self,
        node: &RsvgNode,
        handle: *const RsvgHandle,
184
        pbag: &PropertyBag<'_>,
185 186 187 188 189 190
    ) -> NodeResult {
        self.base.set_atts(node, handle, pbag)?;

        for (_key, attr, value) in pbag.iter() {
            match attr {
                Attribute::PreserveAspectRatio => {
191
                    self.aspect.set(parse("preserveAspectRatio", value, ())?)
192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208
                }

                // "path" is used by some older Adobe Illustrator versions
                Attribute::XlinkHref | Attribute::Path => {
                    drop(self.href.replace(Some(value.to_string())))
                }
                _ => (),
            }
        }

        self.handle.set(handle);

        Ok(())
    }
}

impl Filter for Image {
209 210 211 212
    fn render(
        &self,
        _node: &RsvgNode,
        ctx: &FilterContext,
213
        draw_ctx: &mut DrawingCtx<'_>,
214
    ) -> Result<FilterResult, FilterError> {
215 216
        let href_str = self.href.borrow();
        let href_str = href_str.as_ref().ok_or(FilterError::InvalidInput)?;
217

218
        let bounds_builder = self.base.get_bounds(ctx);
219
        let bounds = bounds_builder.into_irect(draw_ctx);
220

221 222 223
        let href = Href::parse(href_str).map_err(|_| FilterError::InvalidInput)?;

        let output_surface = match href {
224 225
            Href::PlainUri(_) => {
                self.render_external_image(ctx, draw_ctx, bounds_builder, &href)?
226
            }
227
            Href::WithFragment(frag) => self.render_node(ctx, draw_ctx, bounds, &frag)?,
228 229
        };

230 231 232
        Ok(FilterResult {
            name: self.base.result.borrow().clone(),
            output: FilterOutput {
233
                surface: SharedImageSurface::new(output_surface, SurfaceType::SRgb)?,
234 235 236 237
                bounds,
            },
        })
    }
238 239

    #[inline]
240
    fn is_affected_by_color_interpolation_filters(&self) -> bool {
241 242
        false
    }
243
}