diff --git a/src/ImageSharp.Drawing/Processing/PatternBrush{TPixel}.cs b/src/ImageSharp.Drawing/Processing/PatternBrush{TPixel}.cs
index 46ed36f68..20161b517 100644
--- a/src/ImageSharp.Drawing/Processing/PatternBrush{TPixel}.cs
+++ b/src/ImageSharp.Drawing/Processing/PatternBrush{TPixel}.cs
@@ -61,7 +61,7 @@ namespace SixLabors.ImageSharp.Processing
/// Color of the fore.
/// Color of the back.
/// The pattern.
- internal PatternBrush(TPixel foreColor, TPixel backColor, DenseMatrix pattern)
+ internal PatternBrush(TPixel foreColor, TPixel backColor, in DenseMatrix pattern)
{
var foreColorVector = foreColor.ToVector4();
var backColorVector = backColor.ToVector4();
@@ -93,10 +93,7 @@ namespace SixLabors.ImageSharp.Processing
}
///
- public BrushApplicator CreateApplicator(ImageFrame source, RectangleF region, GraphicsOptions options)
- {
- return new PatternBrushApplicator(source, this.pattern, this.patternVector, options);
- }
+ public BrushApplicator CreateApplicator(ImageFrame source, RectangleF region, GraphicsOptions options) => new PatternBrushApplicator(source, this.pattern, this.patternVector, options);
///
/// The pattern brush applicator.
@@ -116,7 +113,7 @@ namespace SixLabors.ImageSharp.Processing
/// The pattern.
/// The patternVector.
/// The options
- public PatternBrushApplicator(ImageFrame source, DenseMatrix pattern, DenseMatrix patternVector, GraphicsOptions options)
+ public PatternBrushApplicator(ImageFrame source, in DenseMatrix pattern, DenseMatrix patternVector, GraphicsOptions options)
: base(source, options)
{
this.pattern = pattern;
diff --git a/src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs b/src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs
index 2e700c9d6..427b24005 100644
--- a/src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs
+++ b/src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs
@@ -3,6 +3,7 @@
using System;
using System.Numerics;
+using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Primitives;
@@ -11,83 +12,120 @@ namespace SixLabors.ImageSharp
{
///
/// Extension methods for .
+ /// TODO: One day rewrite all this to use SIMD intrinsics. There's a lot of scope for improvement.
///
internal static class DenseMatrixUtils
{
///
- /// Computes the sum of vectors in weighted by the kernel weight values.
+ /// 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 dense matrix.
+ /// The vertical dense matrix.
+ /// The horizontal dense matrix.
/// The source frame.
- /// The target row.
+ /// 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.
- /// The column offset to apply to source sampling.
- public static void Convolve(
- in DenseMatrix matrix,
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public static void Convolve2D3(
+ in DenseMatrix matrixY,
+ in DenseMatrix matrixX,
Buffer2D sourcePixels,
- Span targetRow,
+ ref Vector4 targetRowRef,
int row,
int column,
+ int minRow,
int maxRow,
- int maxColumn,
- int offsetColumn)
+ int minColumn,
+ int maxColumn)
where TPixel : struct, IPixel
{
Vector4 vector = default;
- int matrixHeight = matrix.Rows;
- int matrixWidth = matrix.Columns;
- int radiusY = matrixHeight >> 1;
- int radiusX = matrixWidth >> 1;
- int sourceOffsetColumnBase = column + offsetColumn;
-
- for (int y = 0; y < matrixHeight; y++)
- {
- int offsetY = (row + y - radiusY).Clamp(0, maxRow);
- Span sourceRowSpan = sourcePixels.GetRowSpan(offsetY);
- for (int x = 0; x < matrixWidth; x++)
- {
- int offsetX = (sourceOffsetColumnBase + x - radiusX).Clamp(offsetColumn, maxColumn);
- var currentColor = sourceRowSpan[offsetX].ToVector4();
- Vector4Utils.Premultiply(ref currentColor);
-
- vector += matrix[y, x] * currentColor;
- }
- }
+ Convolve2DImpl(
+ in matrixY,
+ in matrixX,
+ sourcePixels,
+ row,
+ column,
+ minRow,
+ maxRow,
+ minColumn,
+ maxColumn,
+ ref vector);
- ref Vector4 target = ref targetRow[column];
+ ref Vector4 target = ref Unsafe.Add(ref targetRowRef, column);
vector.W = target.W;
+
Vector4Utils.UnPremultiply(ref vector);
target = vector;
}
///
- /// Computes the sum of vectors in weighted by the two kernel weight values.
+ /// Computes the sum of vectors in the span referenced by weighted by the two kernel weight values.
+ /// 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 source frame.
- /// The target row.
+ /// 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.
- /// The column offset to apply to source sampling.
- public static void Convolve2D(
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public static void Convolve2D4(
in DenseMatrix matrixY,
in DenseMatrix matrixX,
Buffer2D sourcePixels,
- Span targetRow,
+ ref Vector4 targetRowRef,
int row,
int column,
+ int minRow,
int maxRow,
+ int minColumn,
+ int maxColumn)
+ where TPixel : struct, IPixel
+ {
+ Vector4 vector = default;
+
+ Convolve2DImpl(
+ in matrixY,
+ in matrixX,
+ sourcePixels,
+ row,
+ column,
+ minRow,
+ maxRow,
+ minColumn,
+ maxColumn,
+ ref vector);
+
+ ref Vector4 target = ref Unsafe.Add(ref targetRowRef, column);
+ Vector4Utils.UnPremultiply(ref vector);
+ target = vector;
+ }
+
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public static void Convolve2DImpl(
+ in DenseMatrix matrixY,
+ in DenseMatrix matrixX,
+ Buffer2D sourcePixels,
+ int row,
+ int column,
+ int minRow,
+ int maxRow,
+ int minColumn,
int maxColumn,
- int offsetColumn)
+ ref Vector4 vector)
where TPixel : struct, IPixel
{
Vector4 vectorY = default;
@@ -96,16 +134,16 @@ namespace SixLabors.ImageSharp
int matrixWidth = matrixY.Columns;
int radiusY = matrixHeight >> 1;
int radiusX = matrixWidth >> 1;
- int sourceOffsetColumnBase = column + offsetColumn;
+ int sourceOffsetColumnBase = column + minColumn;
for (int y = 0; y < matrixHeight; y++)
{
- int offsetY = (row + y - radiusY).Clamp(0, maxRow);
+ int offsetY = (row + y - radiusY).Clamp(minRow, maxRow);
Span sourceRowSpan = sourcePixels.GetRowSpan(offsetY);
for (int x = 0; x < matrixWidth; x++)
{
- int offsetX = (sourceOffsetColumnBase + x - radiusX).Clamp(offsetColumn, maxColumn);
+ int offsetX = (sourceOffsetColumnBase + x - radiusX).Clamp(minColumn, maxColumn);
var currentColor = sourceRowSpan[offsetX].ToVector4();
Vector4Utils.Premultiply(ref currentColor);
@@ -114,11 +152,133 @@ namespace SixLabors.ImageSharp
}
}
- var vector = Vector4.SquareRoot((vectorX * vectorX) + (vectorY * vectorY));
- ref Vector4 target = ref targetRow[column];
+ vector = Vector4.SquareRoot((vectorX * vectorX) + (vectorY * vectorY));
+ }
+
+ ///
+ /// Computes the sum of vectors in the span referenced by weighted by the kernel weight values.
+ /// Using this method the convolution filter is not applied to alpha in addition to the color channels.
+ ///
+ /// The pixel format.
+ /// The dense matrix.
+ /// 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 Convolve3(
+ in DenseMatrix matrix,
+ Buffer2D sourcePixels,
+ ref Vector4 targetRowRef,
+ int row,
+ int column,
+ int minRow,
+ int maxRow,
+ int minColumn,
+ int maxColumn)
+ where TPixel : struct, IPixel
+ {
+ Vector4 vector = default;
+
+ ConvolveImpl(
+ in matrix,
+ sourcePixels,
+ row,
+ column,
+ minRow,
+ maxRow,
+ minColumn,
+ maxColumn,
+ ref vector);
+
+ ref Vector4 target = ref Unsafe.Add(ref targetRowRef, column);
vector.W = target.W;
+
Vector4Utils.UnPremultiply(ref vector);
target = vector;
}
+
+ ///
+ /// Computes the sum of vectors in the span referenced by weighted by the kernel weight values.
+ /// Using this method the convolution filter is applied to alpha in addition to the color channels.
+ ///
+ /// The pixel format.
+ /// The dense matrix.
+ /// 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 Convolve4(
+ in DenseMatrix matrix,
+ Buffer2D sourcePixels,
+ ref Vector4 targetRowRef,
+ int row,
+ int column,
+ int minRow,
+ int maxRow,
+ int minColumn,
+ int maxColumn)
+ where TPixel : struct, IPixel
+ {
+ Vector4 vector = default;
+
+ ConvolveImpl(
+ in matrix,
+ sourcePixels,
+ row,
+ column,
+ minRow,
+ maxRow,
+ minColumn,
+ maxColumn,
+ ref vector);
+
+ ref Vector4 target = ref Unsafe.Add(ref targetRowRef, column);
+ Vector4Utils.UnPremultiply(ref vector);
+ target = vector;
+ }
+
+ [MethodImpl(InliningOptions.ShortMethod)]
+ private static void ConvolveImpl(
+ in DenseMatrix matrix,
+ Buffer2D sourcePixels,
+ int row,
+ int column,
+ int minRow,
+ int maxRow,
+ int minColumn,
+ int maxColumn,
+ ref Vector4 vector)
+ where TPixel : struct, IPixel
+ {
+ int matrixHeight = matrix.Rows;
+ int matrixWidth = matrix.Columns;
+ int radiusY = matrixHeight >> 1;
+ int radiusX = matrixWidth >> 1;
+ int sourceOffsetColumnBase = column + minColumn;
+
+ for (int y = 0; y < matrixHeight; y++)
+ {
+ int offsetY = (row + y - radiusY).Clamp(minRow, maxRow);
+ Span sourceRowSpan = sourcePixels.GetRowSpan(offsetY);
+
+ for (int x = 0; x < matrixWidth; x++)
+ {
+ int offsetX = (sourceOffsetColumnBase + x - radiusX).Clamp(minColumn, maxColumn);
+ var currentColor = sourceRowSpan[offsetX].ToVector4();
+ Vector4Utils.Premultiply(ref currentColor);
+ vector += matrix[y, x] * currentColor;
+ }
+ }
+ }
}
}
\ No newline at end of file
diff --git a/src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor.cs
index 644d6c9e1..3d5bdc42a 100644
--- a/src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor.cs
@@ -49,7 +49,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
public DenseMatrix KernelY { get; }
///
- protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) => new Convolution2PassProcessor(this.KernelX, this.KernelY).Apply(source, sourceRectangle, configuration);
+ protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration)
+ => new Convolution2PassProcessor(this.KernelX, this.KernelY, false).Apply(source, sourceRectangle, configuration);
///
/// Create a 1 dimensional Box kernel.
diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs
index 341a26ae8..633b50a9b 100644
--- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs
@@ -3,6 +3,7 @@
using System;
using System.Numerics;
+using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.ParallelUtils;
using SixLabors.ImageSharp.PixelFormats;
@@ -23,11 +24,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
///
/// The horizontal gradient operator.
/// The vertical gradient operator.
- public Convolution2DProcessor(DenseMatrix kernelX, DenseMatrix kernelY)
+ /// Whether the convolution filter is applied to alpha as well as the color channels.
+ public Convolution2DProcessor(in DenseMatrix kernelX, in DenseMatrix kernelY, bool preserveAlpha)
{
Guard.IsTrue(kernelX.Size.Equals(kernelY.Size), $"{nameof(kernelX)} {nameof(kernelY)}", "Kernel sizes must be the same.");
this.KernelX = kernelX;
this.KernelY = kernelY;
+ this.PreserveAlpha = preserveAlpha;
}
///
@@ -40,6 +43,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
///
public DenseMatrix KernelY { get; }
+ ///
+ /// Gets a value indicating whether the convolution filter is applied to alpha as well as the color channels.
+ ///
+ public bool PreserveAlpha { get; }
+
///
protected override void OnFrameApply(
ImageFrame source,
@@ -48,6 +56,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{
DenseMatrix matrixY = this.KernelY;
DenseMatrix matrixX = this.KernelX;
+ bool preserveAlpha = this.PreserveAlpha;
var interest = Rectangle.Intersect(sourceRectangle, source.Bounds());
int startY = interest.Y;
@@ -71,18 +80,49 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{
Span vectorSpan = vectorBuffer.Span;
int length = vectorSpan.Length;
+ ref Vector4 vectorSpanRef = ref MemoryMarshal.GetReference(vectorSpan);
for (int y = rows.Min; y < rows.Max; y++)
{
Span targetRowSpan = targetPixels.GetRowSpan(y).Slice(startX);
PixelOperations.Instance.ToVector4(configuration, targetRowSpan.Slice(0, length), vectorSpan);
- for (int x = 0; x < width; x++)
+ if (preserveAlpha)
+ {
+ for (int x = 0; x < width; x++)
+ {
+ DenseMatrixUtils.Convolve2D3(
+ in matrixY,
+ in matrixX,
+ source.PixelBuffer,
+ ref vectorSpanRef,
+ y,
+ x,
+ startY,
+ maxY,
+ startX,
+ maxX);
+ }
+ }
+ else
{
- DenseMatrixUtils.Convolve2D(in matrixY, in matrixX, source.PixelBuffer, vectorSpan, y, x, maxY, maxX, startX);
+ for (int x = 0; x < width; x++)
+ {
+ DenseMatrixUtils.Convolve2D4(
+ in matrixY,
+ in matrixX,
+ source.PixelBuffer,
+ ref vectorSpanRef,
+ y,
+ x,
+ startY,
+ maxY,
+ startX,
+ maxX);
+ }
}
- PixelOperations.Instance.FromVector4Destructive(configuration, vectorSpan.Slice(0, length), targetRowSpan);
+ PixelOperations.Instance.FromVector4Destructive(configuration, vectorSpan, targetRowSpan);
}
});
diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs
index 9de467328..03268c9dd 100644
--- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs
@@ -3,7 +3,7 @@
using System;
using System.Numerics;
-
+using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.ParallelUtils;
using SixLabors.ImageSharp.PixelFormats;
@@ -24,10 +24,15 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
///
/// The horizontal gradient operator.
/// The vertical gradient operator.
- public Convolution2PassProcessor(DenseMatrix kernelX, DenseMatrix kernelY)
+ /// Whether the convolution filter is applied to alpha as well as the color channels.
+ public Convolution2PassProcessor(
+ in DenseMatrix kernelX,
+ in DenseMatrix kernelY,
+ bool preserveAlpha)
{
this.KernelX = kernelX;
this.KernelY = kernelY;
+ this.PreserveAlpha = preserveAlpha;
}
///
@@ -40,13 +45,16 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
///
public DenseMatrix KernelY { get; }
+ ///
+ /// Gets a value indicating whether the convolution filter is applied to alpha as well as the color channels.
+ ///
+ public bool PreserveAlpha { get; }
+
///
protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration)
{
using (Buffer2D firstPassPixels = configuration.MemoryAllocator.Allocate2D(source.Size()))
{
- source.CopyTo(firstPassPixels);
-
var interest = Rectangle.Intersect(sourceRectangle, source.Bounds());
this.ApplyConvolution(firstPassPixels, source.PixelBuffer, interest, this.KernelX, configuration);
this.ApplyConvolution(source.PixelBuffer, firstPassPixels, interest, this.KernelY, configuration);
@@ -72,6 +80,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
Configuration configuration)
{
DenseMatrix matrix = kernel;
+ bool preserveAlpha = this.PreserveAlpha;
+
int startY = sourceRectangle.Y;
int endY = sourceRectangle.Bottom;
int startX = sourceRectangle.X;
@@ -89,18 +99,47 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{
Span vectorSpan = vectorBuffer.Span;
int length = vectorSpan.Length;
+ ref Vector4 vectorSpanRef = ref MemoryMarshal.GetReference(vectorSpan);
for (int y = rows.Min; y < rows.Max; y++)
{
Span targetRowSpan = targetPixels.GetRowSpan(y).Slice(startX);
PixelOperations.Instance.ToVector4(configuration, targetRowSpan.Slice(0, length), vectorSpan);
- for (int x = 0; x < width; x++)
+ if (preserveAlpha)
+ {
+ for (int x = 0; x < width; x++)
+ {
+ DenseMatrixUtils.Convolve3(
+ in matrix,
+ sourcePixels,
+ ref vectorSpanRef,
+ y,
+ x,
+ startY,
+ maxY,
+ startX,
+ maxX);
+ }
+ }
+ else
{
- DenseMatrixUtils.Convolve(in matrix, sourcePixels, vectorSpan, y, x, maxY, maxX, startX);
+ for (int x = 0; x < width; x++)
+ {
+ DenseMatrixUtils.Convolve4(
+ in matrix,
+ sourcePixels,
+ ref vectorSpanRef,
+ y,
+ x,
+ startY,
+ maxY,
+ startX,
+ maxX);
+ }
}
- PixelOperations.Instance.FromVector4Destructive(configuration, vectorSpan.Slice(0, length), targetRowSpan);
+ PixelOperations.Instance.FromVector4Destructive(configuration, vectorSpan, targetRowSpan);
}
});
}
diff --git a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs
index 7e003cb03..6c3b9a46f 100644
--- a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs
@@ -3,6 +3,7 @@
using System;
using System.Numerics;
+using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.ParallelUtils;
using SixLabors.ImageSharp.PixelFormats;
@@ -22,17 +23,29 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// Initializes a new instance of the class.
///
/// The 2d gradient operator.
- public ConvolutionProcessor(DenseMatrix kernelXY) => this.KernelXY = kernelXY;
+ /// Whether the convolution filter is applied to alpha as well as the color channels.
+ public ConvolutionProcessor(in DenseMatrix kernelXY, bool preserveAlpha)
+ {
+ this.KernelXY = kernelXY;
+ this.PreserveAlpha = preserveAlpha;
+ }
///
/// Gets the 2d gradient operator.
///
public DenseMatrix KernelXY { get; }
+ ///
+ /// Gets a value indicating whether the convolution filter is applied to alpha as well as the color channels.
+ ///
+ public bool PreserveAlpha { get; }
+
///
protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration)
{
DenseMatrix matrix = this.KernelXY;
+ bool preserveAlpha = this.PreserveAlpha;
+
var interest = Rectangle.Intersect(sourceRectangle, source.Bounds());
int startY = interest.Y;
int endY = interest.Bottom;
@@ -55,18 +68,47 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{
Span vectorSpan = vectorBuffer.Span;
int length = vectorSpan.Length;
+ ref Vector4 vectorSpanRef = ref MemoryMarshal.GetReference(vectorSpan);
for (int y = rows.Min; y < rows.Max; y++)
{
Span targetRowSpan = targetPixels.GetRowSpan(y).Slice(startX);
PixelOperations.Instance.ToVector4(configuration, targetRowSpan.Slice(0, length), vectorSpan);
- for (int x = 0; x < width; x++)
+ if (preserveAlpha)
+ {
+ for (int x = 0; x < width; x++)
+ {
+ DenseMatrixUtils.Convolve3(
+ in matrix,
+ source.PixelBuffer,
+ ref vectorSpanRef,
+ y,
+ x,
+ startY,
+ maxY,
+ startX,
+ maxX);
+ }
+ }
+ else
{
- DenseMatrixUtils.Convolve(in matrix, source.PixelBuffer, vectorSpan, y, x, maxY, maxX, startX);
+ for (int x = 0; x < width; x++)
+ {
+ DenseMatrixUtils.Convolve4(
+ in matrix,
+ source.PixelBuffer,
+ ref vectorSpanRef,
+ y,
+ x,
+ startY,
+ maxY,
+ startX,
+ maxX);
+ }
}
- PixelOperations.Instance.FromVector4Destructive(configuration, vectorSpan.Slice(0, length), targetRowSpan);
+ PixelOperations.Instance.FromVector4Destructive(configuration, vectorSpan, targetRowSpan);
}
});
diff --git a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetector2DProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetector2DProcessor.cs
index 892771649..d2e9630dc 100644
--- a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetector2DProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetector2DProcessor.cs
@@ -21,7 +21,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// The horizontal gradient operator.
/// The vertical gradient operator.
/// Whether to convert the image to grayscale before performing edge detection.
- protected EdgeDetector2DProcessor(DenseMatrix kernelX, DenseMatrix kernelY, bool grayscale)
+ protected EdgeDetector2DProcessor(in DenseMatrix kernelX, in DenseMatrix kernelY, bool grayscale)
{
Guard.IsTrue(kernelX.Size.Equals(kernelY.Size), $"{nameof(kernelX)} {nameof(kernelY)}", "Kernel sizes must be the same.");
this.KernelX = kernelX;
@@ -43,7 +43,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
public bool Grayscale { get; set; }
///
- protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) => new Convolution2DProcessor(this.KernelX, this.KernelY).Apply(source, sourceRectangle, configuration);
+ protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration)
+ => new Convolution2DProcessor(this.KernelX, this.KernelY, true).Apply(source, sourceRectangle, configuration);
///
protected override void BeforeFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration)
diff --git a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor.cs
index 4165cf024..73f92fae3 100644
--- a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor.cs
@@ -5,14 +5,12 @@ using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
-using System.Threading.Tasks;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.ParallelUtils;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Primitives;
using SixLabors.ImageSharp.Processing.Processors.Filters;
-using SixLabors.Memory;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors.Convolution
@@ -28,10 +26,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// Initializes a new instance of the class.
///
/// Whether to convert the image to grayscale before performing edge detection.
- protected EdgeDetectorCompassProcessor(bool grayscale)
- {
- this.Grayscale = grayscale;
- }
+ protected EdgeDetectorCompassProcessor(bool grayscale) => this.Grayscale = grayscale;
///
/// Gets the North gradient operator
@@ -104,7 +99,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
// we need a clean copy for each pass to start from
using (ImageFrame cleanCopy = source.Clone())
{
- new ConvolutionProcessor(kernels[0]).Apply(source, sourceRectangle, configuration);
+ new ConvolutionProcessor(kernels[0], true).Apply(source, sourceRectangle, configuration);
if (kernels.Length == 1)
{
@@ -133,7 +128,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{
using (ImageFrame pass = cleanCopy.Clone())
{
- new ConvolutionProcessor(kernels[i]).Apply(pass, sourceRectangle, configuration);
+ new ConvolutionProcessor(kernels[i], true).Apply(pass, sourceRectangle, configuration);
Buffer2D passPixels = pass.PixelBuffer;
Buffer2D targetPixels = source.PixelBuffer;
@@ -147,10 +142,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{
int offsetY = y - shiftY;
- ref TPixel passPixelsBase =
- ref MemoryMarshal.GetReference(passPixels.GetRowSpan(offsetY));
- ref TPixel targetPixelsBase =
- ref MemoryMarshal.GetReference(targetPixels.GetRowSpan(offsetY));
+ ref TPixel passPixelsBase = ref MemoryMarshal.GetReference(passPixels.GetRowSpan(offsetY));
+ ref TPixel targetPixelsBase = ref MemoryMarshal.GetReference(targetPixels.GetRowSpan(offsetY));
for (int x = minX; x < maxX; x++)
{
@@ -158,8 +151,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
// Grab the max components of the two pixels
ref TPixel currentPassPixel = ref Unsafe.Add(ref passPixelsBase, offsetX);
- ref TPixel currentTargetPixel =
- ref Unsafe.Add(ref targetPixelsBase, offsetX);
+ ref TPixel currentTargetPixel = ref Unsafe.Add(ref targetPixelsBase, offsetX);
var pixelValue = Vector4.Max(
currentPassPixel.ToVector4(),
diff --git a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor.cs
index 9173bc229..edc7ec4cc 100644
--- a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor.cs
@@ -20,7 +20,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
///
/// The 2d gradient operator.
/// Whether to convert the image to grayscale before performing edge detection.
- protected EdgeDetectorProcessor(DenseMatrix kernelXY, bool grayscale)
+ protected EdgeDetectorProcessor(in DenseMatrix kernelXY, bool grayscale)
{
this.KernelXY = kernelXY;
this.Grayscale = grayscale;
@@ -45,8 +45,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
///
protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration)
- {
- new ConvolutionProcessor(this.KernelXY).Apply(source, sourceRectangle, configuration);
- }
+ => new ConvolutionProcessor(this.KernelXY, true).Apply(source, sourceRectangle, configuration);
}
}
\ No newline at end of file
diff --git a/src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor.cs
index b3bc15d39..0fc822d57 100644
--- a/src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor.cs
@@ -77,7 +77,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
///
protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration)
- => new Convolution2PassProcessor(this.KernelX, this.KernelY).Apply(source, sourceRectangle, configuration);
+ => new Convolution2PassProcessor(this.KernelX, this.KernelY, false).Apply(source, sourceRectangle, configuration);
///
/// Create a 1 dimensional Gaussian kernel using the Gaussian G(x) function
diff --git a/src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor.cs
index 786bf7757..001471720 100644
--- a/src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor.cs
@@ -79,7 +79,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
///
protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration)
- => new Convolution2PassProcessor(this.KernelX, this.KernelY).Apply(source, sourceRectangle, configuration);
+ => new Convolution2PassProcessor(this.KernelX, this.KernelY, false).Apply(source, sourceRectangle, configuration);
///
/// Create a 1 dimensional Gaussian kernel using the Gaussian G(x) function
diff --git a/src/ImageSharp/Processing/Processors/Dithering/ErrorDiffuserBase.cs b/src/ImageSharp/Processing/Processors/Dithering/ErrorDiffuserBase.cs
index 642da2f00..abf5dce18 100644
--- a/src/ImageSharp/Processing/Processors/Dithering/ErrorDiffuserBase.cs
+++ b/src/ImageSharp/Processing/Processors/Dithering/ErrorDiffuserBase.cs
@@ -45,7 +45,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
///
/// The dithering matrix.
/// The divisor.
- internal ErrorDiffuserBase(DenseMatrix matrix, byte divisor)
+ internal ErrorDiffuserBase(in DenseMatrix matrix, byte divisor)
{
Guard.MustBeGreaterThan(divisor, 0, nameof(divisor));
diff --git a/tests/ImageSharp.Benchmarks/Samplers/GaussianBlur.cs b/tests/ImageSharp.Benchmarks/Samplers/GaussianBlur.cs
new file mode 100644
index 000000000..47bd42a3c
--- /dev/null
+++ b/tests/ImageSharp.Benchmarks/Samplers/GaussianBlur.cs
@@ -0,0 +1,19 @@
+using BenchmarkDotNet.Attributes;
+using SixLabors.ImageSharp.PixelFormats;
+using SixLabors.ImageSharp.Processing;
+
+namespace SixLabors.ImageSharp.Benchmarks.Samplers
+{
+ [Config(typeof(Config.ShortClr))]
+ public class GaussianBlur
+ {
+ [Benchmark]
+ public void Blur()
+ {
+ using (var image = new Image(Configuration.Default, 400, 400, Rgba32.White))
+ {
+ image.Mutate(c => c.GaussianBlur());
+ }
+ }
+ }
+}
diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.MatrixTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.MatrixTests.cs
index b43c17f79..088222222 100644
--- a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.MatrixTests.cs
+++ b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.MatrixTests.cs
@@ -38,7 +38,7 @@ namespace SixLabors.ImageSharp.Tests.Icc
[Theory]
[MemberData(nameof(IccTestDataMatrix.Matrix2D_DenseMatrixTestData), MemberType = typeof(IccTestDataMatrix))]
- internal void WriteMatrix2D_DenseMatrix(byte[] expected, int xCount, int yCount, bool isSingle, DenseMatrix data)
+ internal void WriteMatrix2D_DenseMatrix(byte[] expected, int xCount, int yCount, bool isSingle, in DenseMatrix data)
{
IccDataWriter writer = CreateWriter();
diff --git a/tests/ImageSharp.Tests/Processing/Processors/Convolution/BoxBlurTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Convolution/BoxBlurTest.cs
index 0c40debad..1f0d12cf7 100644
--- a/tests/ImageSharp.Tests/Processing/Processors/Convolution/BoxBlurTest.cs
+++ b/tests/ImageSharp.Tests/Processing/Processors/Convolution/BoxBlurTest.cs
@@ -37,7 +37,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution
where TPixel : struct, IPixel
{
using (Image source = provider.GetImage())
- using (var image = source.Clone())
+ using (Image image = source.Clone())
{
var bounds = new Rectangle(10, 10, image.Width / 2, image.Height / 2);
diff --git a/tests/ImageSharp.Tests/Processing/Processors/Convolution/DetectEdgesTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Convolution/DetectEdgesTest.cs
index edb24d6f1..b6a7741b3 100644
--- a/tests/ImageSharp.Tests/Processing/Processors/Convolution/DetectEdgesTest.cs
+++ b/tests/ImageSharp.Tests/Processing/Processors/Convolution/DetectEdgesTest.cs
@@ -12,6 +12,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution
{
public class DetectEdgesTest : FileTestBase
{
+ // I think our comparison is not accurate enough (nor can be) for RgbaVector.
+ // The image pixels are identical according to BeyondCompare.
private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.0456F);
public static readonly string[] CommonTestImages = { TestImages.Png.Bike };