skia_safe/effects/
image_filters.rs

1use std::ptr;
2
3use skia_bindings::{self as sb, SkImageFilter, SkRect};
4
5use crate::{
6    prelude::*, scalar, Blender, Color, Color4f, ColorChannel, ColorFilter, ColorSpace,
7    CubicResampler, IPoint, IRect, ISize, Image, ImageFilter, Matrix, Picture, Point3, Rect,
8    SamplingOptions, Shader, TileMode, Vector,
9};
10
11/// This is just a convenience type to allow passing [`IRect`]s, [`Rect`]s, and optional references
12/// to those types as a crop rect for the image filter factories. It's not intended to be used
13/// directly.
14#[repr(C)]
15#[derive(Copy, Clone, PartialEq, Debug)]
16pub struct CropRect(Option<Rect>);
17
18impl CropRect {
19    pub const NO_CROP_RECT: CropRect = CropRect(None);
20
21    pub fn rect(&self) -> Option<Rect> {
22        self.0
23    }
24
25    fn native(&self) -> *const SkRect {
26        match &self.0 {
27            None => ptr::null(),
28            Some(r) => r.native(),
29        }
30    }
31}
32
33impl Default for CropRect {
34    fn default() -> Self {
35        CropRect::NO_CROP_RECT
36    }
37}
38
39impl From<Option<CropRect>> for CropRect {
40    fn from(opt: Option<CropRect>) -> Self {
41        opt.unwrap_or(Self::NO_CROP_RECT)
42    }
43}
44
45impl From<&CropRect> for CropRect {
46    fn from(cr: &CropRect) -> Self {
47        *cr
48    }
49}
50
51impl From<IRect> for CropRect {
52    fn from(r: IRect) -> Self {
53        Self(Some(Rect::from(r)))
54    }
55}
56
57impl From<&IRect> for CropRect {
58    fn from(r: &IRect) -> Self {
59        Self::from(*r)
60    }
61}
62
63impl From<Rect> for CropRect {
64    fn from(r: Rect) -> Self {
65        Self(Some(r))
66    }
67}
68
69impl From<&Rect> for CropRect {
70    fn from(r: &Rect) -> Self {
71        Self(Some(*r))
72    }
73}
74
75/// Create a filter that implements a custom blend mode. Each output pixel is the result of
76/// combining the corresponding background and foreground pixels using the 4 coefficients:
77///    k1 * foreground * background + k2 * foreground + k3 * background + k4
78/// * `k1`, `k2`, `k3`, `k4` The four coefficients used to combine the foreground and background.
79/// * `enforce_pm_color` - If `true`, the RGB channels will be clamped to the calculated alpha.
80/// * `background` - The background content, using the source bitmap when this is null.
81/// * `foreground` - The foreground content, using the source bitmap when this is null.
82/// * `crop_rect` - Optional rectangle that crops the inputs and output.
83#[allow(clippy::too_many_arguments)]
84pub fn arithmetic(
85    k1: scalar,
86    k2: scalar,
87    k3: scalar,
88    k4: scalar,
89    enforce_pm_color: bool,
90    background: impl Into<Option<ImageFilter>>,
91    foreground: impl Into<Option<ImageFilter>>,
92    crop_rect: impl Into<CropRect>,
93) -> Option<ImageFilter> {
94    ImageFilter::from_ptr(unsafe {
95        sb::C_SkImageFilters_Arithmetic(
96            k1,
97            k2,
98            k3,
99            k4,
100            enforce_pm_color,
101            background.into().into_ptr_or_null(),
102            foreground.into().into_ptr_or_null(),
103            crop_rect.into().native(),
104        )
105    })
106}
107
108/// This filter takes an [`crate::BlendMode`] and uses it to composite the two filters together.
109/// * `blender` - The blender that defines the compositing operation
110/// * `background` - The Dst pixels used in blending, if null the source bitmap is used.
111/// * `foreground` - The Src pixels used in blending, if null the source bitmap is used.
112/// * `crop_rect``         Optional rectangle to crop input and output.
113pub fn blend(
114    mode: impl Into<Blender>,
115    background: impl Into<Option<ImageFilter>>,
116    foreground: impl Into<Option<ImageFilter>>,
117    crop_rect: impl Into<CropRect>,
118) -> Option<ImageFilter> {
119    ImageFilter::from_ptr(unsafe {
120        sb::C_SkImageFilters_Blend(
121            mode.into().into_ptr(),
122            background.into().into_ptr_or_null(),
123            foreground.into().into_ptr_or_null(),
124            crop_rect.into().native(),
125        )
126    })
127}
128
129/// Create a filter that blurs its input by the separate X and Y sigmas. The provided tile mode
130/// is used when the blur kernel goes outside the input image.
131/// * `sigma_x` - The Gaussian sigma value for blurring along the X axis.
132/// * `sigma_y` - The Gaussian sigma value for blurring along the Y axis.
133/// * `tile_mode` - The tile mode applied at edges .
134/// * `input` - The input filter that is blurred, uses source bitmap if this is null.
135/// * `crop_rect` - Optional rectangle that crops the input and output.
136pub fn blur(
137    (sigma_x, sigma_y): (scalar, scalar),
138    tile_mode: impl Into<Option<TileMode>>,
139    input: impl Into<Option<ImageFilter>>,
140    crop_rect: impl Into<CropRect>,
141) -> Option<ImageFilter> {
142    ImageFilter::from_ptr(unsafe {
143        sb::C_SkImageFilters_Blur(
144            sigma_x,
145            sigma_y,
146            tile_mode.into().unwrap_or(TileMode::Decal),
147            input.into().into_ptr_or_null(),
148            crop_rect.into().native(),
149        )
150    })
151}
152
153pub fn color_filter(
154    cf: impl Into<ColorFilter>,
155    input: impl Into<Option<ImageFilter>>,
156    crop_rect: impl Into<CropRect>,
157) -> Option<ImageFilter> {
158    ImageFilter::from_ptr(unsafe {
159        sb::C_SkImageFilters_ColorFilter(
160            cf.into().into_ptr(),
161            input.into().into_ptr_or_null(),
162            crop_rect.into().native(),
163        )
164    })
165}
166
167/// Create a filter that composes 'inner' with 'outer', such that the results of 'inner' are
168/// treated as the source bitmap passed to 'outer', i.e. result = outer(inner(source)).
169/// * `outer` - The outer filter that evaluates the results of inner.
170/// * `inner` - The inner filter that produces the input to outer.
171pub fn compose(
172    outer: impl Into<ImageFilter>,
173    inner: impl Into<ImageFilter>,
174) -> Option<ImageFilter> {
175    ImageFilter::from_ptr(unsafe {
176        sb::C_SkImageFilters_Compose(outer.into().into_ptr(), inner.into().into_ptr())
177    })
178}
179
180/// Create a filter that applies a crop to the result of the 'input' filter. Pixels within the
181/// crop rectangle are unmodified from what 'input' produced. Pixels outside of crop match the
182/// provided [`TileMode`] (defaulting to `Decal`).
183///
184/// * `rect` - The cropping geometry
185/// * `tile_mode` - The tile mode applied to pixels *outside* of 'crop'
186/// * `input` - The input filter that is cropped, uses source image if this is `None`
187pub fn crop(
188    rect: impl AsRef<Rect>,
189    tile_mode: impl Into<Option<TileMode>>,
190    input: impl Into<Option<ImageFilter>>,
191) -> Option<ImageFilter> {
192    ImageFilter::from_ptr(unsafe {
193        sb::C_SkImageFilters_Crop(
194            rect.as_ref().native(),
195            tile_mode.into().unwrap_or(TileMode::Decal),
196            input.into().into_ptr_or_null(),
197        )
198    })
199}
200
201/// Create a filter that moves each pixel in its color input based on an (x,y) vector encoded
202/// in its displacement input filter. Two color components of the displacement image are
203/// mapped into a vector as `scale * (color[xChannel], color[yChannel])`, where the channel
204/// selectors are one of R, G, B, or A.
205/// * `x_channel_selector` - RGBA channel that encodes the x displacement per pixel.
206/// * `y_channel_selector` - RGBA channel that encodes the y displacement per pixel.
207/// * `scale` - Scale applied to displacement extracted from image.
208/// * `displacement` - The filter defining the displacement image, or `None` to use source.
209/// * `color` - The filter providing the color pixels to be displaced. If `None`,
210///                         it will use the source.
211/// * `crop_rect` - Optional rectangle that crops the color input and output.
212pub fn displacement_map(
213    (x_channel_selector, y_channel_selector): (ColorChannel, ColorChannel),
214    scale: scalar,
215    displacement: impl Into<Option<ImageFilter>>,
216    color: impl Into<Option<ImageFilter>>,
217    crop_rect: impl Into<CropRect>,
218) -> Option<ImageFilter> {
219    ImageFilter::from_ptr(unsafe {
220        sb::C_SkImageFilters_DisplacementMap(
221            x_channel_selector,
222            y_channel_selector,
223            scale,
224            displacement.into().into_ptr_or_null(),
225            color.into().into_ptr_or_null(),
226            crop_rect.into().native(),
227        )
228    })
229}
230
231/// Create a filter that draws a drop shadow under the input content. This filter produces an
232/// image that includes the inputs' content.
233/// * `offset` - The offset of the shadow.
234/// * `sigma_x` - The blur radius for the shadow, along the X axis.
235/// * `sigma_y` - The blur radius for the shadow, along the Y axis.
236/// * `color` - The color of the drop shadow.
237/// * `color_space` - The color space of the drop shadow color.
238/// * `input` - The input filter, or will use the source bitmap if this is null.
239/// * `crop_rect` - Optional rectangle that crops the input and output.
240pub fn drop_shadow(
241    offset: impl Into<Vector>,
242    (sigma_x, sigma_y): (scalar, scalar),
243    color: impl Into<Color4f>,
244    color_space: impl Into<Option<ColorSpace>>,
245    input: impl Into<Option<ImageFilter>>,
246    crop_rect: impl Into<CropRect>,
247) -> Option<ImageFilter> {
248    let delta = offset.into();
249    ImageFilter::from_ptr(unsafe {
250        sb::C_SkImageFilters_DropShadow(
251            delta.x,
252            delta.y,
253            sigma_x,
254            sigma_y,
255            color.into().native(),
256            color_space.into().into_ptr_or_null(),
257            input.into().into_ptr_or_null(),
258            crop_rect.into().native(),
259        )
260    })
261}
262
263/// Create a filter that renders a drop shadow, in exactly the same manner as ::DropShadow,
264/// except that the resulting image does not include the input content. This allows the shadow
265/// and input to be composed by a filter DAG in a more flexible manner.
266/// * `offset` - The offset of the shadow.
267/// * `sigma_x` - The blur radius for the shadow, along the X axis.
268/// * `sigma_y` - The blur radius for the shadow, along the Y axis.
269/// * `color` - The color of the drop shadow.
270/// * `color_space` - The color space of the drop shadow color.
271/// * `input` - The input filter, or will use the source bitmap if this is null.
272/// * `crop_rect` - Optional rectangle that crops the input and output.
273pub fn drop_shadow_only(
274    offset: impl Into<Vector>,
275    (sigma_x, sigma_y): (scalar, scalar),
276    color: impl Into<Color4f>,
277    color_space: impl Into<Option<ColorSpace>>,
278    input: impl Into<Option<ImageFilter>>,
279    crop_rect: impl Into<CropRect>,
280) -> Option<ImageFilter> {
281    let delta = offset.into();
282
283    ImageFilter::from_ptr(unsafe {
284        sb::C_SkImageFilters_DropShadowOnly(
285            delta.x,
286            delta.y,
287            sigma_x,
288            sigma_y,
289            color.into().native(),
290            color_space.into().into_ptr_or_null(),
291            input.into().into_ptr_or_null(),
292            crop_rect.into().native(),
293        )
294    })
295}
296
297/// Create a filter that always produces transparent black.
298pub fn empty() -> ImageFilter {
299    ImageFilter::from_ptr(unsafe { sb::C_SkImageFilters_Empty() }).unwrap()
300}
301/// Create a filter that draws the 'src_rect' portion of image into 'dst_rect' using the given
302/// filter quality. Similar to [`crate::Canvas::draw_image_rect()`].
303///
304/// * `image` - The image that is output by the filter, subset by 'srcRect'.
305/// * `src_rect` - The source pixels sampled into 'dstRect'
306/// * `dst_rect` - The local rectangle to draw the image into.
307/// * `sampling` - The sampling to use when drawing the image.
308pub fn image<'a>(
309    image: impl Into<Image>,
310    src_rect: impl Into<Option<&'a Rect>>,
311    dst_rect: impl Into<Option<&'a Rect>>,
312    sampling: impl Into<Option<SamplingOptions>>,
313) -> Option<ImageFilter> {
314    let image = image.into();
315    let image_rect = Rect::from_iwh(image.width(), image.height());
316    let src_rect = src_rect.into().unwrap_or(&image_rect);
317    let dst_rect = dst_rect.into().unwrap_or(&image_rect);
318    let sampling_options: SamplingOptions = sampling.into().unwrap_or_else(|| {
319        CubicResampler {
320            b: 1.0 / 3.0,
321            c: 1.0 / 3.0,
322        }
323        .into()
324    });
325
326    ImageFilter::from_ptr(unsafe {
327        sb::C_SkImageFilters_Image(
328            image.into_ptr(),
329            src_rect.as_ref().native(),
330            dst_rect.as_ref().native(),
331            sampling_options.native(),
332        )
333    })
334}
335
336/// Create a filter that fills 'lens_bounds' with a magnification of the input.
337///
338/// * `lens_bounds` - The outer bounds of the magnifier effect
339/// * `zoom_amount` - The amount of magnification applied to the input image
340/// * `inset` - The size or width of the fish-eye distortion around the magnified content
341/// * `sampling` - The [`SamplingOptions`] applied to the input image when magnified
342/// * `input` - The input filter that is magnified; if null the source bitmap is used
343/// * `crop_rect` - Optional rectangle that crops the input and output.
344pub fn magnifier(
345    lens_bounds: impl AsRef<Rect>,
346    zoom_amount: scalar,
347    inset: scalar,
348    sampling: SamplingOptions,
349    input: impl Into<Option<ImageFilter>>,
350    crop_rect: impl Into<CropRect>,
351) -> Option<ImageFilter> {
352    ImageFilter::from_ptr(unsafe {
353        sb::C_SkImageFilters_Magnifier(
354            lens_bounds.as_ref().native(),
355            zoom_amount,
356            inset,
357            sampling.native(),
358            input.into().into_ptr_or_null(),
359            crop_rect.into().native(),
360        )
361    })
362}
363
364/// Create a filter that applies an NxM image processing kernel to the input image. This can be
365/// used to produce effects such as sharpening, blurring, edge detection, etc.
366/// * `kernel_size` - The kernel size in pixels, in each dimension (N by M).
367/// * `kernel` - The image processing kernel. Must contain N * M elements, in row order.
368/// * `gain` - A scale factor applied to each pixel after convolution. This can be
369///                      used to normalize the kernel, if it does not already sum to 1.
370/// * `bias` - A bias factor added to each pixel after convolution.
371/// * `kernel_offset` - An offset applied to each pixel coordinate before convolution.
372///                      This can be used to center the kernel over the image
373///                      (e.g., a 3x3 kernel should have an offset of {1, 1}).
374/// * `tile_mode` - How accesses outside the image are treated.
375/// * `convolve_alpha` - If `true`, all channels are convolved. If `false`, only the RGB channels
376///                      are convolved, and alpha is copied from the source image.
377/// * `input` - The input image filter, if null the source bitmap is used instead.
378/// * `crop_rect` - Optional rectangle to which the output processing will be limited.
379#[allow(clippy::too_many_arguments)]
380pub fn matrix_convolution(
381    kernel_size: impl Into<ISize>,
382    kernel: &[scalar],
383    gain: scalar,
384    bias: scalar,
385    kernel_offset: impl Into<IPoint>,
386    tile_mode: TileMode,
387    convolve_alpha: bool,
388    input: impl Into<Option<ImageFilter>>,
389    crop_rect: impl Into<CropRect>,
390) -> Option<ImageFilter> {
391    let kernel_size = kernel_size.into();
392    assert_eq!(
393        (kernel_size.width * kernel_size.height) as usize,
394        kernel.len()
395    );
396    ImageFilter::from_ptr(unsafe {
397        sb::C_SkImageFilters_MatrixConvolution(
398            kernel_size.native(),
399            kernel.as_ptr(),
400            gain,
401            bias,
402            kernel_offset.into().native(),
403            tile_mode,
404            convolve_alpha,
405            input.into().into_ptr_or_null(),
406            crop_rect.into().native(),
407        )
408    })
409}
410
411/// Create a filter that transforms the input image by 'matrix'. This matrix transforms the
412/// local space, which means it effectively happens prior to any transformation coming from the
413/// [`crate::Canvas`] initiating the filtering.
414/// * `matrix` - The matrix to apply to the original content.
415/// * `sampling` - How the image will be sampled when it is transformed
416/// * `input` - The image filter to transform, or null to use the source image.
417pub fn matrix_transform(
418    matrix: &Matrix,
419    sampling: impl Into<SamplingOptions>,
420    input: impl Into<Option<ImageFilter>>,
421) -> Option<ImageFilter> {
422    ImageFilter::from_ptr(unsafe {
423        sb::C_SkImageFilters_MatrixTransform(
424            matrix.native(),
425            sampling.into().native(),
426            input.into().into_ptr_or_null(),
427        )
428    })
429}
430
431/// Create a filter that merges the filters together by drawing their results in order
432/// with src-over blending.
433/// * `filters` - The input filter array to merge. Any None
434///                 filter pointers will use the source bitmap instead.
435/// * `crop_rect` - Optional rectangle that crops all input filters and the output.
436pub fn merge(
437    filters: impl IntoIterator<Item = Option<ImageFilter>>,
438    crop_rect: impl Into<CropRect>,
439) -> Option<ImageFilter> {
440    let filter_ptrs: Vec<*mut SkImageFilter> =
441        filters.into_iter().map(|f| f.into_ptr_or_null()).collect();
442    ImageFilter::from_ptr(unsafe {
443        sb::C_SkImageFilters_Merge(
444            filter_ptrs.as_ptr(),
445            filter_ptrs.len().try_into().unwrap(),
446            crop_rect.into().native(),
447        )
448    })
449}
450
451/// Create a filter that offsets the input filter by the given vector.
452/// * `offset` - The offset in local space that the image is shifted.
453/// * `input` - The input that will be moved, if null the source bitmap is used instead.
454/// * `crop_rect` - Optional rectangle to crop the input and output.
455pub fn offset(
456    offset: impl Into<Vector>,
457    input: impl Into<Option<ImageFilter>>,
458    crop_rect: impl Into<CropRect>,
459) -> Option<ImageFilter> {
460    let delta = offset.into();
461    ImageFilter::from_ptr(unsafe {
462        sb::C_SkImageFilters_Offset(
463            delta.x,
464            delta.y,
465            input.into().into_ptr_or_null(),
466            crop_rect.into().native(),
467        )
468    })
469}
470
471/// Create a filter that produces the [`Picture`] as its output, clipped to both 'target_rect' and
472/// the picture's internal cull rect.
473///
474/// * `pic` - The picture that is drawn for the filter output.
475/// * `target_rect` - The drawing region for the picture.
476pub fn picture<'a>(
477    pic: impl Into<Picture>,
478    target_rect: impl Into<Option<&'a Rect>>,
479) -> Option<ImageFilter> {
480    let picture = pic.into();
481    let picture_rect = picture.cull_rect();
482    let target_rect = target_rect.into().unwrap_or(&picture_rect);
483
484    ImageFilter::from_ptr(unsafe {
485        sb::C_SkImageFilters_Picture(picture.into_ptr(), target_rect.native())
486    })
487}
488
489pub fn runtime_shader(
490    builder: &RuntimeShaderBuilder,
491    child_shader_name: impl AsRef<str>,
492    input: impl Into<Option<ImageFilter>>,
493) -> Option<ImageFilter> {
494    let child_shader_name = child_shader_name.as_ref();
495    unsafe {
496        ImageFilter::from_ptr(sb::C_SkImageFilters_RuntimeShader(
497            builder.native() as *const _,
498            child_shader_name.as_ptr() as *const _,
499            child_shader_name.len(),
500            input.into().into_ptr_or_null(),
501        ))
502    }
503}
504
505pub use skia_bindings::SkImageFilters_Dither as Dither;
506
507use super::runtime_effect::RuntimeShaderBuilder;
508variant_name!(Dither::Yes);
509
510/// Create a filter that fills the output with the per-pixel evaluation of the [`Shader`]. The
511/// shader is defined in the image filter's local coordinate system, so will automatically
512/// be affected by [`Canvas'`] transform.
513///
514/// Like `image()` and Picture(), this is a leaf filter that can be used to introduce inputs to
515/// a complex filter graph, but should generally be combined with a filter that as at least
516/// one null input to use the implicit source image.
517///
518/// * `shader` - The shader that fills the result image
519pub fn shader(shader: impl Into<Shader>, crop_rect: impl Into<CropRect>) -> Option<ImageFilter> {
520    shader_with_dither(shader, Dither::No, crop_rect)
521}
522
523pub fn shader_with_dither(
524    shader: impl Into<Shader>,
525    dither: Dither,
526    crop_rect: impl Into<CropRect>,
527) -> Option<ImageFilter> {
528    ImageFilter::from_ptr(unsafe {
529        sb::C_SkImageFilters_Shader(shader.into().into_ptr(), dither, crop_rect.into().native())
530    })
531}
532
533/// Create a tile image filter.
534/// * `src` - Defines the pixels to tile
535/// * `dst` - Defines the pixel region that the tiles will be drawn to
536/// * `input` - The input that will be tiled, if null the source bitmap is used instead.
537pub fn tile(
538    src: impl AsRef<Rect>,
539    dst: impl AsRef<Rect>,
540    input: impl Into<Option<ImageFilter>>,
541) -> Option<ImageFilter> {
542    ImageFilter::from_ptr(unsafe {
543        sb::C_SkImageFilters_Tile(
544            src.as_ref().native(),
545            dst.as_ref().native(),
546            input.into().into_ptr_or_null(),
547        )
548    })
549}
550
551/// Create a filter that dilates each input pixel's channel values to the max value within the
552/// given radii along the x and y axes.
553/// * `radius_x` - The distance to dilate along the x axis to either side of each pixel.
554/// * `radius_y` - The distance to dilate along the y axis to either side of each pixel.
555/// * `input` - The image filter that is dilated, using source bitmap if this is null.
556/// * `crop_rect` - Optional rectangle that crops the input and output.
557pub fn dilate(
558    (radius_x, radius_y): (scalar, scalar),
559    input: impl Into<Option<ImageFilter>>,
560    crop_rect: impl Into<CropRect>,
561) -> Option<ImageFilter> {
562    ImageFilter::from_ptr(unsafe {
563        sb::C_SkImageFilters_Dilate(
564            radius_x,
565            radius_y,
566            input.into().into_ptr_or_null(),
567            crop_rect.into().native(),
568        )
569    })
570}
571
572/// Create a filter that erodes each input pixel's channel values to the minimum channel value
573/// within the given radii along the x and y axes.
574/// * `radius_x` - The distance to erode along the x axis to either side of each pixel.
575/// * `radius_y` - The distance to erode along the y axis to either side of each pixel.
576/// * `input` - The image filter that is eroded, using source bitmap if this is null.
577/// * `crop_rect` - Optional rectangle that crops the input and output.
578pub fn erode(
579    (radius_x, radius_y): (scalar, scalar),
580    input: impl Into<Option<ImageFilter>>,
581    crop_rect: impl Into<CropRect>,
582) -> Option<ImageFilter> {
583    ImageFilter::from_ptr(unsafe {
584        sb::C_SkImageFilters_Erode(
585            radius_x,
586            radius_y,
587            input.into().into_ptr_or_null(),
588            crop_rect.into().native(),
589        )
590    })
591}
592
593/// Create a filter that calculates the diffuse illumination from a distant light source,
594/// interpreting the alpha channel of the input as the height profile of the surface (to
595/// approximate normal vectors).
596/// * `direction` - The direction to the distance light.
597/// * `light_color` - The color of the diffuse light source.
598/// * `surface_scale` - Scale factor to transform from alpha values to physical height.
599/// * `kd` - Diffuse reflectance coefficient.
600/// * `input` - The input filter that defines surface normals (as alpha), or uses the
601///                     source bitmap when null.
602/// * `crop_rect` - Optional rectangle that crops the input and output.
603pub fn distant_lit_diffuse(
604    direction: impl Into<Point3>,
605    light_color: impl Into<Color>,
606    surface_scale: scalar,
607    kd: scalar,
608    input: impl Into<Option<ImageFilter>>,
609    crop_rect: impl Into<CropRect>,
610) -> Option<ImageFilter> {
611    ImageFilter::from_ptr(unsafe {
612        sb::C_SkImageFilters_DistantLitDiffuse(
613            direction.into().native(),
614            light_color.into().into_native(),
615            surface_scale,
616            kd,
617            input.into().into_ptr_or_null(),
618            crop_rect.into().native(),
619        )
620    })
621}
622
623/// Create a filter that calculates the diffuse illumination from a point light source, using
624/// alpha channel of the input as the height profile of the surface (to approximate normal
625/// vectors).
626/// * `location` - The location of the point light.
627/// * `light_color` - The color of the diffuse light source.
628/// * `surface_scale` - Scale factor to transform from alpha values to physical height.
629/// * `kd` - Diffuse reflectance coefficient.
630/// * `input` - The input filter that defines surface normals (as alpha), or uses the
631///                     source bitmap when `None`.
632/// * `crop_rect` - Optional rectangle that crops the input and output.
633pub fn point_lit_diffuse(
634    location: impl Into<Point3>,
635    light_color: impl Into<Color>,
636    surface_scale: scalar,
637    kd: scalar,
638    input: impl Into<Option<ImageFilter>>,
639    crop_rect: impl Into<CropRect>,
640) -> Option<ImageFilter> {
641    ImageFilter::from_ptr(unsafe {
642        sb::C_SkImageFilters_PointLitDiffuse(
643            location.into().native(),
644            light_color.into().into_native(),
645            surface_scale,
646            kd,
647            input.into().into_ptr_or_null(),
648            crop_rect.into().native(),
649        )
650    })
651}
652
653/// Create a filter that calculates the diffuse illumination from a spot light source, using
654/// alpha channel of the input as the height profile of the surface (to approximate normal
655/// vectors). The spot light is restricted to be within 'cutoff_angle' of the vector between
656/// the location and target.
657/// * `location` - The location of the spot light.
658/// * `target` - The location that the spot light is point towards
659/// * `falloff_exponent` - Exponential falloff parameter for illumination outside of `cutoff_angle`
660/// * `cutoff_angle` - Maximum angle from lighting direction that receives full light
661/// * `light_color` - The color of the diffuse light source.
662/// * `surface_scale` - Scale factor to transform from alpha values to physical height.
663/// * `kd` - Diffuse reflectance coefficient.
664/// * `input` - The input filter that defines surface normals (as alpha), or uses the
665///                        source bitmap when null.
666/// * `crop_rect` - Optional rectangle that crops the input and output.
667#[allow(clippy::too_many_arguments)]
668pub fn spot_lit_diffuse(
669    location: impl Into<Point3>,
670    target: impl Into<Point3>,
671    falloff_exponent: scalar,
672    cutoff_angle: scalar,
673    light_color: impl Into<Color>,
674    surface_scale: scalar,
675    kd: scalar,
676    input: impl Into<Option<ImageFilter>>,
677    crop_rect: impl Into<CropRect>,
678) -> Option<ImageFilter> {
679    ImageFilter::from_ptr(unsafe {
680        sb::C_SkImageFilters_SpotLitDiffuse(
681            location.into().native(),
682            target.into().native(),
683            falloff_exponent,
684            cutoff_angle,
685            light_color.into().into_native(),
686            surface_scale,
687            kd,
688            input.into().into_ptr_or_null(),
689            crop_rect.into().native(),
690        )
691    })
692}
693
694/// Create a filter that calculates the specular illumination from a distant light source,
695/// interpreting the alpha channel of the input as the height profile of the surface (to
696/// approximate normal vectors).
697/// * `direction` - The direction to the distance light.
698/// * `light_color` - The color of the specular light source.
699/// * `surface_scale` - Scale factor to transform from alpha values to physical height.
700/// * `ks` - Specular reflectance coefficient.
701/// * `shininess` - The specular exponent determining how shiny the surface is.
702/// * `input` - The input filter that defines surface normals (as alpha), or uses the
703///                     source bitmap when `None`.
704/// * `crop_rect` - Optional rectangle that crops the input and output.
705pub fn distant_lit_specular(
706    direction: impl Into<Point3>,
707    light_color: impl Into<Color>,
708    surface_scale: scalar,
709    ks: scalar,
710    shininess: scalar,
711    input: impl Into<Option<ImageFilter>>,
712    crop_rect: impl Into<CropRect>,
713) -> Option<ImageFilter> {
714    ImageFilter::from_ptr(unsafe {
715        sb::C_ImageFilters_DistantLitSpecular(
716            direction.into().native(),
717            light_color.into().into_native(),
718            surface_scale,
719            ks,
720            shininess,
721            input.into().into_ptr_or_null(),
722            crop_rect.into().native(),
723        )
724    })
725}
726
727/// Create a filter that calculates the specular illumination from a point light source, using
728/// alpha channel of the input as the height profile of the surface (to approximate normal
729/// vectors).
730/// * `location` - The location of the point light.
731/// * `light_color` - The color of the specular light source.
732/// * `surface_scale` - Scale factor to transform from alpha values to physical height.
733/// * `ks` - Specular reflectance coefficient.
734/// * `shininess` - The specular exponent determining how shiny the surface is.
735/// * `input` - The input filter that defines surface normals (as alpha), or uses the
736///                     source bitmap when `None`.
737/// * `crop_rect` - Optional rectangle that crops the input and output.
738pub fn point_lit_specular(
739    location: impl Into<Point3>,
740    light_color: impl Into<Color>,
741    surface_scale: scalar,
742    ks: scalar,
743    shininess: scalar,
744    input: impl Into<Option<ImageFilter>>,
745    crop_rect: impl Into<CropRect>,
746) -> Option<ImageFilter> {
747    ImageFilter::from_ptr(unsafe {
748        sb::C_SkImageFilters_PointLitSpecular(
749            location.into().native(),
750            light_color.into().into_native(),
751            surface_scale,
752            ks,
753            shininess,
754            input.into().into_ptr_or_null(),
755            crop_rect.into().native(),
756        )
757    })
758}
759
760/// Create a filter that calculates the specular illumination from a spot light source, using
761/// alpha channel of the input as the height profile of the surface (to approximate normal
762/// vectors). The spot light is restricted to be within 'cutoff_angle' of the vector between
763/// the location and target.
764/// * `location` - The location of the spot light.
765/// * `target` - The location that the spot light is point towards
766/// * `falloff_exponent` - Exponential falloff parameter for illumination outside of `cutoff_angle`
767/// * `cutoff_angle` - Maximum angle from lighting direction that receives full light
768/// * `light_color` - The color of the specular light source.
769/// * `surface_scale` - Scale factor to transform from alpha values to physical height.
770/// * `ks` - Specular reflectance coefficient.
771/// * `shininess` - The specular exponent determining how shiny the surface is.
772/// * `input` - The input filter that defines surface normals (as alpha), or uses the
773///                        source bitmap when null.
774/// * `crop_rect` - Optional rectangle that crops the input and output.
775#[allow(clippy::too_many_arguments)]
776pub fn spot_lit_specular(
777    location: impl Into<Point3>,
778    target: impl Into<Point3>,
779    falloff_exponent: scalar,
780    cutoff_angle: scalar,
781    light_color: impl Into<Color>,
782    surface_scale: scalar,
783    ks: scalar,
784    shininess: scalar,
785    input: impl Into<Option<ImageFilter>>,
786    crop_rect: impl Into<CropRect>,
787) -> Option<ImageFilter> {
788    ImageFilter::from_ptr(unsafe {
789        sb::C_SkImageFilters_SpotLitSpecular(
790            location.into().native(),
791            target.into().native(),
792            falloff_exponent,
793            cutoff_angle,
794            light_color.into().into_native(),
795            surface_scale,
796            ks,
797            shininess,
798            input.into().into_ptr_or_null(),
799            crop_rect.into().native(),
800        )
801    })
802}
803
804impl ImageFilter {
805    /// [`arithmetic()`]
806    pub fn arithmetic<'a>(
807        inputs: impl Into<ArithmeticFPInputs>,
808        background: impl Into<Option<Self>>,
809        foreground: impl Into<Option<Self>>,
810        crop_rect: impl Into<Option<&'a IRect>>,
811    ) -> Option<Self> {
812        let inputs = inputs.into();
813        arithmetic(
814            inputs.k[0],
815            inputs.k[1],
816            inputs.k[2],
817            inputs.k[3],
818            inputs.enforce_pm_color,
819            background,
820            foreground,
821            crop_rect.into().map(|r| r.into()),
822        )
823    }
824
825    /// [`blur()`]
826    pub fn blur<'a>(
827        self,
828        crop_rect: impl Into<Option<&'a IRect>>,
829        sigma: (scalar, scalar),
830        tile_mode: impl Into<Option<crate::TileMode>>,
831    ) -> Option<Self> {
832        blur(sigma, tile_mode, self, crop_rect.into().map(|r| r.into()))
833    }
834
835    /// [`color_filter()`]
836    pub fn color_filter<'a>(
837        self,
838        crop_rect: impl Into<Option<&'a IRect>>,
839        cf: impl Into<ColorFilter>,
840    ) -> Option<Self> {
841        color_filter(cf, self, crop_rect.into().map(|r| r.into()))
842    }
843
844    /// [`compose()`]
845    pub fn compose(outer: impl Into<ImageFilter>, inner: impl Into<ImageFilter>) -> Option<Self> {
846        compose(outer, inner)
847    }
848
849    /// [`crop()`]
850    pub fn crop(
851        rect: impl AsRef<Rect>,
852        tile_mode: impl Into<Option<TileMode>>,
853        input: impl Into<Option<ImageFilter>>,
854    ) -> Option<ImageFilter> {
855        crop(rect, tile_mode, input)
856    }
857
858    /// [`displacement_map()`]
859    pub fn displacement_map_effect<'a>(
860        channel_selectors: (ColorChannel, ColorChannel),
861        scale: scalar,
862        displacement: impl Into<Option<ImageFilter>>,
863        color: impl Into<Option<ImageFilter>>,
864        crop_rect: impl Into<Option<&'a IRect>>,
865    ) -> Option<Self> {
866        displacement_map(
867            channel_selectors,
868            scale,
869            displacement,
870            color,
871            crop_rect.into().map(|r| r.into()),
872        )
873    }
874
875    /// [`distant_lit_diffuse()`]
876    pub fn distant_lit_diffuse_lighting<'a>(
877        self,
878        crop_rect: impl Into<Option<&'a IRect>>,
879        direction: impl Into<Point3>,
880        light_color: impl Into<Color>,
881        surface_scale: scalar,
882        kd: scalar,
883    ) -> Option<Self> {
884        distant_lit_diffuse(
885            direction,
886            light_color,
887            surface_scale,
888            kd,
889            self,
890            crop_rect.into().map(|r| r.into()),
891        )
892    }
893
894    /// [`point_lit_diffuse()`]
895    pub fn point_lit_diffuse_lighting<'a>(
896        self,
897        crop_rect: impl Into<Option<&'a IRect>>,
898        location: impl Into<Point3>,
899        light_color: impl Into<Color>,
900        surface_scale: scalar,
901        kd: scalar,
902    ) -> Option<Self> {
903        point_lit_diffuse(
904            location,
905            light_color,
906            surface_scale,
907            kd,
908            self,
909            crop_rect.into().map(|r| r.into()),
910        )
911    }
912
913    /// [`spot_lit_diffuse()`]
914    #[allow(clippy::too_many_arguments)]
915    pub fn spot_lit_diffuse_lighting<'a>(
916        self,
917        crop_rect: impl Into<Option<&'a IRect>>,
918        location: impl Into<Point3>,
919        target: impl Into<Point3>,
920        specular_exponent: scalar,
921        cutoff_angle: scalar,
922        light_color: impl Into<Color>,
923        surface_scale: scalar,
924        kd: scalar,
925    ) -> Option<Self> {
926        spot_lit_diffuse(
927            location,
928            target,
929            specular_exponent,
930            cutoff_angle,
931            light_color,
932            surface_scale,
933            kd,
934            self,
935            crop_rect.into().map(|r| r.into()),
936        )
937    }
938
939    /// [`distant_lit_specular()`]
940    pub fn distant_lit_specular_lighting<'a>(
941        self,
942        crop_rect: impl Into<Option<&'a IRect>>,
943        direction: impl Into<Point3>,
944        light_color: impl Into<Color>,
945        surface_scale: scalar,
946        ks: scalar,
947        shininess: scalar,
948    ) -> Option<Self> {
949        distant_lit_specular(
950            direction,
951            light_color,
952            surface_scale,
953            ks,
954            shininess,
955            self,
956            crop_rect.into().map(|r| r.into()),
957        )
958    }
959
960    /// [`point_lit_specular()`]
961    pub fn point_lit_specular_lighting<'a>(
962        self,
963        crop_rect: impl Into<Option<&'a IRect>>,
964        location: impl Into<Point3>,
965        light_color: impl Into<Color>,
966        surface_scale: scalar,
967        ks: scalar,
968        shininess: scalar,
969    ) -> Option<Self> {
970        point_lit_specular(
971            location,
972            light_color,
973            surface_scale,
974            ks,
975            shininess,
976            self,
977            crop_rect.into().map(|r| r.into()),
978        )
979    }
980
981    /// [`spot_lit_specular()`]
982    #[allow(clippy::too_many_arguments)]
983    pub fn spot_lit_specular_lighting<'a>(
984        self,
985        crop_rect: impl Into<Option<&'a IRect>>,
986        location: impl Into<Point3>,
987        target: impl Into<Point3>,
988        specular_exponent: scalar,
989        cutoff_angle: scalar,
990        light_color: impl Into<Color>,
991        surface_scale: scalar,
992        ks: scalar,
993        shininess: scalar,
994    ) -> Option<Self> {
995        spot_lit_specular(
996            location,
997            target,
998            specular_exponent,
999            cutoff_angle,
1000            light_color,
1001            surface_scale,
1002            ks,
1003            shininess,
1004            self,
1005            crop_rect.into().map(|r| r.into()),
1006        )
1007    }
1008
1009    /// [`magnifier()`]
1010    pub fn magnifier<'a>(
1011        self,
1012        lens_bounds: impl AsRef<Rect>,
1013        zoom_amount: scalar,
1014        inset: scalar,
1015        sampling_options: SamplingOptions,
1016        crop_rect: impl Into<Option<&'a IRect>>,
1017    ) -> Option<Self> {
1018        magnifier(
1019            lens_bounds,
1020            zoom_amount,
1021            inset,
1022            sampling_options,
1023            self,
1024            crop_rect.into().map(|r| r.into()),
1025        )
1026    }
1027
1028    /// [`matrix_convolution()`]
1029    #[allow(clippy::too_many_arguments)]
1030    pub fn matrix_convolution<'a>(
1031        self,
1032        crop_rect: impl Into<Option<&'a IRect>>,
1033        kernel_size: impl Into<ISize>,
1034        kernel: &[scalar],
1035        gain: scalar,
1036        bias: scalar,
1037        kernel_offset: impl Into<IPoint>,
1038        tile_mode: crate::TileMode,
1039        convolve_alpha: bool,
1040    ) -> Option<Self> {
1041        matrix_convolution(
1042            kernel_size,
1043            kernel,
1044            gain,
1045            bias,
1046            kernel_offset,
1047            tile_mode,
1048            convolve_alpha,
1049            self,
1050            crop_rect.into().map(|r| r.into()),
1051        )
1052    }
1053
1054    /// [`merge()`]
1055    pub fn merge<'a>(
1056        filters: impl IntoIterator<Item = Option<Self>>,
1057        crop_rect: impl Into<Option<&'a IRect>>,
1058    ) -> Option<Self> {
1059        merge(filters, crop_rect.into().map(|r| r.into()))
1060    }
1061
1062    /// [`dilate()`]
1063    pub fn dilate<'a>(
1064        self,
1065        crop_rect: impl Into<Option<&'a IRect>>,
1066        radii: (scalar, scalar),
1067    ) -> Option<Self> {
1068        dilate(radii, self, crop_rect.into().map(|r| r.into()))
1069    }
1070
1071    /// [`erode()`]
1072    pub fn erode<'a>(
1073        self,
1074        crop_rect: impl Into<Option<&'a IRect>>,
1075        radii: (scalar, scalar),
1076    ) -> Option<Self> {
1077        erode(radii, self, crop_rect.into().map(|r| r.into()))
1078    }
1079
1080    /// [`offset()`]
1081    pub fn offset<'a>(
1082        self,
1083        crop_rect: impl Into<Option<&'a IRect>>,
1084        delta: impl Into<Vector>,
1085    ) -> Option<Self> {
1086        offset(delta, self, crop_rect.into().map(|r| r.into()))
1087    }
1088
1089    /// [`self::picture()`]
1090    pub fn from_picture<'a>(
1091        picture: impl Into<Picture>,
1092        crop_rect: impl Into<Option<&'a Rect>>,
1093    ) -> Option<Self> {
1094        self::picture(picture, crop_rect)
1095    }
1096
1097    /// [`tile()`]
1098    pub fn tile(self, src: impl AsRef<Rect>, dst: impl AsRef<Rect>) -> Option<Self> {
1099        tile(src, dst, self)
1100    }
1101}
1102
1103#[derive(Copy, Clone, PartialEq, Debug)]
1104pub struct ArithmeticFPInputs {
1105    pub k: [f32; 4],
1106    pub enforce_pm_color: bool,
1107}
1108
1109impl From<([f32; 4], bool)> for ArithmeticFPInputs {
1110    fn from((k, enforce_pm_color): ([f32; 4], bool)) -> Self {
1111        ArithmeticFPInputs {
1112            k,
1113            enforce_pm_color,
1114        }
1115    }
1116}
1117
1118impl ArithmeticFPInputs {
1119    pub fn new(k0: f32, k1: f32, k2: f32, k3: f32, enforce_pm_color: bool) -> Self {
1120        Self {
1121            k: [k0, k1, k2, k3],
1122            enforce_pm_color,
1123        }
1124    }
1125}
1126
1127impl Picture {
1128    pub fn as_image_filter<'a>(
1129        &self,
1130        crop_rect: impl Into<Option<&'a Rect>>,
1131    ) -> Option<ImageFilter> {
1132        self.clone().into_image_filter(crop_rect)
1133    }
1134
1135    pub fn into_image_filter<'a>(
1136        self,
1137        crop_rect: impl Into<Option<&'a Rect>>,
1138    ) -> Option<ImageFilter> {
1139        picture(self, crop_rect)
1140    }
1141}
1142
1143#[cfg(test)]
1144mod tests {
1145    use super::CropRect;
1146    use crate::{IRect, Rect};
1147
1148    fn cr(crop_rect: impl Into<CropRect>) -> CropRect {
1149        crop_rect.into()
1150    }
1151
1152    #[test]
1153    fn test_crop_conversion_options() {
1154        assert_eq!(cr(None), CropRect::NO_CROP_RECT);
1155        assert_eq!(cr(CropRect::NO_CROP_RECT), CropRect::NO_CROP_RECT);
1156        #[allow(clippy::needless_borrow)]
1157        let cr_ref = cr(CropRect::NO_CROP_RECT);
1158        assert_eq!(cr_ref, CropRect::NO_CROP_RECT);
1159        let irect = IRect {
1160            left: 1,
1161            top: 2,
1162            right: 3,
1163            bottom: 4,
1164        };
1165        assert_eq!(cr(irect), CropRect(Some(Rect::from(irect))));
1166        #[allow(clippy::needless_borrow)]
1167        let cr_by_ref = cr(irect);
1168        assert_eq!(cr_by_ref, CropRect(Some(Rect::from(irect))));
1169        let rect = Rect {
1170            left: 1.0,
1171            top: 2.0,
1172            right: 3.0,
1173            bottom: 4.0,
1174        };
1175        assert_eq!(cr(rect), CropRect(Some(rect)));
1176        #[allow(clippy::needless_borrow)]
1177        let cr_by_ref = cr(rect);
1178        assert_eq!(cr_by_ref, CropRect(Some(rect)));
1179    }
1180
1181    #[test]
1182    fn test_drop_shadow_only() {
1183        let rect = Rect {
1184            left: 1.0,
1185            top: 2.0,
1186            right: 3.0,
1187            bottom: 4.0,
1188        };
1189
1190        let _ = super::drop_shadow_only((10., 10.), (1.0, 1.0), 0x404040, None, None, rect);
1191    }
1192}