<commit_before>use std::cmp::{max, min};

use cssparser::Parser;
use markup5ever::{expanded_name, local_name, namespace_url, ns};

use crate::document::AcquiredNodes;
use crate::drawing_ctx::DrawingCtx;
use crate::element::{ElementResult, SetAttributes};
use crate::error::*;
use crate::node::Node;
use crate::parsers::{NonNegative, NumberOptionalNumber, Parse, ParseValue};
use crate::properties::ColorInterpolationFilters;
use crate::rect::IRect;
use crate::session::Session;
use crate::surface_utils::{
    iterators::{PixelRectangle, Pixels},
    shared_surface::ExclusiveImageSurface,
    EdgeMode, ImageSurfaceDataExt, Pixel,
};
use crate::xml::Attributes;

use super::bounds::BoundsBuilder;
use super::context::{FilterContext, FilterOutput};
use super::{
    FilterEffect, FilterError, FilterResolveError, Input, Primitive, PrimitiveParams,
    ResolvedPrimitive,
};

/// Enumeration of the possible morphology operations.
#[derive(Clone)]
enum Operator {
    Erode,
    Dilate,
}

enum_default!(Operator, Operator::Erode);

/// The `feMorphology` filter primitive.
#[derive(Default)]
pub struct FeMorphology {
    base: Primitive,
    params: Morphology,
}

/// Resolved `feMorphology` primitive for rendering.
#[derive(Clone, Default)]
pub struct Morphology {
    in1: Input,
    operator: Operator,
    radius: (f64, f64),
}

impl SetAttributes for FeMorphology {
    fn set_attributes(&mut self, attrs: &Attributes, session: &Session) -> ElementResult {
        self.params.in1 = self.base.parse_one_input(attrs, session);

        for (attr, value) in attrs.iter() {
            match attr.expanded() {
                expanded_name!("", "operator") => self.params.operator = attr.parse(value)?,
                expanded_name!("", "radius") => {
                    let NumberOptionalNumber(NonNegative(x), NonNegative(y)) = attr.parse(value)?;
                    self.params.radius = (x, y);
                }
                _ => (),
            }
        }

        Ok(())
    }
}

impl Morphology {
    pub fn render(
        &self,
        bounds_builder: BoundsBuilder,
        ctx: &FilterContext,
        acquired_nodes: &mut AcquiredNodes<'_>,
        draw_ctx: &mut DrawingCtx,
    ) -> Result<FilterOutput, FilterError> {
        // Although https://www.w3.org/TR/filter-effects/#propdef-color-interpolation-filters does not mention
        // feMorphology as being one of the primitives that does *not* use that property,
        // the SVG1.1 test for filters-morph-01-f.svg fails if we pass the value from the ComputedValues here (that
        // document does not specify the color-interpolation-filters property, so it defaults to linearRGB).
        // So, we pass Auto, which will get resolved to SRGB, and that makes that test pass.
        //
        // I suppose erosion/dilation doesn't care about the color space of the source image?

        let input_1 = ctx.get_input(
            acquired_nodes,
            draw_ctx,
            &self.in1,
            ColorInterpolationFilters::Auto,
        )?;
        let bounds: IRect = bounds_builder
            .add_input(&input_1)
            .compute(ctx)
            .clipped
            .into();

        let (rx, ry) = self.radius;

        if rx <= 0.0 && ry <= 0.0 {
            return Ok(FilterOutput {
                surface: input_1.surface().clone(),
                bounds,
            });
        }

        let (rx, ry) = ctx.paffine().transform_distance(rx, ry);

        // The radii can become negative here due to the transform.
        // Additionally The radii being excessively large causes cpu hangups
        let (rx, ry) = (rx.abs().min(10.0), ry.abs().min(10.0));

        let mut surface = ExclusiveImageSurface::new(
            ctx.source_graphic().width(),
            ctx.source_graphic().height(),
            input_1.surface().surface_type(),
        )?;

        surface.modify(&mut |data, stride| {
            for (x, y, _pixel) in Pixels::within(input_1.surface(), bounds) {
                // Compute the kernel rectangle bounds.
                let kernel_bounds = IRect::new(
                    (f64::from(x) - rx).floor() as i32,
                    (f64::from(y) - ry).floor() as i32,
                    (f64::from(x) + rx).ceil() as i32 + 1,
                    (f64::from(y) + ry).ceil() as i32 + 1,
                );<commit_msg>filters/morphology.rs: use set_attribute()<commit_after>use std::cmp::{max, min};

use cssparser::Parser;
use markup5ever::{expanded_name, local_name, namespace_url, ns};

use crate::document::AcquiredNodes;
use crate::drawing_ctx::DrawingCtx;
use crate::element::{set_attribute, ElementResult, SetAttributes};
use crate::error::*;
use crate::node::Node;
use crate::parsers::{NumberOptionalNumber, Parse, ParseValue};
use crate::properties::ColorInterpolationFilters;
use crate::rect::IRect;
use crate::session::Session;
use crate::surface_utils::{
    iterators::{PixelRectangle, Pixels},
    shared_surface::ExclusiveImageSurface,
    EdgeMode, ImageSurfaceDataExt, Pixel,
};
use crate::xml::Attributes;

use super::bounds::BoundsBuilder;
use super::context::{FilterContext, FilterOutput};
use super::{
    FilterEffect, FilterError, FilterResolveError, Input, Primitive, PrimitiveParams,
    ResolvedPrimitive,
};

/// Enumeration of the possible morphology operations.
#[derive(Clone)]
enum Operator {
    Erode,
    Dilate,
}

enum_default!(Operator, Operator::Erode);

/// The `feMorphology` filter primitive.
#[derive(Default)]
pub struct FeMorphology {
    base: Primitive,
    params: Morphology,
}

/// Resolved `feMorphology` primitive for rendering.
#[derive(Clone)]
pub struct Morphology {
    in1: Input,
    operator: Operator,
    radius: NumberOptionalNumber<f64>,
}

// We need this because NumberOptionalNumber doesn't impl Default
impl Default for Morphology {
    fn default() -> Morphology {
        Morphology {
            in1: Default::default(),
            operator: Default::default(),
            radius: NumberOptionalNumber(0.0, 0.0),
        }
    }
}

impl SetAttributes for FeMorphology {
    fn set_attributes(&mut self, attrs: &Attributes, session: &Session) -> ElementResult {
        self.params.in1 = self.base.parse_one_input(attrs, session);

        for (attr, value) in attrs.iter() {
            match attr.expanded() {
                expanded_name!("", "operator") => {
                    set_attribute(&mut self.params.operator, attr.parse(value), session);
                }
                expanded_name!("", "radius") => {
                    set_attribute(&mut self.params.radius, attr.parse(value), session);
                }
                _ => (),
            }
        }

        Ok(())
    }
}

impl Morphology {
    pub fn render(
        &self,
        bounds_builder: BoundsBuilder,
        ctx: &FilterContext,
        acquired_nodes: &mut AcquiredNodes<'_>,
        draw_ctx: &mut DrawingCtx,
    ) -> Result<FilterOutput, FilterError> {
        // Although https://www.w3.org/TR/filter-effects/#propdef-color-interpolation-filters does not mention
        // feMorphology as being one of the primitives that does *not* use that property,
        // the SVG1.1 test for filters-morph-01-f.svg fails if we pass the value from the ComputedValues here (that
        // document does not specify the color-interpolation-filters property, so it defaults to linearRGB).
        // So, we pass Auto, which will get resolved to SRGB, and that makes that test pass.
        //
        // I suppose erosion/dilation doesn't care about the color space of the source image?

        let input_1 = ctx.get_input(
            acquired_nodes,
            draw_ctx,
            &self.in1,
            ColorInterpolationFilters::Auto,
        )?;
        let bounds: IRect = bounds_builder
            .add_input(&input_1)
            .compute(ctx)
            .clipped
            .into();

        let NumberOptionalNumber(rx, ry) = self.radius;

        if rx <= 0.0 && ry <= 0.0 {
            return Ok(FilterOutput {
                surface: input_1.surface().clone(),
                bounds,
            });
        }

        let (rx, ry) = ctx.paffine().transform_distance(rx, ry);

        // The radii can become negative here due to the transform.
        // Additionally The radii being excessively large causes cpu hangups
        let (rx, ry) = (rx.abs().min(10.0), ry.abs().min(10.0));

        let mut surface = ExclusiveImageSurface::new(
            ctx.source_graphic().width(),
            ctx.source_graphic().height(),
            input_1.surface().surface_type(),
        )?;

        surface.modify(&mut |data, stride| {
            for (x, y, _pixel) in Pixels::within(input_1.surface(), bounds) {
                // Compute the kernel rectangle bounds.
                let kernel_bounds = IRect::new(
                    (f64::from(x) - rx).floor() as i32,
                    (f64::from(y) - ry).floor() as i32,
                    (f64::from(x) + rx).ceil() as i32 + 1,
                    (f64::from(y) + ry).ceil() as i32 + 1,
                );
{'fraction_non_alphanumeric': 0.12544052863436123, 'fraction_numerical': 0.010462555066079295, 'mean_word_length': 2.42420814479638, 'pattern_counts': {'":': 0, '<': 12, '<?xml version=': 0, '>': 19, 'https://': 2, 'lorem ipsum': 0, 'www.': 2, 'xml': 2}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3483938', 'n_tokens_mistral': 2679, 'n_tokens_neox': 2612, 'n_words': 678}