skia-safe 0.58.0

Safe Skia Bindings for Rust
use crate::{
    prelude::*, scalar, Blender, Color, ColorChannel, ColorFilter, CubicResampler, IPoint, IRect,
    ISize, Image, ImageFilter, Matrix, Paint, Picture, Point3, Rect, Region, SamplingOptions,
    Shader, TileMode, Vector,
};
use skia_bindings::{self as sb, SkImageFilter, SkImageFilters_CropRect};

#[repr(C)]
#[derive(Copy, Clone, PartialEq, Debug)]
pub struct CropRect(Rect);

native_transmutable!(SkImageFilters_CropRect, CropRect, crop_rect_layout);

impl CropRect {
    pub const NO_CROP_RECT: CropRect = CropRect(Rect {
        left: scalar::NEG_INFINITY,
        top: scalar::NEG_INFINITY,
        right: scalar::INFINITY,
        bottom: scalar::INFINITY,
    });

    pub fn rect(&self) -> Option<Rect> {
        if *self == Self::NO_CROP_RECT {
            None
        } else {
            Some(self.0)
        }
    }
}

impl Default for CropRect {
    fn default() -> Self {
        CropRect::NO_CROP_RECT
    }
}

impl From<Option<CropRect>> for CropRect {
    fn from(opt: Option<CropRect>) -> Self {
        opt.unwrap_or(Self::NO_CROP_RECT)
    }
}

impl From<&CropRect> for CropRect {
    fn from(cr: &CropRect) -> Self {
        *cr
    }
}

impl From<IRect> for CropRect {
    fn from(r: IRect) -> Self {
        Self(Rect::from(r))
    }
}

impl From<&IRect> for CropRect {
    fn from(r: &IRect) -> Self {
        Self::from(*r)
    }
}

impl From<Rect> for CropRect {
    fn from(r: Rect) -> Self {
        Self(r)
    }
}

impl From<&Rect> for CropRect {
    fn from(r: &Rect) -> Self {
        Self(*r)
    }
}

pub fn alpha_threshold(
    region: &Region,
    inner_min: scalar,
    outer_max: scalar,
    input: impl Into<Option<ImageFilter>>,
    crop_rect: impl Into<CropRect>,
) -> Option<ImageFilter> {
    ImageFilter::from_ptr(unsafe {
        sb::C_SkImageFilters_AlphaThreshold(
            region.native(),
            inner_min,
            outer_max,
            input.into().into_ptr_or_null(),
            crop_rect.into().native(),
        )
    })
}

#[allow(clippy::too_many_arguments)]
pub fn arithmetic(
    k1: scalar,
    k2: scalar,
    k3: scalar,
    k4: scalar,
    enforce_pm_color: bool,
    background: impl Into<Option<ImageFilter>>,
    foreground: impl Into<Option<ImageFilter>>,
    crop_rect: impl Into<CropRect>,
) -> Option<ImageFilter> {
    ImageFilter::from_ptr(unsafe {
        sb::C_SkImageFilters_Arithmetic(
            k1,
            k2,
            k3,
            k4,
            enforce_pm_color,
            background.into().into_ptr_or_null(),
            foreground.into().into_ptr_or_null(),
            crop_rect.into().native(),
        )
    })
}

pub fn blend(
    mode: impl Into<Blender>,
    background: impl Into<Option<ImageFilter>>,
    foreground: impl Into<Option<ImageFilter>>,
    crop_rect: impl Into<CropRect>,
) -> Option<ImageFilter> {
    ImageFilter::from_ptr(unsafe {
        sb::C_SkImageFilters_Blend(
            mode.into().into_ptr(),
            background.into().into_ptr_or_null(),
            foreground.into().into_ptr_or_null(),
            crop_rect.into().native(),
        )
    })
}

pub fn blur(
    (sigma_x, sigma_y): (scalar, scalar),
    tile_mode: impl Into<Option<TileMode>>,
    input: impl Into<Option<ImageFilter>>,
    crop_rect: impl Into<CropRect>,
) -> Option<ImageFilter> {
    ImageFilter::from_ptr(unsafe {
        sb::C_SkImageFilters_Blur(
            sigma_x,
            sigma_y,
            tile_mode.into().unwrap_or(TileMode::Decal),
            input.into().into_ptr_or_null(),
            crop_rect.into().native(),
        )
    })
}

pub fn color_filter(
    cf: impl Into<ColorFilter>,
    input: impl Into<Option<ImageFilter>>,
    crop_rect: impl Into<CropRect>,
) -> Option<ImageFilter> {
    ImageFilter::from_ptr(unsafe {
        sb::C_SkImageFilters_ColorFilter(
            cf.into().into_ptr(),
            input.into().into_ptr_or_null(),
            crop_rect.into().native(),
        )
    })
}

pub fn compose(
    outer: impl Into<ImageFilter>,
    inner: impl Into<ImageFilter>,
) -> Option<ImageFilter> {
    ImageFilter::from_ptr(unsafe {
        sb::C_SkImageFilters_Compose(outer.into().into_ptr(), inner.into().into_ptr())
    })
}

pub fn displacement_map(
    (x_channel_selector, y_channel_selector): (ColorChannel, ColorChannel),
    scale: scalar,
    displacement: impl Into<Option<ImageFilter>>,
    color: impl Into<ImageFilter>,
    crop_rect: impl Into<CropRect>,
) -> Option<ImageFilter> {
    ImageFilter::from_ptr(unsafe {
        sb::C_SkImageFilters_DisplacementMap(
            x_channel_selector,
            y_channel_selector,
            scale,
            displacement.into().into_ptr_or_null(),
            color.into().into_ptr(),
            crop_rect.into().native(),
        )
    })
}

pub fn drop_shadow(
    delta: impl Into<Vector>,
    (sigma_x, sigma_y): (scalar, scalar),
    color: impl Into<Color>,
    input: impl Into<Option<ImageFilter>>,
    crop_rect: impl Into<CropRect>,
) -> Option<ImageFilter> {
    let delta = delta.into();
    let color = color.into();
    ImageFilter::from_ptr(unsafe {
        sb::C_SkImageFilters_DropShadow(
            delta.x,
            delta.y,
            sigma_x,
            sigma_y,
            color.into_native(),
            input.into().into_ptr_or_null(),
            crop_rect.into().native(),
        )
    })
}

pub fn drop_shadow_only(
    delta: impl Into<Vector>,
    (sigma_x, sigma_y): (scalar, scalar),
    color: impl Into<Color>,
    input: impl Into<Option<ImageFilter>>,
    crop_rect: impl Into<CropRect>,
) -> Option<ImageFilter> {
    let delta = delta.into();
    let color = color.into();
    ImageFilter::from_ptr(unsafe {
        sb::C_SkImageFilters_DropShadowOnly(
            delta.x,
            delta.y,
            sigma_x,
            sigma_y,
            color.into_native(),
            input.into().into_ptr_or_null(),
            crop_rect.into().native(),
        )
    })
}

pub fn image<'a>(
    image: impl Into<Image>,
    src_rect: impl Into<Option<&'a Rect>>,
    dst_rect: impl Into<Option<&'a Rect>>,
    sampling_options: impl Into<Option<SamplingOptions>>,
) -> Option<ImageFilter> {
    let image = image.into();
    let image_rect = Rect::from_iwh(image.width(), image.height());
    let src_rect = src_rect.into().unwrap_or(&image_rect);
    let dst_rect = dst_rect.into().unwrap_or(&image_rect);
    let sampling_options: SamplingOptions = sampling_options.into().unwrap_or_else(|| {
        CubicResampler {
            b: 1.0 / 3.0,
            c: 1.0 / 3.0,
        }
        .into()
    });

    ImageFilter::from_ptr(unsafe {
        sb::C_SkImageFilters_Image(
            image.into_ptr(),
            src_rect.as_ref().native(),
            dst_rect.as_ref().native(),
            sampling_options.native(),
        )
    })
}

pub fn magnifier(
    src_rect: impl AsRef<Rect>,
    inset: scalar,
    input: impl Into<Option<ImageFilter>>,
    crop_rect: impl Into<CropRect>,
) -> Option<ImageFilter> {
    ImageFilter::from_ptr(unsafe {
        sb::C_SkImageFilters_Magnifier(
            src_rect.as_ref().native(),
            inset,
            input.into().into_ptr_or_null(),
            crop_rect.into().native(),
        )
    })
}

#[allow(clippy::too_many_arguments)]
pub fn matrix_convolution(
    kernel_size: impl Into<ISize>,
    kernel: &[scalar],
    gain: scalar,
    bias: scalar,
    kernel_offset: impl Into<IPoint>,
    tile_mode: TileMode,
    convolve_alpha: bool,
    input: impl Into<Option<ImageFilter>>,
    crop_rect: impl Into<CropRect>,
) -> Option<ImageFilter> {
    let kernel_size = kernel_size.into();
    assert_eq!(
        (kernel_size.width * kernel_size.height) as usize,
        kernel.len()
    );
    ImageFilter::from_ptr(unsafe {
        sb::C_SkImageFilters_MatrixConvolution(
            kernel_size.native(),
            kernel.as_ptr(),
            gain,
            bias,
            kernel_offset.into().native(),
            tile_mode,
            convolve_alpha,
            input.into().into_ptr_or_null(),
            crop_rect.into().native(),
        )
    })
}

pub fn matrix_transform(
    matrix: &Matrix,
    sampling_options: impl Into<SamplingOptions>,
    input: impl Into<Option<ImageFilter>>,
) -> Option<ImageFilter> {
    ImageFilter::from_ptr(unsafe {
        sb::C_SkImageFilters_MatrixTransform(
            matrix.native(),
            sampling_options.into().native(),
            input.into().into_ptr_or_null(),
        )
    })
}

pub fn merge(
    filters: impl IntoIterator<Item = Option<ImageFilter>>,
    crop_rect: impl Into<CropRect>,
) -> Option<ImageFilter> {
    let filter_ptrs: Vec<*mut SkImageFilter> =
        filters.into_iter().map(|f| f.into_ptr_or_null()).collect();
    ImageFilter::from_ptr(unsafe {
        sb::C_SkImageFilters_Merge(
            filter_ptrs.as_ptr(),
            filter_ptrs.len().try_into().unwrap(),
            crop_rect.into().native(),
        )
    })
}

pub fn offset(
    delta: impl Into<Vector>,
    input: impl Into<Option<ImageFilter>>,
    crop_rect: impl Into<CropRect>,
) -> Option<ImageFilter> {
    let delta = delta.into();
    ImageFilter::from_ptr(unsafe {
        sb::C_SkImageFilters_Offset(
            delta.x,
            delta.y,
            input.into().into_ptr_or_null(),
            crop_rect.into().native(),
        )
    })
}

pub fn paint(paint: &Paint, crop_rect: impl Into<CropRect>) -> Option<ImageFilter> {
    ImageFilter::from_ptr(unsafe {
        sb::C_SkImageFilters_Paint(paint.native(), crop_rect.into().native())
    })
}

pub fn picture<'a>(
    picture: impl Into<Picture>,
    target_rect: impl Into<Option<&'a Rect>>,
) -> Option<ImageFilter> {
    let picture = picture.into();
    let picture_rect = picture.cull_rect();
    let target_rect = target_rect.into().unwrap_or(&picture_rect);

    ImageFilter::from_ptr(unsafe {
        sb::C_SkImageFilters_Picture(picture.into_ptr(), target_rect.native())
    })
}

pub use skia_bindings::SkImageFilters_Dither as Dither;
variant_name!(Dither::Yes, dither_naming);

pub fn shader(shader: impl Into<Shader>, crop_rect: impl Into<CropRect>) -> Option<ImageFilter> {
    shader_with_dither(shader, Dither::No, crop_rect)
}

pub fn shader_with_dither(
    shader: impl Into<Shader>,
    dither: Dither,
    crop_rect: impl Into<CropRect>,
) -> Option<ImageFilter> {
    ImageFilter::from_ptr(unsafe {
        sb::C_SkImageFilters_Shader(shader.into().into_ptr(), dither, crop_rect.into().native())
    })
}

pub fn tile(
    src: impl AsRef<Rect>,
    dst: impl AsRef<Rect>,
    input: impl Into<Option<ImageFilter>>,
) -> Option<ImageFilter> {
    ImageFilter::from_ptr(unsafe {
        sb::C_SkImageFilters_Tile(
            src.as_ref().native(),
            dst.as_ref().native(),
            input.into().into_ptr_or_null(),
        )
    })
}

pub fn dilate(
    (radius_x, radius_y): (scalar, scalar),
    input: impl Into<Option<ImageFilter>>,
    crop_rect: impl Into<CropRect>,
) -> Option<ImageFilter> {
    ImageFilter::from_ptr(unsafe {
        sb::C_SkImageFilters_Dilate(
            radius_x,
            radius_y,
            input.into().into_ptr_or_null(),
            crop_rect.into().native(),
        )
    })
}

pub fn erode(
    (radius_x, radius_y): (scalar, scalar),
    input: impl Into<Option<ImageFilter>>,
    crop_rect: impl Into<CropRect>,
) -> Option<ImageFilter> {
    ImageFilter::from_ptr(unsafe {
        sb::C_SkImageFilters_Erode(
            radius_x,
            radius_y,
            input.into().into_ptr_or_null(),
            crop_rect.into().native(),
        )
    })
}

pub fn distant_lit_diffuse(
    direction: impl Into<Point3>,
    light_color: impl Into<Color>,
    surface_scale: scalar,
    kd: scalar,
    input: impl Into<Option<ImageFilter>>,
    crop_rect: impl Into<CropRect>,
) -> Option<ImageFilter> {
    ImageFilter::from_ptr(unsafe {
        sb::C_SkImageFilters_DistantLitDiffuse(
            direction.into().native(),
            light_color.into().into_native(),
            surface_scale,
            kd,
            input.into().into_ptr_or_null(),
            crop_rect.into().native(),
        )
    })
}

pub fn point_lit_diffuse(
    location: impl Into<Point3>,
    light_color: impl Into<Color>,
    surface_scale: scalar,
    kd: scalar,
    input: impl Into<Option<ImageFilter>>,
    crop_rect: impl Into<CropRect>,
) -> Option<ImageFilter> {
    ImageFilter::from_ptr(unsafe {
        sb::C_SkImageFilters_PointLitDiffuse(
            location.into().native(),
            light_color.into().into_native(),
            surface_scale,
            kd,
            input.into().into_ptr_or_null(),
            crop_rect.into().native(),
        )
    })
}

#[allow(clippy::too_many_arguments)]
pub fn spot_lit_diffuse(
    location: impl Into<Point3>,
    target: impl Into<Point3>,
    specular_exponent: scalar,
    cutoff_angle: scalar,
    light_color: impl Into<Color>,
    surface_scale: scalar,
    kd: scalar,
    input: impl Into<Option<ImageFilter>>,
    crop_rect: impl Into<CropRect>,
) -> Option<ImageFilter> {
    ImageFilter::from_ptr(unsafe {
        sb::C_SkImageFilters_SpotLitDiffuse(
            location.into().native(),
            target.into().native(),
            specular_exponent,
            cutoff_angle,
            light_color.into().into_native(),
            surface_scale,
            kd,
            input.into().into_ptr_or_null(),
            crop_rect.into().native(),
        )
    })
}

pub fn distant_lit_specular(
    direction: impl Into<Point3>,
    light_color: impl Into<Color>,
    surface_scale: scalar,
    ks: scalar,
    shininess: scalar,
    input: impl Into<Option<ImageFilter>>,
    crop_rect: impl Into<CropRect>,
) -> Option<ImageFilter> {
    ImageFilter::from_ptr(unsafe {
        sb::C_ImageFilters_DistantLitSpecular(
            direction.into().native(),
            light_color.into().into_native(),
            surface_scale,
            ks,
            shininess,
            input.into().into_ptr_or_null(),
            crop_rect.into().native(),
        )
    })
}

pub fn point_lit_specular(
    location: impl Into<Point3>,
    light_color: impl Into<Color>,
    surface_scale: scalar,
    ks: scalar,
    shininess: scalar,
    input: impl Into<Option<ImageFilter>>,
    crop_rect: impl Into<CropRect>,
) -> Option<ImageFilter> {
    ImageFilter::from_ptr(unsafe {
        sb::C_SkImageFilters_PointLitSpecular(
            location.into().native(),
            light_color.into().into_native(),
            surface_scale,
            ks,
            shininess,
            input.into().into_ptr_or_null(),
            crop_rect.into().native(),
        )
    })
}

#[allow(clippy::too_many_arguments)]
pub fn spot_lit_specular(
    location: impl Into<Point3>,
    target: impl Into<Point3>,
    specular_exponent: scalar,
    cutoff_angle: scalar,
    light_color: impl Into<Color>,
    surface_scale: scalar,
    ks: scalar,
    shininess: scalar,
    input: impl Into<Option<ImageFilter>>,
    crop_rect: impl Into<CropRect>,
) -> Option<ImageFilter> {
    ImageFilter::from_ptr(unsafe {
        sb::C_SkImageFilters_SpotLitSpecular(
            location.into().native(),
            target.into().native(),
            specular_exponent,
            cutoff_angle,
            light_color.into().into_native(),
            surface_scale,
            ks,
            shininess,
            input.into().into_ptr_or_null(),
            crop_rect.into().native(),
        )
    })
}

impl ImageFilter {
    pub fn alpha_threshold<'a>(
        self,
        crop_rect: impl Into<Option<&'a IRect>>,
        region: &Region,
        inner_min: scalar,
        outer_max: scalar,
    ) -> Option<Self> {
        alpha_threshold(
            region,
            inner_min,
            outer_max,
            self,
            crop_rect.into().map(|r| r.into()),
        )
    }

    pub fn arithmetic<'a>(
        inputs: impl Into<ArithmeticFPInputs>,
        background: impl Into<Option<Self>>,
        foreground: impl Into<Option<Self>>,
        crop_rect: impl Into<Option<&'a IRect>>,
    ) -> Option<Self> {
        let inputs = inputs.into();
        arithmetic(
            inputs.k[0],
            inputs.k[1],
            inputs.k[2],
            inputs.k[3],
            inputs.enforce_pm_color,
            background,
            foreground,
            crop_rect.into().map(|r| r.into()),
        )
    }

    pub fn blur<'a>(
        self,
        crop_rect: impl Into<Option<&'a IRect>>,
        sigma: (scalar, scalar),
        tile_mode: impl Into<Option<crate::TileMode>>,
    ) -> Option<Self> {
        blur(sigma, tile_mode, self, crop_rect.into().map(|r| r.into()))
    }

    pub fn color_filter<'a>(
        self,
        crop_rect: impl Into<Option<&'a IRect>>,
        cf: impl Into<ColorFilter>,
    ) -> Option<Self> {
        color_filter(cf, self, crop_rect.into().map(|r| r.into()))
    }

    pub fn compose(outer: impl Into<ImageFilter>, inner: impl Into<ImageFilter>) -> Option<Self> {
        compose(outer, inner)
    }

    pub fn displacement_map_effect<'a>(
        channel_selectors: (ColorChannel, ColorChannel),
        scale: scalar,
        displacement: impl Into<ImageFilter>,
        color: impl Into<ImageFilter>,
        crop_rect: impl Into<Option<&'a IRect>>,
    ) -> Option<Self> {
        displacement_map(
            channel_selectors,
            scale,
            displacement.into(),
            color,
            crop_rect.into().map(|r| r.into()),
        )
    }

    pub fn distant_lit_diffuse_lighting<'a>(
        self,
        crop_rect: impl Into<Option<&'a IRect>>,
        direction: impl Into<Point3>,
        light_color: impl Into<Color>,
        surface_scale: scalar,
        kd: scalar,
    ) -> Option<Self> {
        distant_lit_diffuse(
            direction,
            light_color,
            surface_scale,
            kd,
            self,
            crop_rect.into().map(|r| r.into()),
        )
    }

    pub fn point_lit_diffuse_lighting<'a>(
        self,
        crop_rect: impl Into<Option<&'a IRect>>,
        location: impl Into<Point3>,
        light_color: impl Into<Color>,
        surface_scale: scalar,
        kd: scalar,
    ) -> Option<Self> {
        point_lit_diffuse(
            location,
            light_color,
            surface_scale,
            kd,
            self,
            crop_rect.into().map(|r| r.into()),
        )
    }

    #[allow(clippy::too_many_arguments)]
    pub fn spot_lit_diffuse_lighting<'a>(
        self,
        crop_rect: impl Into<Option<&'a IRect>>,
        location: impl Into<Point3>,
        target: impl Into<Point3>,
        specular_exponent: scalar,
        cutoff_angle: scalar,
        light_color: impl Into<Color>,
        surface_scale: scalar,
        kd: scalar,
    ) -> Option<Self> {
        spot_lit_diffuse(
            location,
            target,
            specular_exponent,
            cutoff_angle,
            light_color,
            surface_scale,
            kd,
            self,
            crop_rect.into().map(|r| r.into()),
        )
    }

    pub fn distant_lit_specular_lighting<'a>(
        self,
        crop_rect: impl Into<Option<&'a IRect>>,
        direction: impl Into<Point3>,
        light_color: impl Into<Color>,
        surface_scale: scalar,
        ks: scalar,
        shininess: scalar,
    ) -> Option<Self> {
        distant_lit_specular(
            direction,
            light_color,
            surface_scale,
            ks,
            shininess,
            self,
            crop_rect.into().map(|r| r.into()),
        )
    }

    pub fn point_lit_specular_lighting<'a>(
        self,
        crop_rect: impl Into<Option<&'a IRect>>,
        location: impl Into<Point3>,
        light_color: impl Into<Color>,
        surface_scale: scalar,
        ks: scalar,
        shininess: scalar,
    ) -> Option<Self> {
        point_lit_specular(
            location,
            light_color,
            surface_scale,
            ks,
            shininess,
            self,
            crop_rect.into().map(|r| r.into()),
        )
    }

    #[allow(clippy::too_many_arguments)]
    pub fn spot_lit_specular_lighting<'a>(
        self,
        crop_rect: impl Into<Option<&'a IRect>>,
        location: impl Into<Point3>,
        target: impl Into<Point3>,
        specular_exponent: scalar,
        cutoff_angle: scalar,
        light_color: impl Into<Color>,
        surface_scale: scalar,
        ks: scalar,
        shininess: scalar,
    ) -> Option<Self> {
        spot_lit_specular(
            location,
            target,
            specular_exponent,
            cutoff_angle,
            light_color,
            surface_scale,
            ks,
            shininess,
            self,
            crop_rect.into().map(|r| r.into()),
        )
    }

    pub fn magnifier<'a>(
        self,
        crop_rect: impl Into<Option<&'a IRect>>,
        src_rect: impl AsRef<Rect>,
        inset: scalar,
    ) -> Option<Self> {
        magnifier(src_rect, inset, self, crop_rect.into().map(|r| r.into()))
    }

    #[allow(clippy::too_many_arguments)]
    pub fn matrix_convolution<'a>(
        self,
        crop_rect: impl Into<Option<&'a IRect>>,
        kernel_size: impl Into<ISize>,
        kernel: &[scalar],
        gain: scalar,
        bias: scalar,
        kernel_offset: impl Into<IPoint>,
        tile_mode: crate::TileMode,
        convolve_alpha: bool,
    ) -> Option<Self> {
        matrix_convolution(
            kernel_size,
            kernel,
            gain,
            bias,
            kernel_offset,
            tile_mode,
            convolve_alpha,
            self,
            crop_rect.into().map(|r| r.into()),
        )
    }

    pub fn merge<'a>(
        filters: impl IntoIterator<Item = Option<Self>>,
        crop_rect: impl Into<Option<&'a IRect>>,
    ) -> Option<Self> {
        merge(filters, crop_rect.into().map(|r| r.into()))
    }

    pub fn dilate<'a>(
        self,
        crop_rect: impl Into<Option<&'a IRect>>,
        radii: (scalar, scalar),
    ) -> Option<Self> {
        dilate(radii, self, crop_rect.into().map(|r| r.into()))
    }

    pub fn erode<'a>(
        self,
        crop_rect: impl Into<Option<&'a IRect>>,
        radii: (scalar, scalar),
    ) -> Option<Self> {
        erode(radii, self, crop_rect.into().map(|r| r.into()))
    }

    pub fn offset<'a>(
        self,
        crop_rect: impl Into<Option<&'a IRect>>,
        delta: impl Into<Vector>,
    ) -> Option<Self> {
        offset(delta, self, crop_rect.into().map(|r| r.into()))
    }

    pub fn from_paint<'a>(paint: &Paint, crop_rect: impl Into<Option<&'a IRect>>) -> Option<Self> {
        self::paint(paint, crop_rect.into().map(|r| r.into()))
    }

    pub fn from_picture<'a>(
        picture: impl Into<Picture>,
        crop_rect: impl Into<Option<&'a Rect>>,
    ) -> Option<Self> {
        self::picture(picture, crop_rect)
    }

    pub fn tile(self, src: impl AsRef<Rect>, dst: impl AsRef<Rect>) -> Option<Self> {
        tile(src, dst, self)
    }
}

#[derive(Copy, Clone, PartialEq, Debug)]
pub struct ArithmeticFPInputs {
    pub k: [f32; 4],
    pub enforce_pm_color: bool,
}

impl From<([f32; 4], bool)> for ArithmeticFPInputs {
    fn from((k, enforce_pm_color): ([f32; 4], bool)) -> Self {
        ArithmeticFPInputs {
            k,
            enforce_pm_color,
        }
    }
}

impl ArithmeticFPInputs {
    pub fn new(k0: f32, k1: f32, k2: f32, k3: f32, enforce_pm_color: bool) -> Self {
        Self {
            k: [k0, k1, k2, k3],
            enforce_pm_color,
        }
    }
}

impl Paint {
    pub fn as_image_filter<'a>(
        &self,
        crop_rect: impl Into<Option<&'a IRect>>,
    ) -> Option<ImageFilter> {
        paint(self, crop_rect.into().map(|r| r.into()))
    }
}

impl Picture {
    pub fn as_image_filter<'a>(
        &self,
        crop_rect: impl Into<Option<&'a Rect>>,
    ) -> Option<ImageFilter> {
        self.clone().into_image_filter(crop_rect)
    }

    pub fn into_image_filter<'a>(
        self,
        crop_rect: impl Into<Option<&'a Rect>>,
    ) -> Option<ImageFilter> {
        picture(self, crop_rect)
    }
}

#[cfg(test)]
mod tests {
    use super::CropRect;
    use crate::{IRect, Rect};

    fn cr(crop_rect: impl Into<CropRect>) -> CropRect {
        crop_rect.into()
    }

    #[test]
    fn test_crop_conversion_options() {
        assert_eq!(cr(None), CropRect::NO_CROP_RECT);
        assert_eq!(cr(CropRect::NO_CROP_RECT), CropRect::NO_CROP_RECT);
        #[allow(clippy::needless_borrow)]
        let cr_ref = cr(&CropRect::NO_CROP_RECT);
        assert_eq!(cr_ref, CropRect::NO_CROP_RECT);
        let irect = IRect {
            left: 1,
            top: 2,
            right: 3,
            bottom: 4,
        };
        assert_eq!(cr(irect), CropRect(Rect::from(irect)));
        #[allow(clippy::needless_borrow)]
        let cr_by_ref = cr(&irect);
        assert_eq!(cr_by_ref, CropRect(Rect::from(irect)));
        let rect = Rect {
            left: 1.0,
            top: 2.0,
            right: 3.0,
            bottom: 4.0,
        };
        assert_eq!(cr(rect), CropRect(rect));
        #[allow(clippy::needless_borrow)]
        let cr_by_ref = cr(&rect);
        assert_eq!(cr_by_ref, CropRect(rect));
    }
}