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 };