Skip to content

Commit b69405f

Browse files
Add convolution API
1 parent 5c28129 commit b69405f

12 files changed

+316
-67
lines changed

src/ImageSharp/Processing/Extensions/Convolution/BoxBlurExtensions.cs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,11 @@ public static IImageProcessingContext BoxBlur(this IImageProcessingContext sourc
5555
/// The <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in Y direction.
5656
/// </param>
5757
/// <returns>The <see cref="IImageProcessingContext"/>.</returns>
58-
public static IImageProcessingContext BoxBlur(this IImageProcessingContext source, int radius, Rectangle rectangle, BorderWrappingMode borderWrapModeX, BorderWrappingMode borderWrapModeY)
59-
{
60-
var processor = new BoxBlurProcessor(radius, borderWrapModeX, borderWrapModeY);
61-
return source.ApplyProcessor(processor, rectangle);
62-
}
58+
public static IImageProcessingContext BoxBlur(
59+
this IImageProcessingContext source,
60+
int radius,
61+
Rectangle rectangle,
62+
BorderWrappingMode borderWrapModeX,
63+
BorderWrappingMode borderWrapModeY)
64+
=> source.ApplyProcessor(new BoxBlurProcessor(radius, borderWrapModeX, borderWrapModeY), rectangle);
6365
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Six Labors Split License.
3+
4+
using SixLabors.ImageSharp.Processing.Processors.Convolution;
5+
6+
namespace SixLabors.ImageSharp.Processing.Extensions.Convolution;
7+
8+
/// <summary>
9+
/// Defines general convolution extensions to apply on an <see cref="Image"/>
10+
/// using Mutate/Clone.
11+
/// </summary>
12+
public static class ConvolutionExtensions
13+
{
14+
/// <summary>
15+
/// Applies a convolution filter to the image.
16+
/// </summary>
17+
/// <param name="source">The current image processing context.</param>
18+
/// <param name="kernelXY">The convolution kernel to apply.</param>
19+
/// <returns>The <see cref="IImageProcessingContext"/>.</returns>
20+
public static IImageProcessingContext Convolve(this IImageProcessingContext source, DenseMatrix<float> kernelXY)
21+
=> Convolve(source, kernelXY, false);
22+
23+
/// <summary>
24+
/// Applies a convolution filter to the image.
25+
/// </summary>
26+
/// <param name="source">The current image processing context.</param>
27+
/// <param name="kernelXY">The convolution kernel to apply.</param>
28+
/// <param name="preserveAlpha">Whether the convolution filter is applied to alpha as well as the color channels.</param>
29+
/// <returns>The <see cref="IImageProcessingContext"/>.</returns>
30+
public static IImageProcessingContext Convolve(this IImageProcessingContext source, DenseMatrix<float> kernelXY, bool preserveAlpha)
31+
=> Convolve(source, kernelXY, preserveAlpha, BorderWrappingMode.Repeat, BorderWrappingMode.Repeat);
32+
33+
/// <summary>
34+
/// Applies a convolution filter to the image.
35+
/// </summary>
36+
/// <param name="source">The current image processing context.</param>
37+
/// <param name="kernelXY">The convolution kernel to apply.</param>
38+
/// <param name="preserveAlpha">Whether the convolution filter is applied to alpha as well as the color channels.</param>
39+
/// <param name="borderWrapModeX">The <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in X direction.</param>
40+
/// <param name="borderWrapModeY">The <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in Y direction.</param>
41+
/// <returns>The <see cref="IImageProcessingContext"/>.</returns>
42+
public static IImageProcessingContext Convolve(
43+
this IImageProcessingContext source,
44+
DenseMatrix<float> kernelXY,
45+
bool preserveAlpha,
46+
BorderWrappingMode borderWrapModeX,
47+
BorderWrappingMode borderWrapModeY)
48+
=> source.ApplyProcessor(new ConvolutionProcessor(kernelXY, preserveAlpha, borderWrapModeX, borderWrapModeY));
49+
50+
/// <summary>
51+
/// Applies a convolution filter to the image.
52+
/// </summary>
53+
/// <param name="source">The current image processing context.</param>
54+
/// <param name="rectangle">The rectangle structure that specifies the portion of the image object to alter.</param>
55+
/// <param name="kernelXY">The convolution kernel to apply.</param>
56+
/// <returns>The <see cref="IImageProcessingContext"/>.</returns>
57+
public static IImageProcessingContext Convolve(this IImageProcessingContext source, Rectangle rectangle, DenseMatrix<float> kernelXY)
58+
=> Convolve(source, rectangle, kernelXY, false);
59+
60+
/// <summary>
61+
/// Applies a convolution filter to the image.
62+
/// </summary>
63+
/// <param name="source">The current image processing context.</param>
64+
/// <param name="rectangle">The rectangle structure that specifies the portion of the image object to alter.</param>
65+
/// <param name="kernelXY">The convolution kernel to apply.</param>
66+
/// <param name="preserveAlpha">Whether the convolution filter is applied to alpha as well as the color channels.</param>
67+
/// <returns>The <see cref="IImageProcessingContext"/>.</returns>
68+
public static IImageProcessingContext Convolve(this IImageProcessingContext source, Rectangle rectangle, DenseMatrix<float> kernelXY, bool preserveAlpha)
69+
=> Convolve(source, rectangle, kernelXY, preserveAlpha, BorderWrappingMode.Repeat, BorderWrappingMode.Repeat);
70+
71+
/// <summary>
72+
/// Applies a convolution filter to the image.
73+
/// </summary>
74+
/// <param name="source">The current image processing context.</param>
75+
/// <param name="rectangle">The rectangle structure that specifies the portion of the image object to alter.</param>
76+
/// <param name="kernelXY">The convolution kernel to apply.</param>
77+
/// <param name="preserveAlpha">Whether the convolution filter is applied to alpha as well as the color channels.</param>
78+
/// <param name="borderWrapModeX">The <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in X direction.</param>
79+
/// <param name="borderWrapModeY">The <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in Y direction.</param>
80+
/// <returns>The <see cref="IImageProcessingContext"/>.</returns>
81+
public static IImageProcessingContext Convolve(
82+
this IImageProcessingContext source,
83+
Rectangle rectangle,
84+
DenseMatrix<float> kernelXY,
85+
bool preserveAlpha,
86+
BorderWrappingMode borderWrapModeX,
87+
BorderWrappingMode borderWrapModeY)
88+
=> source.ApplyProcessor(new ConvolutionProcessor(kernelXY, preserveAlpha, borderWrapModeX, borderWrapModeY), rectangle);
89+
}

src/ImageSharp/Processing/Extensions/Convolution/GaussianBlurExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ public static IImageProcessingContext GaussianBlur(this IImageProcessingContext
5757
/// <returns>The <see cref="IImageProcessingContext"/>.</returns>
5858
public static IImageProcessingContext GaussianBlur(this IImageProcessingContext source, float sigma, Rectangle rectangle, BorderWrappingMode borderWrapModeX, BorderWrappingMode borderWrapModeY)
5959
{
60-
var processor = new GaussianBlurProcessor(sigma, borderWrapModeX, borderWrapModeY);
60+
GaussianBlurProcessor processor = new(sigma, borderWrapModeX, borderWrapModeY);
6161
return source.ApplyProcessor(processor, rectangle);
6262
}
6363
}

src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,14 +62,14 @@ protected override void OnFrameApply(ImageFrame<TPixel> source)
6262

6363
source.CopyTo(targetPixels);
6464

65-
var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds());
65+
Rectangle interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds());
6666

67-
using (var map = new KernelSamplingMap(allocator))
67+
using (KernelSamplingMap map = new(allocator))
6868
{
6969
// Since the kernel sizes are identical we can use a single map.
7070
map.BuildSamplingOffsetMap(this.KernelY, interest);
7171

72-
var operation = new Convolution2DRowOperation<TPixel>(
72+
Convolution2DRowOperation<TPixel> operation = new(
7373
interest,
7474
targetPixels,
7575
source.PixelBuffer,

src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs

Lines changed: 42 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -35,18 +35,48 @@ public Convolution2PassProcessor(
3535
Rectangle sourceRectangle,
3636
BorderWrappingMode borderWrapModeX,
3737
BorderWrappingMode borderWrapModeY)
38+
: this(configuration, kernel, kernel, preserveAlpha, source, sourceRectangle, borderWrapModeX, borderWrapModeY)
39+
{
40+
}
41+
42+
/// <summary>
43+
/// Initializes a new instance of the <see cref="Convolution2PassProcessor{TPixel}"/> class.
44+
/// </summary>
45+
/// <param name="configuration">The configuration which allows altering default behaviour or extending the library.</param>
46+
/// <param name="kernelX">The 1D convolution kernel. X Direction</param>
47+
/// <param name="kernelY">The 1D convolution kernel. Y Direction</param>
48+
/// <param name="preserveAlpha">Whether the convolution filter is applied to alpha as well as the color channels.</param>
49+
/// <param name="source">The source <see cref="Image{TPixel}"/> for the current processor instance.</param>
50+
/// <param name="sourceRectangle">The source area to process for the current processor instance.</param>
51+
/// <param name="borderWrapModeX">The <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in X direction.</param>
52+
/// <param name="borderWrapModeY">The <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in Y direction.</param>
53+
public Convolution2PassProcessor(
54+
Configuration configuration,
55+
float[] kernelX,
56+
float[] kernelY,
57+
bool preserveAlpha,
58+
Image<TPixel> source,
59+
Rectangle sourceRectangle,
60+
BorderWrappingMode borderWrapModeX,
61+
BorderWrappingMode borderWrapModeY)
3862
: base(configuration, source, sourceRectangle)
3963
{
40-
this.Kernel = kernel;
64+
this.KernelX = kernelX;
65+
this.KernelY = kernelY;
4166
this.PreserveAlpha = preserveAlpha;
4267
this.BorderWrapModeX = borderWrapModeX;
4368
this.BorderWrapModeY = borderWrapModeY;
4469
}
4570

4671
/// <summary>
47-
/// Gets the convolution kernel.
72+
/// Gets the convolution kernel. X direction.
73+
/// </summary>
74+
public float[] KernelX { get; }
75+
76+
/// <summary>
77+
/// Gets the convolution kernel. Y direction.
4878
/// </summary>
49-
public float[] Kernel { get; }
79+
public float[] KernelY { get; }
5080

5181
/// <summary>
5282
/// Gets a value indicating whether the convolution filter is applied to alpha as well as the color channels.
@@ -68,21 +98,21 @@ protected override void OnFrameApply(ImageFrame<TPixel> source)
6898
{
6999
using Buffer2D<TPixel> firstPassPixels = this.Configuration.MemoryAllocator.Allocate2D<TPixel>(source.Size);
70100

71-
var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds());
101+
Rectangle interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds());
72102

73103
// We can create a single sampling map with the size as if we were using the non separated 2D kernel
74104
// the two 1D kernels represent, and reuse it across both convolution steps, like in the bokeh blur.
75-
using var mapXY = new KernelSamplingMap(this.Configuration.MemoryAllocator);
105+
using KernelSamplingMap mapXY = new(this.Configuration.MemoryAllocator);
76106

77-
mapXY.BuildSamplingOffsetMap(this.Kernel.Length, this.Kernel.Length, interest, this.BorderWrapModeX, this.BorderWrapModeY);
107+
mapXY.BuildSamplingOffsetMap(this.KernelX.Length, this.KernelX.Length, interest, this.BorderWrapModeX, this.BorderWrapModeY);
78108

79109
// Horizontal convolution
80-
var horizontalOperation = new HorizontalConvolutionRowOperation(
110+
HorizontalConvolutionRowOperation horizontalOperation = new(
81111
interest,
82112
firstPassPixels,
83113
source.PixelBuffer,
84114
mapXY,
85-
this.Kernel,
115+
this.KernelX,
86116
this.Configuration,
87117
this.PreserveAlpha);
88118

@@ -92,12 +122,12 @@ protected override void OnFrameApply(ImageFrame<TPixel> source)
92122
in horizontalOperation);
93123

94124
// Vertical convolution
95-
var verticalOperation = new VerticalConvolutionRowOperation(
125+
VerticalConvolutionRowOperation verticalOperation = new(
96126
interest,
97127
source.PixelBuffer,
98128
firstPassPixels,
99129
mapXY,
100-
this.Kernel,
130+
this.KernelY,
101131
this.Configuration,
102132
this.PreserveAlpha);
103133

@@ -140,7 +170,7 @@ public HorizontalConvolutionRowOperation(
140170
}
141171

142172
/// <inheritdoc/>
143-
[MethodImpl(InliningOptions.ShortMethod)]
173+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
144174
public int GetRequiredBufferLength(Rectangle bounds)
145175
=> 2 * bounds.Width;
146176

@@ -306,7 +336,7 @@ public VerticalConvolutionRowOperation(
306336
}
307337

308338
/// <inheritdoc/>
309-
[MethodImpl(InliningOptions.ShortMethod)]
339+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
310340
public int GetRequiredBufferLength(Rectangle bounds)
311341
=> 2 * bounds.Width;
312342

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Six Labors Split License.
3+
4+
using SixLabors.ImageSharp.PixelFormats;
5+
6+
namespace SixLabors.ImageSharp.Processing.Processors.Convolution;
7+
8+
/// <summary>
9+
/// Defines a processor that uses a 2 dimensional matrix to perform convolution against an image.
10+
/// </summary>
11+
public class ConvolutionProcessor : IImageProcessor
12+
{
13+
/// <summary>
14+
/// Initializes a new instance of the <see cref="ConvolutionProcessor"/> class.
15+
/// </summary>
16+
/// <param name="kernelXY">The 2d gradient operator.</param>
17+
/// <param name="preserveAlpha">Whether the convolution filter is applied to alpha as well as the color channels.</param>
18+
/// <param name="borderWrapModeX">The <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in X direction.</param>
19+
/// <param name="borderWrapModeY">The <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in Y direction.</param>
20+
public ConvolutionProcessor(
21+
in DenseMatrix<float> kernelXY,
22+
bool preserveAlpha,
23+
BorderWrappingMode borderWrapModeX,
24+
BorderWrappingMode borderWrapModeY)
25+
{
26+
this.KernelXY = kernelXY;
27+
this.PreserveAlpha = preserveAlpha;
28+
this.BorderWrapModeX = borderWrapModeX;
29+
this.BorderWrapModeY = borderWrapModeY;
30+
}
31+
32+
/// <summary>
33+
/// Gets the 2d convolution kernel.
34+
/// </summary>
35+
public DenseMatrix<float> KernelXY { get; }
36+
37+
/// <summary>
38+
/// Gets a value indicating whether the convolution filter is applied to alpha as well as the color channels.
39+
/// </summary>
40+
public bool PreserveAlpha { get; }
41+
42+
/// <summary>
43+
/// Gets the <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in X direction.
44+
/// </summary>
45+
public BorderWrappingMode BorderWrapModeX { get; }
46+
47+
/// <summary>
48+
/// Gets the <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in Y direction.
49+
/// </summary>
50+
public BorderWrappingMode BorderWrapModeY { get; }
51+
52+
/// <inheritdoc/>
53+
public IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Configuration configuration, Image<TPixel> source, Rectangle sourceRectangle)
54+
where TPixel : unmanaged,
55+
IPixel<TPixel>
56+
{
57+
if (this.KernelXY.TryGetLinearlySeparableComponents(out float[]? kernelX, out float[]? kernelY))
58+
{
59+
return new Convolution2PassProcessor<TPixel>(
60+
configuration,
61+
kernelX,
62+
kernelY,
63+
this.PreserveAlpha,
64+
source,
65+
sourceRectangle,
66+
this.BorderWrapModeX,
67+
this.BorderWrapModeY);
68+
}
69+
70+
return new ConvolutionProcessor<TPixel>(
71+
configuration,
72+
this.KernelXY,
73+
this.PreserveAlpha,
74+
source,
75+
sourceRectangle,
76+
this.BorderWrapModeX,
77+
this.BorderWrapModeY);
78+
}
79+
}

0 commit comments

Comments
 (0)