diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs index 3a5f35cd14..8f1d373556 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs @@ -60,17 +60,32 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// protected override void OnFrameApply(ImageFrame source) { - using Buffer2D targetPixels = this.Configuration.MemoryAllocator.Allocate2D(source.Width, source.Height); + MemoryAllocator allocator = this.Configuration.MemoryAllocator; + using Buffer2D targetPixels = allocator.Allocate2D(source.Width, source.Height); source.CopyTo(targetPixels); var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); - var operation = new RowOperation(interest, targetPixels, source.PixelBuffer, this.KernelY, this.KernelX, this.Configuration, this.PreserveAlpha); + using (var map = new KernelSamplingMap(allocator)) + { + // Since the kernel sizes are identical we can use a single map. + map.BuildSamplingOffsetMap(this.KernelY, interest); + + var operation = new RowOperation( + interest, + targetPixels, + source.PixelBuffer, + map, + this.KernelY, + this.KernelX, + this.Configuration, + this.PreserveAlpha); - ParallelRowIterator.IterateRows( - this.Configuration, - interest, - in operation); + ParallelRowIterator.IterateRows( + this.Configuration, + interest, + in operation); + } Buffer2D.SwapOrCopyContent(source.PixelBuffer, targetPixels); } @@ -81,10 +96,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution private readonly struct RowOperation : IRowOperation { private readonly Rectangle bounds; - private readonly int maxY; - private readonly int maxX; private readonly Buffer2D targetPixels; private readonly Buffer2D sourcePixels; + private readonly KernelSamplingMap map; private readonly DenseMatrix kernelY; private readonly DenseMatrix kernelX; private readonly Configuration configuration; @@ -95,16 +109,16 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution Rectangle bounds, Buffer2D targetPixels, Buffer2D sourcePixels, + KernelSamplingMap map, DenseMatrix kernelY, DenseMatrix kernelX, Configuration configuration, bool preserveAlpha) { this.bounds = bounds; - this.maxY = this.bounds.Bottom - 1; - this.maxX = this.bounds.Right - 1; this.targetPixels = targetPixels; this.sourcePixels = sourcePixels; + this.map = map; this.kernelY = kernelY; this.kernelX = kernelX; this.configuration = configuration; @@ -115,42 +129,41 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution [MethodImpl(InliningOptions.ShortMethod)] public void Invoke(int y, Span span) { - ref Vector4 spanRef = ref MemoryMarshal.GetReference(span); + ref Vector4 targetRowRef = ref MemoryMarshal.GetReference(span); Span targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X); PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, span.Length), span); + Span yOffsets = this.map.GetYOffsetSpan(); + Span xOffsets = this.map.GetXOffsetSpan(); + int row = y - this.bounds.Y; if (this.preserveAlpha) { - for (int x = 0; x < this.bounds.Width; x++) + for (int column = 0; column < this.bounds.Width; column++) { - DenseMatrixUtils.Convolve2D3( + Convolver.Convolve2D3( in this.kernelY, in this.kernelX, + yOffsets, + xOffsets, this.sourcePixels, - ref spanRef, - y, - x, - this.bounds.Y, - this.maxY, - this.bounds.X, - this.maxX); + ref targetRowRef, + row, + column); } } else { - for (int x = 0; x < this.bounds.Width; x++) + for (int column = 0; column < this.bounds.Width; column++) { - DenseMatrixUtils.Convolve2D4( + Convolver.Convolve2D4( in this.kernelY, in this.kernelX, + yOffsets, + xOffsets, this.sourcePixels, - ref spanRef, - y, - x, - this.bounds.Y, - this.maxY, - this.bounds.X, - this.maxX); + ref targetRowRef, + row, + column); } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs index 63fbca98a9..2ea062e281 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs @@ -63,9 +63,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); - using (var mapX = new KernelOffsetMap(this.Configuration.MemoryAllocator)) + using (var mapX = new KernelSamplingMap(this.Configuration.MemoryAllocator)) { - mapX.BuildOffsetMap(this.KernelX, interest); + mapX.BuildSamplingOffsetMap(this.KernelX, interest); // Horizontal convolution var horizontalOperation = new RowOperation( @@ -83,9 +83,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution in horizontalOperation); } - using (var mapY = new KernelOffsetMap(this.Configuration.MemoryAllocator)) + using (var mapY = new KernelSamplingMap(this.Configuration.MemoryAllocator)) { - mapY.BuildOffsetMap(this.KernelY, interest); + mapY.BuildSamplingOffsetMap(this.KernelY, interest); // Vertical convolution var verticalOperation = new RowOperation( @@ -112,7 +112,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution private readonly Rectangle bounds; private readonly Buffer2D targetPixels; private readonly Buffer2D sourcePixels; - private readonly KernelOffsetMap map; + private readonly KernelSamplingMap map; private readonly DenseMatrix kernel; private readonly Configuration configuration; private readonly bool preserveAlpha; @@ -122,7 +122,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution Rectangle bounds, Buffer2D targetPixels, Buffer2D sourcePixels, - KernelOffsetMap map, + KernelSamplingMap map, DenseMatrix kernel, Configuration configuration, bool preserveAlpha) @@ -140,7 +140,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution [MethodImpl(InliningOptions.ShortMethod)] public void Invoke(int y, Span span) { - ref Vector4 targetRef = ref MemoryMarshal.GetReference(span); + ref Vector4 targetRowRef = ref MemoryMarshal.GetReference(span); Span targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X); PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, span.Length), span); Span yOffsets = this.map.GetYOffsetSpan(); @@ -151,12 +151,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution { for (int column = 0; column < this.bounds.Width; column++) { - DenseMatrixUtils.Convolve3( + Convolver.Convolve3( in this.kernel, yOffsets, xOffsets, this.sourcePixels, - ref targetRef, + ref targetRowRef, row, column); } @@ -165,12 +165,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution { for (int column = 0; column < this.bounds.Width; column++) { - DenseMatrixUtils.Convolve4( + Convolver.Convolve4( in this.kernel, yOffsets, xOffsets, this.sourcePixels, - ref targetRef, + ref targetRowRef, row, column); } diff --git a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs index ae2e8893f7..999fba22be 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs @@ -57,9 +57,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution source.CopyTo(targetPixels); var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); - using (var map = new KernelOffsetMap(allocator)) + using (var map = new KernelSamplingMap(allocator)) { - map.BuildOffsetMap(this.KernelXY, interest); + map.BuildSamplingOffsetMap(this.KernelXY, interest); var operation = new RowOperation(interest, targetPixels, source.PixelBuffer, map, this.KernelXY, this.Configuration, this.PreserveAlpha); ParallelRowIterator.IterateRows( @@ -79,7 +79,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution private readonly Rectangle bounds; private readonly Buffer2D targetPixels; private readonly Buffer2D sourcePixels; - private readonly KernelOffsetMap map; + private readonly KernelSamplingMap map; private readonly DenseMatrix kernel; private readonly Configuration configuration; private readonly bool preserveAlpha; @@ -89,7 +89,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution Rectangle bounds, Buffer2D targetPixels, Buffer2D sourcePixels, - KernelOffsetMap map, + KernelSamplingMap map, DenseMatrix kernel, Configuration configuration, bool preserveAlpha) @@ -107,7 +107,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution [MethodImpl(InliningOptions.ShortMethod)] public void Invoke(int y, Span span) { - ref Vector4 targetRef = ref MemoryMarshal.GetReference(span); + ref Vector4 targetRowRef = ref MemoryMarshal.GetReference(span); Span targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X); PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, span.Length), span); Span yOffsets = this.map.GetYOffsetSpan(); @@ -118,12 +118,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution { for (int column = 0; column < this.bounds.Width; column++) { - DenseMatrixUtils.Convolve3( + Convolver.Convolve3( in this.kernel, yOffsets, xOffsets, this.sourcePixels, - ref targetRef, + ref targetRowRef, row, column); } @@ -132,12 +132,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution { for (int column = 0; column < this.bounds.Width; column++) { - DenseMatrixUtils.Convolve4( + Convolver.Convolve4( in this.kernel, yOffsets, xOffsets, this.sourcePixels, - ref targetRef, + ref targetRowRef, row, column); } diff --git a/src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolver.cs similarity index 57% rename from src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs rename to src/ImageSharp/Processing/Processors/Convolution/Convolver.cs index cf7eb1162a..c9e9d74148 100644 --- a/src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolver.cs @@ -6,56 +6,50 @@ using System.Numerics; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing.Processors.Convolution; namespace SixLabors.ImageSharp { /// - /// Extension methods for . - /// TODO: One day rewrite all this to use SIMD intrinsics. There's a lot of scope for improvement. + /// Provides methods to perform convolution operations. /// - internal static class DenseMatrixUtils + internal static class Convolver { /// /// Computes the sum of vectors in the span referenced by weighted by the two kernel weight values. /// Using this method the convolution filter is not applied to alpha in addition to the color channels. /// /// The pixel format. - /// The vertical dense matrix. - /// The horizontal dense matrix. + /// The vertical convolution kernel. + /// The horizontal convolution kernel. + /// The span containing precalculated kernel y-sampling offsets. + /// The span containing precalculated kernel x-sampling offsets. /// The source frame. /// The target row base reference. /// The current row. /// The current column. - /// The minimum working area row. - /// The maximum working area row. - /// The minimum working area column. - /// The maximum working area column. [MethodImpl(InliningOptions.ShortMethod)] public static void Convolve2D3( - in DenseMatrix matrixY, - in DenseMatrix matrixX, + in DenseMatrix kernelY, + in DenseMatrix kernelX, + Span rowSampleOffsets, + Span columnSampleOffsets, Buffer2D sourcePixels, ref Vector4 targetRowRef, int row, - int column, - int minRow, - int maxRow, - int minColumn, - int maxColumn) + int column) where TPixel : unmanaged, IPixel { + Vector4 vector = default; + Convolve2DImpl( - in matrixY, - in matrixX, + in kernelY, + in kernelX, + rowSampleOffsets, + columnSampleOffsets, sourcePixels, row, column, - minRow, - maxRow, - minColumn, - maxColumn, - out Vector4 vector); + ref vector); ref Vector4 target = ref Unsafe.Add(ref targetRowRef, column); vector.W = target.W; @@ -69,41 +63,37 @@ namespace SixLabors.ImageSharp /// Using this method the convolution filter is applied to alpha in addition to the color channels. /// /// The pixel format. - /// The vertical dense matrix. - /// The horizontal dense matrix. + /// The vertical convolution kernel. + /// The horizontal convolution kernel. + /// The span containing precalculated kernel y-sampling offsets. + /// The span containing precalculated kernel x-sampling offsets. /// The source frame. /// The target row base reference. /// The current row. /// The current column. - /// The minimum working area row. - /// The maximum working area row. - /// The minimum working area column. - /// The maximum working area column. [MethodImpl(InliningOptions.ShortMethod)] public static void Convolve2D4( - in DenseMatrix matrixY, - in DenseMatrix matrixX, + in DenseMatrix kernelY, + in DenseMatrix kernelX, + Span rowSampleOffsets, + Span columnSampleOffsets, Buffer2D sourcePixels, ref Vector4 targetRowRef, int row, - int column, - int minRow, - int maxRow, - int minColumn, - int maxColumn) + int column) where TPixel : unmanaged, IPixel { + Vector4 vector = default; + Convolve2DImpl( - in matrixY, - in matrixX, + in kernelY, + in kernelX, + rowSampleOffsets, + columnSampleOffsets, sourcePixels, row, column, - minRow, - maxRow, - minColumn, - maxColumn, - out Vector4 vector); + ref vector); ref Vector4 target = ref Unsafe.Add(ref targetRowRef, column); Numerics.UnPremultiply(ref vector); @@ -112,43 +102,38 @@ namespace SixLabors.ImageSharp [MethodImpl(InliningOptions.ShortMethod)] public static void Convolve2DImpl( - in DenseMatrix matrixY, - in DenseMatrix matrixX, + in DenseMatrix kernelY, + in DenseMatrix kernelX, + Span rowSampleOffsets, + Span columnSampleOffsets, Buffer2D sourcePixels, int row, int column, - int minRow, - int maxRow, - int minColumn, - int maxColumn, - out Vector4 vector) + ref Vector4 targetVector) where TPixel : unmanaged, IPixel { Vector4 vectorY = default; Vector4 vectorX = default; - int matrixHeight = matrixY.Rows; - int matrixWidth = matrixY.Columns; - int radiusY = matrixHeight >> 1; - int radiusX = matrixWidth >> 1; - int sourceOffsetColumnBase = column + minColumn; + int kernelHeight = kernelY.Rows; + int kernelWidth = kernelY.Columns; - for (int y = 0; y < matrixHeight; y++) + for (int y = 0; y < kernelHeight; y++) { - int offsetY = Numerics.Clamp(row + y - radiusY, minRow, maxRow); + int offsetY = rowSampleOffsets[(row * kernelHeight) + y]; Span sourceRowSpan = sourcePixels.GetRowSpan(offsetY); - for (int x = 0; x < matrixWidth; x++) + for (int x = 0; x < kernelWidth; x++) { - int offsetX = Numerics.Clamp(sourceOffsetColumnBase + x - radiusX, minColumn, maxColumn); - var currentColor = sourceRowSpan[offsetX].ToVector4(); - Numerics.Premultiply(ref currentColor); + int offsetX = columnSampleOffsets[(column * kernelWidth) + x]; + var sample = sourceRowSpan[offsetX].ToVector4(); + Numerics.Premultiply(ref sample); - vectorX += matrixX[y, x] * currentColor; - vectorY += matrixY[y, x] * currentColor; + vectorX += kernelX[y, x] * sample; + vectorY += kernelY[y, x] * sample; } } - vector = Vector4.SquareRoot((vectorX * vectorX) + (vectorY * vectorY)); + targetVector = Vector4.SquareRoot((vectorX * vectorX) + (vectorY * vectorY)); } /// @@ -156,18 +141,18 @@ namespace SixLabors.ImageSharp /// Using this method the convolution filter is not applied to alpha in addition to the color channels. /// /// The pixel format. - /// The dense matrix. - /// The span containing precalculated kernel y-offsets. - /// The span containing precalculated kernel x-offsets. + /// The convolution kernel. + /// The span containing precalculated kernel y-sampling offsets. + /// The span containing precalculated kernel x-sampling offsets. /// The source frame. /// The target row base reference. /// The current row. /// The current column. [MethodImpl(InliningOptions.ShortMethod)] public static void Convolve3( - in DenseMatrix matrix, - Span yOffsetSpan, - Span xOffsetSpan, + in DenseMatrix kernel, + Span rowSampleOffsets, + Span columnSampleOffsets, Buffer2D sourcePixels, ref Vector4 targetRowRef, int row, @@ -177,9 +162,9 @@ namespace SixLabors.ImageSharp Vector4 vector = default; ConvolveImpl( - in matrix, - yOffsetSpan, - xOffsetSpan, + in kernel, + rowSampleOffsets, + columnSampleOffsets, sourcePixels, row, column, @@ -197,18 +182,18 @@ namespace SixLabors.ImageSharp /// Using this method the convolution filter is applied to alpha in addition to the color channels. /// /// The pixel format. - /// The dense matrix. - /// The span containing precalculated kernel y-offsets. - /// The span containing precalculated kernel x-offsets. + /// The convolution kernel. + /// The span containing precalculated kernel y-offsets. + /// The span containing precalculated kernel x-offsets. /// The source frame. /// The target row base reference. /// The current row. /// The current column. [MethodImpl(InliningOptions.ShortMethod)] public static void Convolve4( - in DenseMatrix matrix, - Span yOffsetSpan, - Span xOffsetSpan, + in DenseMatrix kernel, + Span rowSampleOffsets, + Span columnSampleOffsets, Buffer2D sourcePixels, ref Vector4 targetRowRef, int row, @@ -218,9 +203,9 @@ namespace SixLabors.ImageSharp Vector4 vector = default; ConvolveImpl( - in matrix, - yOffsetSpan, - xOffsetSpan, + in kernel, + rowSampleOffsets, + columnSampleOffsets, sourcePixels, row, column, @@ -233,29 +218,29 @@ namespace SixLabors.ImageSharp [MethodImpl(InliningOptions.ShortMethod)] private static void ConvolveImpl( - in DenseMatrix matrix, - Span yOffsetSpan, - Span xOffsetSpan, + in DenseMatrix kernel, + Span rowSampleOffsets, + Span columnSampleOffsets, Buffer2D sourcePixels, int row, int column, ref Vector4 targetVector) where TPixel : unmanaged, IPixel { - int matrixHeight = matrix.Rows; - int matrixWidth = matrix.Columns; + int kernelHeight = kernel.Rows; + int kernelWidth = kernel.Columns; - for (int y = 0; y < matrixHeight; y++) + for (int y = 0; y < kernelHeight; y++) { - int offsetY = yOffsetSpan[(row * matrixHeight) + y]; + int offsetY = rowSampleOffsets[(row * kernelHeight) + y]; Span sourceRowSpan = sourcePixels.GetRowSpan(offsetY); - for (int x = 0; x < matrixWidth; x++) + for (int x = 0; x < kernelWidth; x++) { - int offsetX = xOffsetSpan[(column * matrixWidth) + x]; - var currentColor = sourceRowSpan[offsetX].ToVector4(); - Numerics.Premultiply(ref currentColor); - targetVector += matrix[y, x] * currentColor; + int offsetX = columnSampleOffsets[(column * kernelWidth) + x]; + var sample = sourceRowSpan[offsetX].ToVector4(); + Numerics.Premultiply(ref sample); + targetVector += kernel[y, x] * sample; } } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/Kernels/KernelOffsetMap.cs b/src/ImageSharp/Processing/Processors/Convolution/Kernels/KernelSamplingMap.cs similarity index 66% rename from src/ImageSharp/Processing/Processors/Convolution/Kernels/KernelOffsetMap.cs rename to src/ImageSharp/Processing/Processors/Convolution/Kernels/KernelSamplingMap.cs index c1adf357ca..493c0d0fd2 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Kernels/KernelOffsetMap.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Kernels/KernelSamplingMap.cs @@ -11,7 +11,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// /// Provides a map of the convolution kernel sampling offsets. /// - internal sealed class KernelOffsetMap : IDisposable + internal sealed class KernelSamplingMap : IDisposable { private readonly MemoryAllocator allocator; private bool isDisposed; @@ -19,37 +19,42 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution private IMemoryOwner xOffsets; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The memory allocator. - public KernelOffsetMap(MemoryAllocator allocator) => this.allocator = allocator; + public KernelSamplingMap(MemoryAllocator allocator) => this.allocator = allocator; - public void BuildOffsetMap(in DenseMatrix matrix, Rectangle bounds) + /// + /// Builds a map of the sampling offsets for the kernel clamped by the given bounds. + /// + /// The convolution kernel. + /// The source bounds. + public void BuildSamplingOffsetMap(DenseMatrix kernel, Rectangle bounds) { - int matrixHeight = matrix.Rows; - int matrixWidth = matrix.Columns; - this.yOffsets = this.allocator.Allocate(bounds.Height * matrixHeight); - this.xOffsets = this.allocator.Allocate(bounds.Width * matrixWidth); + int kernelHeight = kernel.Rows; + int kernelWidth = kernel.Columns; + this.yOffsets = this.allocator.Allocate(bounds.Height * kernelHeight); + this.xOffsets = this.allocator.Allocate(bounds.Width * kernelWidth); int minY = bounds.Y; int maxY = bounds.Bottom - 1; int minX = bounds.X; int maxX = bounds.Right - 1; - int radiusY = matrixHeight >> 1; - int radiusX = matrixWidth >> 1; + int radiusY = kernelHeight >> 1; + int radiusX = kernelWidth >> 1; // Calculate the potential sampling y-offsets. Span ySpan = this.yOffsets.GetSpan(); for (int row = 0; row < bounds.Height; row++) { - for (int y = 0; y < matrixHeight; y++) + for (int y = 0; y < kernelHeight; y++) { - ySpan[(row * matrixHeight) + y] = row + y + minY - radiusY; + ySpan[(row * kernelHeight) + y] = row + y + minY - radiusY; } } - if (matrixHeight > 1) + if (kernelHeight > 1) { Numerics.Clamp(ySpan, minY, maxY); } @@ -58,13 +63,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution Span xSpan = this.xOffsets.GetSpan(); for (int column = 0; column < bounds.Width; column++) { - for (int x = 0; x < matrixWidth; x++) + for (int x = 0; x < kernelWidth; x++) { - xSpan[(column * matrixWidth) + x] = column + x + minX - radiusX; + xSpan[(column * kernelWidth) + x] = column + x + minX - radiusX; } } - if (matrixWidth > 1) + if (kernelWidth > 1) { Numerics.Clamp(xSpan, minX, maxX); }