From 3137c6217634860fe16b7ab9d29cefea5aabc0c0 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 23 Feb 2019 23:19:53 +1100 Subject: [PATCH 01/11] Remove multiple premultiplication. --- .../Common/Helpers/ConvolutionPassType.cs | 26 +++ .../Common/Helpers/DenseMatrixUtils.cs | 155 +++++++++++++++--- .../Convolution/Convolution2DProcessor.cs | 8 +- .../Convolution/Convolution2PassProcessor.cs | 28 +++- .../Convolution/ConvolutionProcessor.cs | 17 +- 5 files changed, 192 insertions(+), 42 deletions(-) create mode 100644 src/ImageSharp/Common/Helpers/ConvolutionPassType.cs diff --git a/src/ImageSharp/Common/Helpers/ConvolutionPassType.cs b/src/ImageSharp/Common/Helpers/ConvolutionPassType.cs new file mode 100644 index 0000000000..88bc6ec81a --- /dev/null +++ b/src/ImageSharp/Common/Helpers/ConvolutionPassType.cs @@ -0,0 +1,26 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp +{ + /// + /// Enumerates the different convolution pass types. + /// + internal enum ConvolutionPassType + { + /// + /// A single pass. + /// + Single, + + /// + /// The first pass of a two pass convolution. + /// + First, + + /// + /// The second pass of a two pass convolution. + /// + Second + } +} diff --git a/src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs b/src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs index 2e700c9d67..ab374c4f89 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; @@ -15,21 +16,23 @@ namespace SixLabors.ImageSharp 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. /// /// 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 maximum working area row. /// The maximum working area column. /// The column offset to apply to source sampling. - public static void Convolve( - in DenseMatrix matrix, + public static void Convolve2D( + in DenseMatrix matrixY, + in DenseMatrix matrixX, Buffer2D sourcePixels, - Span targetRow, + ref Vector4 targetRowRef, int row, int column, int maxRow, @@ -37,9 +40,10 @@ public static void Convolve( int offsetColumn) where TPixel : struct, IPixel { - Vector4 vector = default; - int matrixHeight = matrix.Rows; - int matrixWidth = matrix.Columns; + 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 + offsetColumn; @@ -55,34 +59,61 @@ public static void Convolve( var currentColor = sourceRowSpan[offsetX].ToVector4(); Vector4Utils.Premultiply(ref currentColor); - vector += matrix[y, x] * currentColor; + vectorX += matrixX[y, x] * currentColor; + vectorY += matrixY[y, x] * currentColor; } } - ref Vector4 target = ref targetRow[column]; + var vector = Vector4.SquareRoot((vectorX * vectorX) + (vectorY * vectorY)); + 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 kernel weight values. /// /// The pixel format. - /// The vertical dense matrix. - /// The horizontal dense matrix. + /// The dense matrix. /// The source frame. - /// The target row. + /// The target row base reference. /// The current row. /// The current column. /// The maximum working area row. /// The maximum working area column. /// The column offset to apply to source sampling. - public static void Convolve2D( - in DenseMatrix matrixY, - in DenseMatrix matrixX, + /// The convolution pass type. + public static void Convolve( + in DenseMatrix matrix, Buffer2D sourcePixels, - Span targetRow, + ref Vector4 targetRowRef, + int row, + int column, + int maxRow, + int maxColumn, + int offsetColumn, + ConvolutionPassType passType) + where TPixel : struct, IPixel + { + switch (passType) + { + case ConvolutionPassType.Single: + ConvolveSinglePass(matrix, sourcePixels, ref targetRowRef, row, column, maxRow, maxColumn, offsetColumn); + break; + case ConvolutionPassType.First: + ConvolveFirstPass(matrix, sourcePixels, ref targetRowRef, row, column, maxRow, maxColumn, offsetColumn); + break; + case ConvolutionPassType.Second: + ConvolveSecondPass(matrix, sourcePixels, ref targetRowRef, row, column, maxRow, maxColumn, offsetColumn); + break; + } + } + + private static void ConvolveSinglePass( + in DenseMatrix matrix, + Buffer2D sourcePixels, + ref Vector4 targetRowRef, int row, int column, int maxRow, @@ -90,10 +121,9 @@ public static void Convolve2D( int offsetColumn) where TPixel : struct, IPixel { - Vector4 vectorY = default; - Vector4 vectorX = default; - int matrixHeight = matrixY.Rows; - int matrixWidth = matrixY.Columns; + Vector4 vector = default; + int matrixHeight = matrix.Rows; + int matrixWidth = matrix.Columns; int radiusY = matrixHeight >> 1; int radiusX = matrixWidth >> 1; int sourceOffsetColumnBase = column + offsetColumn; @@ -109,13 +139,84 @@ public static void Convolve2D( var currentColor = sourceRowSpan[offsetX].ToVector4(); Vector4Utils.Premultiply(ref currentColor); - vectorX += matrixX[y, x] * currentColor; - vectorY += matrixY[y, x] * currentColor; + vector += matrix[y, x] * currentColor; } } - var vector = Vector4.SquareRoot((vectorX * vectorX) + (vectorY * vectorY)); - 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; + } + + public static void ConvolveFirstPass( + in DenseMatrix matrix, + Buffer2D sourcePixels, + ref Vector4 targetRowRef, + int row, + int column, + int maxRow, + int maxColumn, + int offsetColumn) + 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; + } + } + + Unsafe.Add(ref targetRowRef, column) = vector; + } + + private static void ConvolveSecondPass( + in DenseMatrix matrix, + Buffer2D sourcePixels, + ref Vector4 targetRowRef, + int row, + int column, + int maxRow, + int maxColumn, + int offsetColumn) + 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(); + vector += matrix[y, x] * currentColor; + } + } + + ref Vector4 target = ref Unsafe.Add(ref targetRowRef, column); vector.W = target.W; Vector4Utils.UnPremultiply(ref vector); target = vector; diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs index bd1419e4bb..e9fd390fc7 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,7 +24,7 @@ internal class Convolution2DProcessor : ImageProcessor /// /// The horizontal gradient operator. /// The vertical gradient operator. - public Convolution2DProcessor(DenseMatrix kernelX, DenseMatrix kernelY) + public Convolution2DProcessor(in DenseMatrix kernelX, in DenseMatrix kernelY) { Guard.IsTrue(kernelX.Size.Equals(kernelY.Size), $"{nameof(kernelX)} {nameof(kernelY)}", "Kernel sizes must be the same."); this.KernelX = kernelX; @@ -71,6 +72,7 @@ protected override void OnFrameApply( { Span vectorSpan = vectorBuffer.Span; int length = vectorSpan.Length; + ref Vector4 vectorSpanRef = ref MemoryMarshal.GetReference(vectorSpan); for (int y = rows.Min; y < rows.Max; y++) { @@ -79,10 +81,10 @@ protected override void OnFrameApply( for (int x = 0; x < width; x++) { - DenseMatrixUtils.Convolve2D(in matrixY, in matrixX, source.PixelBuffer, vectorSpan, y, x, maxY, maxX, startX); + DenseMatrixUtils.Convolve2D(in matrixY, in matrixX, source.PixelBuffer, ref vectorSpanRef, y, x, maxY, maxX, startX); } - PixelOperations.Instance.FromVector4(configuration, vectorSpan.Slice(0, length), targetRowSpan); + PixelOperations.Instance.FromVector4(configuration, vectorSpan, targetRowSpan); } }); diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs index 05007c3706..54030ce722 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,7 +24,7 @@ internal class Convolution2PassProcessor : ImageProcessor /// /// The horizontal gradient operator. /// The vertical gradient operator. - public Convolution2PassProcessor(DenseMatrix kernelX, DenseMatrix kernelY) + public Convolution2PassProcessor(in DenseMatrix kernelX, in DenseMatrix kernelY) { this.KernelX = kernelX; this.KernelY = kernelY; @@ -45,11 +45,9 @@ protected override void OnFrameApply(ImageFrame source, Rectangle source { 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); + this.ApplyConvolution(firstPassPixels, source.PixelBuffer, interest, this.KernelX, configuration, ConvolutionPassType.First); + this.ApplyConvolution(source.PixelBuffer, firstPassPixels, interest, this.KernelY, configuration, ConvolutionPassType.Second); } } @@ -64,12 +62,14 @@ protected override void OnFrameApply(ImageFrame source, Rectangle source /// /// The kernel operator. /// The + /// The convolution pass type. private void ApplyConvolution( Buffer2D targetPixels, Buffer2D sourcePixels, Rectangle sourceRectangle, in DenseMatrix kernel, - Configuration configuration) + Configuration configuration, + ConvolutionPassType passType) { DenseMatrix matrix = kernel; int startY = sourceRectangle.Y; @@ -89,6 +89,7 @@ private void ApplyConvolution( { Span vectorSpan = vectorBuffer.Span; int length = vectorSpan.Length; + ref Vector4 vectorSpanRef = ref MemoryMarshal.GetReference(vectorSpan); for (int y = rows.Min; y < rows.Max; y++) { @@ -97,10 +98,19 @@ private void ApplyConvolution( for (int x = 0; x < width; x++) { - DenseMatrixUtils.Convolve(in matrix, sourcePixels, vectorSpan, y, x, maxY, maxX, startX); + DenseMatrixUtils.Convolve( + in matrix, + sourcePixels, + ref vectorSpanRef, + y, + x, + maxY, + maxX, + startX, + passType); } - PixelOperations.Instance.FromVector4(configuration, vectorSpan.Slice(0, length), targetRowSpan); + PixelOperations.Instance.FromVector4(configuration, vectorSpan, targetRowSpan); } }); } diff --git a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs index 8ef64bdacc..fc7f7f405a 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,7 +23,7 @@ internal class ConvolutionProcessor : ImageProcessor /// Initializes a new instance of the class. /// /// The 2d gradient operator. - public ConvolutionProcessor(DenseMatrix kernelXY) => this.KernelXY = kernelXY; + public ConvolutionProcessor(in DenseMatrix kernelXY) => this.KernelXY = kernelXY; /// /// Gets the 2d gradient operator. @@ -55,6 +56,7 @@ protected override void OnFrameApply(ImageFrame source, Rectangle source { Span vectorSpan = vectorBuffer.Span; int length = vectorSpan.Length; + ref Vector4 vectorSpanRef = ref MemoryMarshal.GetReference(vectorSpan); for (int y = rows.Min; y < rows.Max; y++) { @@ -63,10 +65,19 @@ protected override void OnFrameApply(ImageFrame source, Rectangle source for (int x = 0; x < width; x++) { - DenseMatrixUtils.Convolve(in matrix, source.PixelBuffer, vectorSpan, y, x, maxY, maxX, startX); + DenseMatrixUtils.Convolve( + in matrix, + source.PixelBuffer, + ref vectorSpanRef, + y, + x, + maxY, + maxX, + startX, + ConvolutionPassType.Single); } - PixelOperations.Instance.FromVector4(configuration, vectorSpan.Slice(0, length), targetRowSpan); + PixelOperations.Instance.FromVector4(configuration, vectorSpan, targetRowSpan); } }); From b67386b74db4b663f2961484cb62835be4e1b0a8 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 23 Feb 2019 23:30:09 +1100 Subject: [PATCH 02/11] Use in DenseMatrix everywhere. --- .../Processing/PatternBrush{TPixel}.cs | 9 +++------ .../Processors/Convolution/EdgeDetector2DProcessor.cs | 2 +- .../Processors/Convolution/EdgeDetectorProcessor.cs | 2 +- .../Processing/Processors/Dithering/ErrorDiffuserBase.cs | 2 +- .../Profiles/ICC/DataWriter/IccDataWriter.MatrixTests.cs | 2 +- 5 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/ImageSharp.Drawing/Processing/PatternBrush{TPixel}.cs b/src/ImageSharp.Drawing/Processing/PatternBrush{TPixel}.cs index 46ed36f687..20161b517d 100644 --- a/src/ImageSharp.Drawing/Processing/PatternBrush{TPixel}.cs +++ b/src/ImageSharp.Drawing/Processing/PatternBrush{TPixel}.cs @@ -61,7 +61,7 @@ public PatternBrush(TPixel foreColor, TPixel backColor, bool[,] pattern) /// 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 @@ internal PatternBrush(PatternBrush brush) } /// - 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 @@ private class PatternBrushApplicator : BrushApplicator /// 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/Processing/Processors/Convolution/EdgeDetector2DProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetector2DProcessor.cs index 8927716492..ef5f892d55 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetector2DProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetector2DProcessor.cs @@ -21,7 +21,7 @@ internal abstract class EdgeDetector2DProcessor : ImageProcessor /// 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; diff --git a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor.cs index 9173bc229b..136f7f69e9 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor.cs @@ -20,7 +20,7 @@ internal abstract class EdgeDetectorProcessor : ImageProcessor, /// /// 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; diff --git a/src/ImageSharp/Processing/Processors/Dithering/ErrorDiffuserBase.cs b/src/ImageSharp/Processing/Processors/Dithering/ErrorDiffuserBase.cs index 642da2f001..abf5dce184 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/ErrorDiffuserBase.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/ErrorDiffuserBase.cs @@ -45,7 +45,7 @@ public abstract class ErrorDiffuserBase : IErrorDiffuser /// /// 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.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.MatrixTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.MatrixTests.cs index 71dd18621c..76ccdf2320 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 @@ public void WriteMatrix2D_Matrix4x4(byte[] expected, int xCount, int yCount, boo [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(); From 5345cb3a4594aadabe24cdb3655c5f03dc24888c Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 23 Feb 2019 23:30:59 +1100 Subject: [PATCH 03/11] Make private --- src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs b/src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs index ab374c4f89..2be96dca94 100644 --- a/src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs +++ b/src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs @@ -149,7 +149,7 @@ private static void ConvolveSinglePass( target = vector; } - public static void ConvolveFirstPass( + private static void ConvolveFirstPass( in DenseMatrix matrix, Buffer2D sourcePixels, ref Vector4 targetRowRef, From d1f171768fe5c03ea07b5862fff48cc08b8ba69d Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sun, 24 Feb 2019 00:37:16 +1100 Subject: [PATCH 04/11] Dont convert vector row on first pass --- .../Processors/Convolution/Convolution2PassProcessor.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs index 54030ce722..f1cbed22fc 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs @@ -94,7 +94,11 @@ private void ApplyConvolution( 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); + + if (passType.Equals(ConvolutionPassType.Second)) + { + PixelOperations.Instance.ToVector4(configuration, targetRowSpan.Slice(0, length), vectorSpan); + } for (int x = 0; x < width; x++) { From 73b379f7f29982b1426867742ce861f83b6dec71 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sun, 24 Feb 2019 01:12:36 +1100 Subject: [PATCH 05/11] Remove incorrectly assigned alpha. --- src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs b/src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs index 2be96dca94..3090748fa7 100644 --- a/src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs +++ b/src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs @@ -217,7 +217,6 @@ private static void ConvolveSecondPass( } ref Vector4 target = ref Unsafe.Add(ref targetRowRef, column); - vector.W = target.W; Vector4Utils.UnPremultiply(ref vector); target = vector; } From a5aab566eefa1340b93936a05cbed6fb134179da Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sun, 24 Feb 2019 01:32:36 +1100 Subject: [PATCH 06/11] Remove boxing. --- .../Convolution/Convolution2PassProcessor.cs | 2 +- .../Samplers/GaussianBlur.cs | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 tests/ImageSharp.Benchmarks/Samplers/GaussianBlur.cs diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs index f1cbed22fc..e46581de2a 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs @@ -95,7 +95,7 @@ private void ApplyConvolution( { Span targetRowSpan = targetPixels.GetRowSpan(y).Slice(startX); - if (passType.Equals(ConvolutionPassType.Second)) + if (passType == ConvolutionPassType.Second) { PixelOperations.Instance.ToVector4(configuration, targetRowSpan.Slice(0, length), vectorSpan); } diff --git a/tests/ImageSharp.Benchmarks/Samplers/GaussianBlur.cs b/tests/ImageSharp.Benchmarks/Samplers/GaussianBlur.cs new file mode 100644 index 0000000000..47bd42a3c2 --- /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()); + } + } + } +} From 1d359d1e1019754ebe1fa1b20b67c5f46bfcb953 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 16 Apr 2019 12:15:46 +1000 Subject: [PATCH 07/11] Use correct min row. --- .../Common/Helpers/DenseMatrixUtils.cs | 21 ++++++++++++------- .../Convolution/Convolution2DProcessor.cs | 12 ++++++++++- .../Convolution/Convolution2PassProcessor.cs | 1 + .../Convolution/ConvolutionProcessor.cs | 1 + 4 files changed, 27 insertions(+), 8 deletions(-) diff --git a/src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs b/src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs index 3090748fa7..824587ed2e 100644 --- a/src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs +++ b/src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs @@ -25,6 +25,7 @@ internal static class DenseMatrixUtils /// The target row base reference. /// The current row. /// The current column. + /// The minimum working area row. /// The maximum working area row. /// The maximum working area column. /// The column offset to apply to source sampling. @@ -35,6 +36,7 @@ public static void Convolve2D( ref Vector4 targetRowRef, int row, int column, + int minRow, int maxRow, int maxColumn, int offsetColumn) @@ -50,7 +52,7 @@ public static void Convolve2D( 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++) @@ -80,6 +82,7 @@ public static void Convolve2D( /// The target row base reference. /// The current row. /// The current column. + /// The minimum working area row. /// The maximum working area row. /// The maximum working area column. /// The column offset to apply to source sampling. @@ -90,6 +93,7 @@ public static void Convolve( ref Vector4 targetRowRef, int row, int column, + int minRow, int maxRow, int maxColumn, int offsetColumn, @@ -99,13 +103,13 @@ public static void Convolve( switch (passType) { case ConvolutionPassType.Single: - ConvolveSinglePass(matrix, sourcePixels, ref targetRowRef, row, column, maxRow, maxColumn, offsetColumn); + ConvolveSinglePass(matrix, sourcePixels, ref targetRowRef, row, column, minRow, maxRow, maxColumn, offsetColumn); break; case ConvolutionPassType.First: - ConvolveFirstPass(matrix, sourcePixels, ref targetRowRef, row, column, maxRow, maxColumn, offsetColumn); + ConvolveFirstPass(matrix, sourcePixels, ref targetRowRef, row, column, minRow, maxRow, maxColumn, offsetColumn); break; case ConvolutionPassType.Second: - ConvolveSecondPass(matrix, sourcePixels, ref targetRowRef, row, column, maxRow, maxColumn, offsetColumn); + ConvolveSecondPass(matrix, sourcePixels, ref targetRowRef, row, column, minRow, maxRow, maxColumn, offsetColumn); break; } } @@ -116,6 +120,7 @@ private static void ConvolveSinglePass( ref Vector4 targetRowRef, int row, int column, + int minRow, int maxRow, int maxColumn, int offsetColumn) @@ -130,7 +135,7 @@ private static void ConvolveSinglePass( 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++) @@ -155,6 +160,7 @@ private static void ConvolveFirstPass( ref Vector4 targetRowRef, int row, int column, + int minRow, int maxRow, int maxColumn, int offsetColumn) @@ -169,7 +175,7 @@ private static void ConvolveFirstPass( 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++) @@ -191,6 +197,7 @@ private static void ConvolveSecondPass( ref Vector4 targetRowRef, int row, int column, + int minRow, int maxRow, int maxColumn, int offsetColumn) @@ -205,7 +212,7 @@ private static void ConvolveSecondPass( 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++) diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs index e9fd390fc7..6c85e818fe 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs @@ -81,7 +81,17 @@ protected override void OnFrameApply( for (int x = 0; x < width; x++) { - DenseMatrixUtils.Convolve2D(in matrixY, in matrixX, source.PixelBuffer, ref vectorSpanRef, y, x, maxY, maxX, startX); + DenseMatrixUtils.Convolve2D( + in matrixY, + in matrixX, + source.PixelBuffer, + ref vectorSpanRef, + y, + x, + startY, + maxY, + maxX, + startX); } PixelOperations.Instance.FromVector4(configuration, vectorSpan, targetRowSpan); diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs index e46581de2a..3019d8651d 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs @@ -108,6 +108,7 @@ private void ApplyConvolution( ref vectorSpanRef, y, x, + startY, maxY, maxX, startX, diff --git a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs index fc7f7f405a..f6a4e63999 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs @@ -71,6 +71,7 @@ protected override void OnFrameApply(ImageFrame source, Rectangle source ref vectorSpanRef, y, x, + startY, maxY, maxX, startX, From f1c024403bec480311565cf0af993eafa2279877 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 16 Apr 2019 12:40:44 +1000 Subject: [PATCH 08/11] Reorder parameters --- .../Common/Helpers/DenseMatrixUtils.cs | 47 ++++++++++--------- .../Convolution/Convolution2DProcessor.cs | 4 +- .../Convolution/Convolution2PassProcessor.cs | 2 +- .../Convolution/ConvolutionProcessor.cs | 2 +- .../Processors/Convolution/DetectEdgesTest.cs | 1 + 5 files changed, 30 insertions(+), 26 deletions(-) diff --git a/src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs b/src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs index 824587ed2e..2c0efbaead 100644 --- a/src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs +++ b/src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs @@ -27,8 +27,8 @@ internal static class DenseMatrixUtils /// 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( in DenseMatrix matrixY, in DenseMatrix matrixX, @@ -38,8 +38,8 @@ public static void Convolve2D( int column, int minRow, int maxRow, - int maxColumn, - int offsetColumn) + int minColumn, + int maxColumn) where TPixel : struct, IPixel { Vector4 vectorY = default; @@ -48,7 +48,7 @@ public static void Convolve2D( 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++) { @@ -57,7 +57,7 @@ public static void Convolve2D( 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); @@ -84,8 +84,8 @@ public static void Convolve2D( /// 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. /// The convolution pass type. public static void Convolve( in DenseMatrix matrix, @@ -95,21 +95,21 @@ public static void Convolve( int column, int minRow, int maxRow, + int minColumn, int maxColumn, - int offsetColumn, ConvolutionPassType passType) where TPixel : struct, IPixel { switch (passType) { case ConvolutionPassType.Single: - ConvolveSinglePass(matrix, sourcePixels, ref targetRowRef, row, column, minRow, maxRow, maxColumn, offsetColumn); + ConvolveSinglePass(matrix, sourcePixels, ref targetRowRef, row, column, minRow, maxRow, minColumn, maxColumn); break; case ConvolutionPassType.First: - ConvolveFirstPass(matrix, sourcePixels, ref targetRowRef, row, column, minRow, maxRow, maxColumn, offsetColumn); + ConvolveFirstPass(matrix, sourcePixels, ref targetRowRef, row, column, minRow, maxRow, minColumn, maxColumn); break; case ConvolutionPassType.Second: - ConvolveSecondPass(matrix, sourcePixels, ref targetRowRef, row, column, minRow, maxRow, maxColumn, offsetColumn); + ConvolveSecondPass(matrix, sourcePixels, ref targetRowRef, row, column, minRow, maxRow, minColumn, maxColumn); break; } } @@ -122,8 +122,8 @@ private static void ConvolveSinglePass( int column, int minRow, int maxRow, - int maxColumn, - int offsetColumn) + int minColumn, + int maxColumn) where TPixel : struct, IPixel { Vector4 vector = default; @@ -131,7 +131,7 @@ private static void ConvolveSinglePass( int matrixWidth = matrix.Columns; int radiusY = matrixHeight >> 1; int radiusX = matrixWidth >> 1; - int sourceOffsetColumnBase = column + offsetColumn; + int sourceOffsetColumnBase = column + minColumn; for (int y = 0; y < matrixHeight; y++) { @@ -140,7 +140,7 @@ private static void ConvolveSinglePass( 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); @@ -162,8 +162,8 @@ private static void ConvolveFirstPass( int column, int minRow, int maxRow, - int maxColumn, - int offsetColumn) + int minColumn, + int maxColumn) where TPixel : struct, IPixel { Vector4 vector = default; @@ -171,7 +171,7 @@ private static void ConvolveFirstPass( int matrixWidth = matrix.Columns; int radiusY = matrixHeight >> 1; int radiusX = matrixWidth >> 1; - int sourceOffsetColumnBase = column + offsetColumn; + int sourceOffsetColumnBase = column + minColumn; for (int y = 0; y < matrixHeight; y++) { @@ -180,7 +180,7 @@ private static void ConvolveFirstPass( 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); @@ -188,6 +188,7 @@ private static void ConvolveFirstPass( } } + // No need to unpremultiply. Handled in second pass. Unsafe.Add(ref targetRowRef, column) = vector; } @@ -199,8 +200,8 @@ private static void ConvolveSecondPass( int column, int minRow, int maxRow, - int maxColumn, - int offsetColumn) + int minColumn, + int maxColumn) where TPixel : struct, IPixel { Vector4 vector = default; @@ -208,7 +209,7 @@ private static void ConvolveSecondPass( int matrixWidth = matrix.Columns; int radiusY = matrixHeight >> 1; int radiusX = matrixWidth >> 1; - int sourceOffsetColumnBase = column + offsetColumn; + int sourceOffsetColumnBase = column + minColumn; for (int y = 0; y < matrixHeight; y++) { @@ -217,7 +218,9 @@ private static void ConvolveSecondPass( for (int x = 0; x < matrixWidth; x++) { - int offsetX = (sourceOffsetColumnBase + x - radiusX).Clamp(offsetColumn, maxColumn); + int offsetX = (sourceOffsetColumnBase + x - radiusX).Clamp(minColumn, maxColumn); + + // No need to premultiply. Handled in first pass. var currentColor = sourceRowSpan[offsetX].ToVector4(); vector += matrix[y, x] * currentColor; } diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs index 6c85e818fe..f6780b0db9 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs @@ -90,8 +90,8 @@ protected override void OnFrameApply( x, startY, maxY, - maxX, - startX); + startX, + maxX); } PixelOperations.Instance.FromVector4(configuration, vectorSpan, targetRowSpan); diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs index 3019d8651d..1f750e84e9 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs @@ -110,8 +110,8 @@ private void ApplyConvolution( x, startY, maxY, - maxX, startX, + maxX, passType); } diff --git a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs index f6a4e63999..963ca23d4e 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs @@ -73,8 +73,8 @@ protected override void OnFrameApply(ImageFrame source, Rectangle source x, startY, maxY, - maxX, startX, + maxX, ConvolutionPassType.Single); } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Convolution/DetectEdgesTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Convolution/DetectEdgesTest.cs index de72f6d09e..edb24d6f10 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Convolution/DetectEdgesTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Convolution/DetectEdgesTest.cs @@ -42,6 +42,7 @@ public void DetectEdges_WorksOnWrappedMemoryImage(TestImageProvider Date: Wed, 17 Apr 2019 09:54:08 +1000 Subject: [PATCH 09/11] Correctly handle alpha component. --- .../Common/Helpers/ConvolutionPassType.cs | 26 ---- .../Common/Helpers/DenseMatrixUtils.cs | 117 ++---------------- .../Convolution/BoxBlurProcessor.cs | 3 +- .../Convolution/Convolution2DProcessor.cs | 13 +- .../Convolution/Convolution2PassProcessor.cs | 30 +++-- .../Convolution/ConvolutionProcessor.cs | 16 ++- .../Convolution/EdgeDetector2DProcessor.cs | 3 +- .../EdgeDetectorCompassProcessor.cs | 20 +-- .../Convolution/EdgeDetectorProcessor.cs | 4 +- .../Convolution/GaussianBlurProcessor.cs | 2 +- .../Convolution/GaussianSharpenProcessor.cs | 2 +- .../Processors/Convolution/DetectEdgesTest.cs | 2 +- 12 files changed, 70 insertions(+), 168 deletions(-) delete mode 100644 src/ImageSharp/Common/Helpers/ConvolutionPassType.cs diff --git a/src/ImageSharp/Common/Helpers/ConvolutionPassType.cs b/src/ImageSharp/Common/Helpers/ConvolutionPassType.cs deleted file mode 100644 index 88bc6ec81a..0000000000 --- a/src/ImageSharp/Common/Helpers/ConvolutionPassType.cs +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp -{ - /// - /// Enumerates the different convolution pass types. - /// - internal enum ConvolutionPassType - { - /// - /// A single pass. - /// - Single, - - /// - /// The first pass of a two pass convolution. - /// - First, - - /// - /// The second pass of a two pass convolution. - /// - Second - } -} diff --git a/src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs b/src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs index 2c0efbaead..f75045a209 100644 --- a/src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs +++ b/src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs @@ -29,6 +29,7 @@ internal static class DenseMatrixUtils /// The maximum working area row. /// The minimum working area column. /// The maximum working area column. + /// Whether the convolution filter is applied to alpha as well as the color channels. public static void Convolve2D( in DenseMatrix matrixY, in DenseMatrix matrixX, @@ -39,7 +40,8 @@ public static void Convolve2D( int minRow, int maxRow, int minColumn, - int maxColumn) + int maxColumn, + bool preserveAlpha) where TPixel : struct, IPixel { Vector4 vectorY = default; @@ -68,7 +70,12 @@ public static void Convolve2D( var vector = Vector4.SquareRoot((vectorX * vectorX) + (vectorY * vectorY)); ref Vector4 target = ref Unsafe.Add(ref targetRowRef, column); - vector.W = target.W; + + if (preserveAlpha) + { + vector.W = target.W; + } + Vector4Utils.UnPremultiply(ref vector); target = vector; } @@ -86,7 +93,7 @@ public static void Convolve2D( /// The maximum working area row. /// The minimum working area column. /// The maximum working area column. - /// The convolution pass type. + /// Whether the convolution filter is applied to alpha as well as the color channels. public static void Convolve( in DenseMatrix matrix, Buffer2D sourcePixels, @@ -97,33 +104,7 @@ public static void Convolve( int maxRow, int minColumn, int maxColumn, - ConvolutionPassType passType) - where TPixel : struct, IPixel - { - switch (passType) - { - case ConvolutionPassType.Single: - ConvolveSinglePass(matrix, sourcePixels, ref targetRowRef, row, column, minRow, maxRow, minColumn, maxColumn); - break; - case ConvolutionPassType.First: - ConvolveFirstPass(matrix, sourcePixels, ref targetRowRef, row, column, minRow, maxRow, minColumn, maxColumn); - break; - case ConvolutionPassType.Second: - ConvolveSecondPass(matrix, sourcePixels, ref targetRowRef, row, column, minRow, maxRow, minColumn, maxColumn); - break; - } - } - - private static void ConvolveSinglePass( - in DenseMatrix matrix, - Buffer2D sourcePixels, - ref Vector4 targetRowRef, - int row, - int column, - int minRow, - int maxRow, - int minColumn, - int maxColumn) + bool preserveAlpha) where TPixel : struct, IPixel { Vector4 vector = default; @@ -149,84 +130,12 @@ private static void ConvolveSinglePass( } ref Vector4 target = ref Unsafe.Add(ref targetRowRef, column); - vector.W = target.W; - Vector4Utils.UnPremultiply(ref vector); - target = vector; - } - private static void ConvolveFirstPass( - 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; - 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++) + if (preserveAlpha) { - 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 need to unpremultiply. Handled in second pass. - Unsafe.Add(ref targetRowRef, column) = vector; - } - - private static void ConvolveSecondPass( - 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; - 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); - - // No need to premultiply. Handled in first pass. - var currentColor = sourceRowSpan[offsetX].ToVector4(); - vector += matrix[y, x] * currentColor; - } + vector.W = target.W; } - ref Vector4 target = ref Unsafe.Add(ref targetRowRef, column); Vector4Utils.UnPremultiply(ref vector); target = vector; } diff --git a/src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor.cs index 644d6c9e17..3d5bdc42a7 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor.cs @@ -49,7 +49,8 @@ public BoxBlurProcessor(int radius = 7) 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 9aa8f30326..2890c9fc4c 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs @@ -24,11 +24,13 @@ internal class Convolution2DProcessor : ImageProcessor /// /// The horizontal gradient operator. /// The vertical gradient operator. - public Convolution2DProcessor(in DenseMatrix kernelX, in 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; } /// @@ -41,6 +43,11 @@ public Convolution2DProcessor(in DenseMatrix kernelX, in DenseMatrix 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, @@ -49,6 +56,7 @@ protected override void OnFrameApply( { DenseMatrix matrixY = this.KernelY; DenseMatrix matrixX = this.KernelX; + bool preserveAlpha = this.PreserveAlpha; var interest = Rectangle.Intersect(sourceRectangle, source.Bounds()); int startY = interest.Y; @@ -91,7 +99,8 @@ protected override void OnFrameApply( startY, maxY, startX, - maxX); + maxX, + preserveAlpha); } 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 b0f2b49d38..ffbb59970b 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs @@ -24,10 +24,15 @@ internal class Convolution2PassProcessor : ImageProcessor /// /// The horizontal gradient operator. /// The vertical gradient operator. - public Convolution2PassProcessor(in DenseMatrix kernelX, in 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,14 +45,19 @@ public Convolution2PassProcessor(in DenseMatrix kernelX, in DenseMatrix 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())) { var interest = Rectangle.Intersect(sourceRectangle, source.Bounds()); - this.ApplyConvolution(firstPassPixels, source.PixelBuffer, interest, this.KernelX, configuration, ConvolutionPassType.First); - this.ApplyConvolution(source.PixelBuffer, firstPassPixels, interest, this.KernelY, configuration, ConvolutionPassType.Second); + this.ApplyConvolution(firstPassPixels, source.PixelBuffer, interest, this.KernelX, configuration); + this.ApplyConvolution(source.PixelBuffer, firstPassPixels, interest, this.KernelY, configuration); } } @@ -62,16 +72,16 @@ protected override void OnFrameApply(ImageFrame source, Rectangle source /// /// The kernel operator. /// The - /// The convolution pass type. private void ApplyConvolution( Buffer2D targetPixels, Buffer2D sourcePixels, Rectangle sourceRectangle, in DenseMatrix kernel, - Configuration configuration, - ConvolutionPassType passType) + Configuration configuration) { DenseMatrix matrix = kernel; + bool preserveAlpha = this.PreserveAlpha; + int startY = sourceRectangle.Y; int endY = sourceRectangle.Bottom; int startX = sourceRectangle.X; @@ -94,11 +104,7 @@ private void ApplyConvolution( for (int y = rows.Min; y < rows.Max; y++) { Span targetRowSpan = targetPixels.GetRowSpan(y).Slice(startX); - - if (passType == ConvolutionPassType.Second) - { - PixelOperations.Instance.ToVector4(configuration, targetRowSpan.Slice(0, length), vectorSpan); - } + PixelOperations.Instance.ToVector4(configuration, targetRowSpan.Slice(0, length), vectorSpan); for (int x = 0; x < width; x++) { @@ -112,7 +118,7 @@ private void ApplyConvolution( maxY, startX, maxX, - passType); + preserveAlpha); } 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 afcf1bf0cf..03421dd818 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs @@ -23,17 +23,29 @@ internal class ConvolutionProcessor : ImageProcessor /// Initializes a new instance of the class. /// /// The 2d gradient operator. - public ConvolutionProcessor(in 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; @@ -75,7 +87,7 @@ protected override void OnFrameApply(ImageFrame source, Rectangle source maxY, startX, maxX, - ConvolutionPassType.Single); + preserveAlpha); } 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 ef5f892d55..d2e9630dc0 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetector2DProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetector2DProcessor.cs @@ -43,7 +43,8 @@ protected EdgeDetector2DProcessor(in DenseMatrix kernelX, in DenseMatrix< 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 4165cf024e..73f92fae38 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor.cs @@ -5,14 +5,12 @@ 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 @@ internal abstract class EdgeDetectorCompassProcessor : ImageProcessor 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 @@ protected override void OnFrameApply(ImageFrame source, Rectangle source // 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 @@ protected override void OnFrameApply(ImageFrame source, Rectangle source { 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 @@ protected override void OnFrameApply(ImageFrame source, Rectangle source { 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 @@ protected override void OnFrameApply(ImageFrame source, Rectangle source // 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 136f7f69e9..edc7ec4ccd 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor.cs @@ -45,8 +45,6 @@ protected override void BeforeFrameApply(ImageFrame source, Rectangle so /// 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 b3bc15d391..0fc822d57c 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor.cs @@ -77,7 +77,7 @@ public GaussianBlurProcessor(float sigma, int radius) /// 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 786bf7757a..001471720a 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor.cs @@ -79,7 +79,7 @@ public GaussianSharpenProcessor(float sigma, int radius) /// 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/tests/ImageSharp.Tests/Processing/Processors/Convolution/DetectEdgesTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Convolution/DetectEdgesTest.cs index edb24d6f10..c315e21874 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Convolution/DetectEdgesTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Convolution/DetectEdgesTest.cs @@ -12,7 +12,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution { public class DetectEdgesTest : FileTestBase { - private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.0456F); + private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.1F); public static readonly string[] CommonTestImages = { TestImages.Png.Bike }; From c738f188d5f1af28d1da616e0a63ef80fc547698 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 17 Apr 2019 10:51:23 +1000 Subject: [PATCH 10/11] Update tests --- .../Processing/Processors/Convolution/BoxBlurTest.cs | 2 +- .../Processing/Processors/Convolution/DetectEdgesTest.cs | 4 +++- tests/Images/External | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/ImageSharp.Tests/Processing/Processors/Convolution/BoxBlurTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Convolution/BoxBlurTest.cs index 0c40debad1..1f0d12cf7f 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Convolution/BoxBlurTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Convolution/BoxBlurTest.cs @@ -37,7 +37,7 @@ public void ImageShouldApplyBoxBlurFilterInBox(TestImageProvider 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 c315e21874..b6a7741b3c 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Convolution/DetectEdgesTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Convolution/DetectEdgesTest.cs @@ -12,7 +12,9 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution { public class DetectEdgesTest : FileTestBase { - private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.1F); + // 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 }; diff --git a/tests/Images/External b/tests/Images/External index 802725dec2..b615960d1b 160000 --- a/tests/Images/External +++ b/tests/Images/External @@ -1 +1 @@ -Subproject commit 802725dec2a6b1ca02f9e2f9a4c3f625583d0696 +Subproject commit b615960d1b5e0e21ee115c2e0794e8e3c96da2f1 From 4a58d3a3d79a7ba8952f6c08fc9a84904ce53ff7 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 23 Apr 2019 17:35:11 +1000 Subject: [PATCH 11/11] Use dedicated methods over branching. --- .../Common/Helpers/DenseMatrixUtils.cs | 189 +++++++++++++++--- .../Convolution/Convolution2DProcessor.cs | 45 +++-- .../Convolution/Convolution2PassProcessor.cs | 42 ++-- .../Convolution/ConvolutionProcessor.cs | 42 ++-- 4 files changed, 257 insertions(+), 61 deletions(-) diff --git a/src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs b/src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs index f75045a209..427b240057 100644 --- a/src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs +++ b/src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs @@ -12,11 +12,13 @@ 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 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. @@ -29,8 +31,8 @@ internal static class DenseMatrixUtils /// The maximum working area row. /// The minimum working area column. /// The maximum working area column. - /// Whether the convolution filter is applied to alpha as well as the color channels. - public static void Convolve2D( + [MethodImpl(InliningOptions.ShortMethod)] + public static void Convolve2D3( in DenseMatrix matrixY, in DenseMatrix matrixX, Buffer2D sourcePixels, @@ -40,8 +42,90 @@ public static void Convolve2D( 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); + vector.W = target.W; + + Vector4Utils.UnPremultiply(ref vector); + target = vector; + } + + /// + /// 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 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, + Buffer2D sourcePixels, + 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, - bool preserveAlpha) + ref Vector4 vector) where TPixel : struct, IPixel { Vector4 vectorY = default; @@ -68,13 +152,51 @@ public static void Convolve2D( } } - var vector = Vector4.SquareRoot((vectorX * vectorX) + (vectorY * vectorY)); - ref Vector4 target = ref Unsafe.Add(ref targetRowRef, column); + vector = Vector4.SquareRoot((vectorX * vectorX) + (vectorY * vectorY)); + } - if (preserveAlpha) - { - vector.W = target.W; - } + /// + /// 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; @@ -82,6 +204,7 @@ public static void Convolve2D( /// /// 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. @@ -93,8 +216,8 @@ public static void Convolve2D( /// The maximum working area row. /// The minimum working area column. /// The maximum working area column. - /// Whether the convolution filter is applied to alpha as well as the color channels. - public static void Convolve( + [MethodImpl(InliningOptions.ShortMethod)] + public static void Convolve4( in DenseMatrix matrix, Buffer2D sourcePixels, ref Vector4 targetRowRef, @@ -103,11 +226,40 @@ public static void Convolve( int minRow, int maxRow, int minColumn, - int maxColumn, - bool preserveAlpha) + 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; @@ -124,20 +276,9 @@ public static void Convolve( int offsetX = (sourceOffsetColumnBase + x - radiusX).Clamp(minColumn, maxColumn); var currentColor = sourceRowSpan[offsetX].ToVector4(); Vector4Utils.Premultiply(ref currentColor); - vector += matrix[y, x] * currentColor; } } - - ref Vector4 target = ref Unsafe.Add(ref targetRowRef, column); - - if (preserveAlpha) - { - vector.W = target.W; - } - - Vector4Utils.UnPremultiply(ref vector); - target = vector; } } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs index 2890c9fc4c..633b50a9b7 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs @@ -87,20 +87,39 @@ protected override void OnFrameApply( 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) { - DenseMatrixUtils.Convolve2D( - in matrixY, - in matrixX, - source.PixelBuffer, - ref vectorSpanRef, - y, - x, - startY, - maxY, - startX, - maxX, - 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 + { + 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, targetRowSpan); diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs index ffbb59970b..03268c9dda 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs @@ -106,19 +106,37 @@ private void ApplyConvolution( 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) { - DenseMatrixUtils.Convolve( - in matrix, - sourcePixels, - ref vectorSpanRef, - y, - x, - startY, - maxY, - startX, - maxX, - preserveAlpha); + for (int x = 0; x < width; x++) + { + DenseMatrixUtils.Convolve3( + in matrix, + sourcePixels, + ref vectorSpanRef, + y, + x, + startY, + maxY, + startX, + maxX); + } + } + else + { + 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, targetRowSpan); diff --git a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs index 03421dd818..6c3b9a46f5 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs @@ -75,19 +75,37 @@ protected override void OnFrameApply(ImageFrame source, Rectangle source 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) { - DenseMatrixUtils.Convolve( - in matrix, - source.PixelBuffer, - ref vectorSpanRef, - y, - x, - startY, - maxY, - startX, - maxX, - preserveAlpha); + for (int x = 0; x < width; x++) + { + DenseMatrixUtils.Convolve3( + in matrix, + source.PixelBuffer, + ref vectorSpanRef, + y, + x, + startY, + maxY, + startX, + maxX); + } + } + else + { + 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, targetRowSpan);