From b69405f534e129159f8a69f5ecb57906904900de Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 22 Aug 2024 22:18:58 +1000 Subject: [PATCH 1/3] Add convolution API --- .../Convolution/BoxBlurExtensions.cs | 12 +-- .../Convolution/ConvolutionExtensions.cs | 89 +++++++++++++++++++ .../Convolution/GaussianBlurExtensions.cs | 2 +- .../Convolution2DProcessor{TPixel}.cs | 6 +- .../Convolution2PassProcessor{TPixel}.cs | 54 ++++++++--- .../Convolution/ConvolutionProcessor.cs | 79 ++++++++++++++++ .../ConvolutionProcessor{TPixel}.cs | 44 +++++++-- .../EdgeDetectorCompassProcessor{TPixel}.cs | 8 +- .../EdgeDetectorProcessor{TPixel}.cs | 2 +- .../GaussianBlurProcessor{TPixel}.cs | 20 +---- .../GaussianSharpenProcessor{TPixel}.cs | 18 +--- .../Convolution/ConvolutionTests.cs | 49 ++++++++++ 12 files changed, 316 insertions(+), 67 deletions(-) create mode 100644 src/ImageSharp/Processing/Extensions/Convolution/ConvolutionExtensions.cs create mode 100644 src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs create mode 100644 tests/ImageSharp.Tests/Processing/Processors/Convolution/ConvolutionTests.cs diff --git a/src/ImageSharp/Processing/Extensions/Convolution/BoxBlurExtensions.cs b/src/ImageSharp/Processing/Extensions/Convolution/BoxBlurExtensions.cs index 6611af742b..11a7fc6a7a 100644 --- a/src/ImageSharp/Processing/Extensions/Convolution/BoxBlurExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/Convolution/BoxBlurExtensions.cs @@ -55,9 +55,11 @@ public static IImageProcessingContext BoxBlur(this IImageProcessingContext sourc /// The to use when mapping the pixels outside of the border, in Y direction. /// /// The . - public static IImageProcessingContext BoxBlur(this IImageProcessingContext source, int radius, Rectangle rectangle, BorderWrappingMode borderWrapModeX, BorderWrappingMode borderWrapModeY) - { - var processor = new BoxBlurProcessor(radius, borderWrapModeX, borderWrapModeY); - return source.ApplyProcessor(processor, rectangle); - } + public static IImageProcessingContext BoxBlur( + this IImageProcessingContext source, + int radius, + Rectangle rectangle, + BorderWrappingMode borderWrapModeX, + BorderWrappingMode borderWrapModeY) + => source.ApplyProcessor(new BoxBlurProcessor(radius, borderWrapModeX, borderWrapModeY), rectangle); } diff --git a/src/ImageSharp/Processing/Extensions/Convolution/ConvolutionExtensions.cs b/src/ImageSharp/Processing/Extensions/Convolution/ConvolutionExtensions.cs new file mode 100644 index 0000000000..2980ff44f0 --- /dev/null +++ b/src/ImageSharp/Processing/Extensions/Convolution/ConvolutionExtensions.cs @@ -0,0 +1,89 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Processing.Processors.Convolution; + +namespace SixLabors.ImageSharp.Processing.Extensions.Convolution; + +/// +/// Defines general convolution extensions to apply on an +/// using Mutate/Clone. +/// +public static class ConvolutionExtensions +{ + /// + /// Applies a convolution filter to the image. + /// + /// The current image processing context. + /// The convolution kernel to apply. + /// The . + public static IImageProcessingContext Convolve(this IImageProcessingContext source, DenseMatrix kernelXY) + => Convolve(source, kernelXY, false); + + /// + /// Applies a convolution filter to the image. + /// + /// The current image processing context. + /// The convolution kernel to apply. + /// Whether the convolution filter is applied to alpha as well as the color channels. + /// The . + public static IImageProcessingContext Convolve(this IImageProcessingContext source, DenseMatrix kernelXY, bool preserveAlpha) + => Convolve(source, kernelXY, preserveAlpha, BorderWrappingMode.Repeat, BorderWrappingMode.Repeat); + + /// + /// Applies a convolution filter to the image. + /// + /// The current image processing context. + /// The convolution kernel to apply. + /// Whether the convolution filter is applied to alpha as well as the color channels. + /// The to use when mapping the pixels outside of the border, in X direction. + /// The to use when mapping the pixels outside of the border, in Y direction. + /// The . + public static IImageProcessingContext Convolve( + this IImageProcessingContext source, + DenseMatrix kernelXY, + bool preserveAlpha, + BorderWrappingMode borderWrapModeX, + BorderWrappingMode borderWrapModeY) + => source.ApplyProcessor(new ConvolutionProcessor(kernelXY, preserveAlpha, borderWrapModeX, borderWrapModeY)); + + /// + /// Applies a convolution filter to the image. + /// + /// The current image processing context. + /// The rectangle structure that specifies the portion of the image object to alter. + /// The convolution kernel to apply. + /// The . + public static IImageProcessingContext Convolve(this IImageProcessingContext source, Rectangle rectangle, DenseMatrix kernelXY) + => Convolve(source, rectangle, kernelXY, false); + + /// + /// Applies a convolution filter to the image. + /// + /// The current image processing context. + /// The rectangle structure that specifies the portion of the image object to alter. + /// The convolution kernel to apply. + /// Whether the convolution filter is applied to alpha as well as the color channels. + /// The . + public static IImageProcessingContext Convolve(this IImageProcessingContext source, Rectangle rectangle, DenseMatrix kernelXY, bool preserveAlpha) + => Convolve(source, rectangle, kernelXY, preserveAlpha, BorderWrappingMode.Repeat, BorderWrappingMode.Repeat); + + /// + /// Applies a convolution filter to the image. + /// + /// The current image processing context. + /// The rectangle structure that specifies the portion of the image object to alter. + /// The convolution kernel to apply. + /// Whether the convolution filter is applied to alpha as well as the color channels. + /// The to use when mapping the pixels outside of the border, in X direction. + /// The to use when mapping the pixels outside of the border, in Y direction. + /// The . + public static IImageProcessingContext Convolve( + this IImageProcessingContext source, + Rectangle rectangle, + DenseMatrix kernelXY, + bool preserveAlpha, + BorderWrappingMode borderWrapModeX, + BorderWrappingMode borderWrapModeY) + => source.ApplyProcessor(new ConvolutionProcessor(kernelXY, preserveAlpha, borderWrapModeX, borderWrapModeY), rectangle); +} diff --git a/src/ImageSharp/Processing/Extensions/Convolution/GaussianBlurExtensions.cs b/src/ImageSharp/Processing/Extensions/Convolution/GaussianBlurExtensions.cs index b851482008..64e0be3eac 100644 --- a/src/ImageSharp/Processing/Extensions/Convolution/GaussianBlurExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/Convolution/GaussianBlurExtensions.cs @@ -57,7 +57,7 @@ public static IImageProcessingContext GaussianBlur(this IImageProcessingContext /// The . public static IImageProcessingContext GaussianBlur(this IImageProcessingContext source, float sigma, Rectangle rectangle, BorderWrappingMode borderWrapModeX, BorderWrappingMode borderWrapModeY) { - var processor = new GaussianBlurProcessor(sigma, borderWrapModeX, borderWrapModeY); + GaussianBlurProcessor processor = new(sigma, borderWrapModeX, borderWrapModeY); return source.ApplyProcessor(processor, rectangle); } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs index 8a7c424815..8f5ddd1690 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs @@ -62,14 +62,14 @@ protected override void OnFrameApply(ImageFrame source) source.CopyTo(targetPixels); - var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); + Rectangle interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); - using (var map = new KernelSamplingMap(allocator)) + using (KernelSamplingMap map = new(allocator)) { // Since the kernel sizes are identical we can use a single map. map.BuildSamplingOffsetMap(this.KernelY, interest); - var operation = new Convolution2DRowOperation( + Convolution2DRowOperation operation = new( interest, targetPixels, source.PixelBuffer, diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs index 10780a21e2..cdd8ff8ae6 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs @@ -35,18 +35,48 @@ public Convolution2PassProcessor( Rectangle sourceRectangle, BorderWrappingMode borderWrapModeX, BorderWrappingMode borderWrapModeY) + : this(configuration, kernel, kernel, preserveAlpha, source, sourceRectangle, borderWrapModeX, borderWrapModeY) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The configuration which allows altering default behaviour or extending the library. + /// The 1D convolution kernel. X Direction + /// The 1D convolution kernel. Y Direction + /// Whether the convolution filter is applied to alpha as well as the color channels. + /// The source for the current processor instance. + /// The source area to process for the current processor instance. + /// The to use when mapping the pixels outside of the border, in X direction. + /// The to use when mapping the pixels outside of the border, in Y direction. + public Convolution2PassProcessor( + Configuration configuration, + float[] kernelX, + float[] kernelY, + bool preserveAlpha, + Image source, + Rectangle sourceRectangle, + BorderWrappingMode borderWrapModeX, + BorderWrappingMode borderWrapModeY) : base(configuration, source, sourceRectangle) { - this.Kernel = kernel; + this.KernelX = kernelX; + this.KernelY = kernelY; this.PreserveAlpha = preserveAlpha; this.BorderWrapModeX = borderWrapModeX; this.BorderWrapModeY = borderWrapModeY; } /// - /// Gets the convolution kernel. + /// Gets the convolution kernel. X direction. + /// + public float[] KernelX { get; } + + /// + /// Gets the convolution kernel. Y direction. /// - public float[] Kernel { get; } + public float[] KernelY { get; } /// /// Gets a value indicating whether the convolution filter is applied to alpha as well as the color channels. @@ -68,21 +98,21 @@ protected override void OnFrameApply(ImageFrame source) { using Buffer2D firstPassPixels = this.Configuration.MemoryAllocator.Allocate2D(source.Size); - var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); + Rectangle interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); // We can create a single sampling map with the size as if we were using the non separated 2D kernel // the two 1D kernels represent, and reuse it across both convolution steps, like in the bokeh blur. - using var mapXY = new KernelSamplingMap(this.Configuration.MemoryAllocator); + using KernelSamplingMap mapXY = new(this.Configuration.MemoryAllocator); - mapXY.BuildSamplingOffsetMap(this.Kernel.Length, this.Kernel.Length, interest, this.BorderWrapModeX, this.BorderWrapModeY); + mapXY.BuildSamplingOffsetMap(this.KernelX.Length, this.KernelX.Length, interest, this.BorderWrapModeX, this.BorderWrapModeY); // Horizontal convolution - var horizontalOperation = new HorizontalConvolutionRowOperation( + HorizontalConvolutionRowOperation horizontalOperation = new( interest, firstPassPixels, source.PixelBuffer, mapXY, - this.Kernel, + this.KernelX, this.Configuration, this.PreserveAlpha); @@ -92,12 +122,12 @@ protected override void OnFrameApply(ImageFrame source) in horizontalOperation); // Vertical convolution - var verticalOperation = new VerticalConvolutionRowOperation( + VerticalConvolutionRowOperation verticalOperation = new( interest, source.PixelBuffer, firstPassPixels, mapXY, - this.Kernel, + this.KernelY, this.Configuration, this.PreserveAlpha); @@ -140,7 +170,7 @@ public HorizontalConvolutionRowOperation( } /// - [MethodImpl(InliningOptions.ShortMethod)] + [MethodImpl(MethodImplOptions.AggressiveInlining)] public int GetRequiredBufferLength(Rectangle bounds) => 2 * bounds.Width; @@ -306,7 +336,7 @@ public VerticalConvolutionRowOperation( } /// - [MethodImpl(InliningOptions.ShortMethod)] + [MethodImpl(MethodImplOptions.AggressiveInlining)] public int GetRequiredBufferLength(Rectangle bounds) => 2 * bounds.Width; diff --git a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs new file mode 100644 index 0000000000..995a5164d9 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs @@ -0,0 +1,79 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Processing.Processors.Convolution; + +/// +/// Defines a processor that uses a 2 dimensional matrix to perform convolution against an image. +/// +public class ConvolutionProcessor : IImageProcessor +{ + /// + /// Initializes a new instance of the class. + /// + /// The 2d gradient operator. + /// Whether the convolution filter is applied to alpha as well as the color channels. + /// The to use when mapping the pixels outside of the border, in X direction. + /// The to use when mapping the pixels outside of the border, in Y direction. + public ConvolutionProcessor( + in DenseMatrix kernelXY, + bool preserveAlpha, + BorderWrappingMode borderWrapModeX, + BorderWrappingMode borderWrapModeY) + { + this.KernelXY = kernelXY; + this.PreserveAlpha = preserveAlpha; + this.BorderWrapModeX = borderWrapModeX; + this.BorderWrapModeY = borderWrapModeY; + } + + /// + /// Gets the 2d convolution kernel. + /// + 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; } + + /// + /// Gets the to use when mapping the pixels outside of the border, in X direction. + /// + public BorderWrappingMode BorderWrapModeX { get; } + + /// + /// Gets the to use when mapping the pixels outside of the border, in Y direction. + /// + public BorderWrappingMode BorderWrapModeY { get; } + + /// + public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + where TPixel : unmanaged, + IPixel + { + if (this.KernelXY.TryGetLinearlySeparableComponents(out float[]? kernelX, out float[]? kernelY)) + { + return new Convolution2PassProcessor( + configuration, + kernelX, + kernelY, + this.PreserveAlpha, + source, + sourceRectangle, + this.BorderWrapModeX, + this.BorderWrapModeY); + } + + return new ConvolutionProcessor( + configuration, + this.KernelXY, + this.PreserveAlpha, + source, + sourceRectangle, + this.BorderWrapModeX, + this.BorderWrapModeY); + } +} diff --git a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs index ae79f2c31d..9b4659929b 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs @@ -31,10 +31,34 @@ public ConvolutionProcessor( bool preserveAlpha, Image source, Rectangle sourceRectangle) + : this(configuration, kernelXY, preserveAlpha, source, sourceRectangle, BorderWrappingMode.Repeat, BorderWrappingMode.Repeat) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The configuration which allows altering default behaviour or extending the library. + /// The 2d gradient operator. + /// Whether the convolution filter is applied to alpha as well as the color channels. + /// The source for the current processor instance. + /// The source area to process for the current processor instance. + /// The to use when mapping the pixels outside of the border, in X direction. + /// The to use when mapping the pixels outside of the border, in Y direction. + public ConvolutionProcessor( + Configuration configuration, + in DenseMatrix kernelXY, + bool preserveAlpha, + Image source, + Rectangle sourceRectangle, + BorderWrappingMode borderWrapModeX, + BorderWrappingMode borderWrapModeY) : base(configuration, source, sourceRectangle) { this.KernelXY = kernelXY; this.PreserveAlpha = preserveAlpha; + this.BorderWrapModeX = borderWrapModeX; + this.BorderWrapModeY = borderWrapModeY; } /// @@ -47,6 +71,16 @@ public ConvolutionProcessor( /// public bool PreserveAlpha { get; } + /// + /// Gets the to use when mapping the pixels outside of the border, in X direction. + /// + public BorderWrappingMode BorderWrapModeX { get; } + + /// + /// Gets the to use when mapping the pixels outside of the border, in Y direction. + /// + public BorderWrappingMode BorderWrapModeY { get; } + /// protected override void OnFrameApply(ImageFrame source) { @@ -55,13 +89,13 @@ protected override void OnFrameApply(ImageFrame source) source.CopyTo(targetPixels); - var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); + Rectangle interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); - using (var map = new KernelSamplingMap(allocator)) + using (KernelSamplingMap map = new(allocator)) { - map.BuildSamplingOffsetMap(this.KernelXY, interest); + map.BuildSamplingOffsetMap(this.KernelXY.Rows, this.KernelXY.Columns, interest, this.BorderWrapModeX, this.BorderWrapModeY); - var operation = new RowOperation(interest, targetPixels, source.PixelBuffer, map, this.KernelXY, this.Configuration, this.PreserveAlpha); + RowOperation operation = new(interest, targetPixels, source.PixelBuffer, map, this.KernelXY, this.Configuration, this.PreserveAlpha); ParallelRowIterator.IterateRows( this.Configuration, interest, @@ -121,7 +155,7 @@ public void Invoke(int y, Span span) ref Vector4 targetRowRef = ref MemoryMarshal.GetReference(span); Span targetRowSpan = this.targetPixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); - var state = new ConvolutionState(in this.kernel, this.map); + ConvolutionState state = new(in this.kernel, this.map); int row = y - this.bounds.Y; ref int sampleRowBase = ref state.GetSampleRow((uint)row); diff --git a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs index ae891f3507..a1fa4db973 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs @@ -58,12 +58,12 @@ protected override void BeforeImageApply() /// protected override void OnFrameApply(ImageFrame source) { - var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); + Rectangle interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); // We need a clean copy for each pass to start from using ImageFrame cleanCopy = source.Clone(); - using (var processor = new ConvolutionProcessor(this.Configuration, in this.kernels[0], true, this.Source, interest)) + using (ConvolutionProcessor processor = new(this.Configuration, in this.kernels[0], true, this.Source, interest)) { processor.Apply(source); } @@ -78,12 +78,12 @@ protected override void OnFrameApply(ImageFrame source) { using ImageFrame pass = cleanCopy.Clone(); - using (var processor = new ConvolutionProcessor(this.Configuration, in this.kernels[i], true, this.Source, interest)) + using (ConvolutionProcessor processor = new(this.Configuration, in this.kernels[i], true, this.Source, interest)) { processor.Apply(pass); } - var operation = new RowOperation(source.PixelBuffer, pass.PixelBuffer, interest); + RowOperation operation = new(source.PixelBuffer, pass.PixelBuffer, interest); ParallelRowIterator.IterateRows( this.Configuration, interest, diff --git a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor{TPixel}.cs index c353f46b5f..3139d24bb4 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor{TPixel}.cs @@ -53,7 +53,7 @@ protected override void BeforeImageApply() /// protected override void OnFrameApply(ImageFrame source) { - using var processor = new ConvolutionProcessor(this.Configuration, in this.kernelXY, true, this.Source, this.SourceRectangle); + using ConvolutionProcessor processor = new(this.Configuration, in this.kernelXY, true, this.Source, this.SourceRectangle); processor.Apply(source); } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor{TPixel}.cs index 6518375b9e..d762dd336b 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor{TPixel}.cs @@ -12,24 +12,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution; internal class GaussianBlurProcessor : ImageProcessor where TPixel : unmanaged, IPixel { - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The defining the processor parameters. - /// The source for the current processor instance. - /// The source area to process for the current processor instance. - public GaussianBlurProcessor( - Configuration configuration, - GaussianBlurProcessor definition, - Image source, - Rectangle sourceRectangle) - : base(configuration, source, sourceRectangle) - { - int kernelSize = (definition.Radius * 2) + 1; - this.Kernel = ConvolutionProcessorHelpers.CreateGaussianBlurKernel(kernelSize, definition.Sigma); - } - /// /// Initializes a new instance of the class. /// @@ -72,7 +54,7 @@ public GaussianBlurProcessor( /// protected override void OnFrameApply(ImageFrame source) { - using var processor = new Convolution2PassProcessor(this.Configuration, this.Kernel, false, this.Source, this.SourceRectangle, this.BorderWrapModeX, this.BorderWrapModeY); + using Convolution2PassProcessor processor = new(this.Configuration, this.Kernel, false, this.Source, this.SourceRectangle, this.BorderWrapModeX, this.BorderWrapModeY); processor.Apply(source); } diff --git a/src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor{TPixel}.cs index a286201dff..bdb3a4b380 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor{TPixel}.cs @@ -12,22 +12,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution; internal class GaussianSharpenProcessor : ImageProcessor where TPixel : unmanaged, IPixel { - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The defining the processor parameters. - /// The source for the current processor instance. - /// The source area to process for the current processor instance. - public GaussianSharpenProcessor( - Configuration configuration, - GaussianSharpenProcessor definition, - Image source, - Rectangle sourceRectangle) - : this(configuration, definition, source, sourceRectangle, BorderWrappingMode.Repeat, BorderWrappingMode.Repeat) - { - } - /// /// Initializes a new instance of the class. /// @@ -70,7 +54,7 @@ public GaussianSharpenProcessor( /// protected override void OnFrameApply(ImageFrame source) { - using var processor = new Convolution2PassProcessor(this.Configuration, this.Kernel, false, this.Source, this.SourceRectangle, this.BorderWrapModeX, this.BorderWrapModeY); + using Convolution2PassProcessor processor = new(this.Configuration, this.Kernel, false, this.Source, this.SourceRectangle, this.BorderWrapModeX, this.BorderWrapModeY); processor.Apply(source); } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Convolution/ConvolutionTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Convolution/ConvolutionTests.cs new file mode 100644 index 0000000000..46e9ede2ad --- /dev/null +++ b/tests/ImageSharp.Tests/Processing/Processors/Convolution/ConvolutionTests.cs @@ -0,0 +1,49 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Extensions.Convolution; +using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; + +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution; + +public class ConvolutionTests +{ + private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.05F); + + public static readonly TheoryData> Values = new TheoryData> + { + // Sharpening kernel. + new float[,] + { + { -1, -1, -1 }, + { -1, 16, -1 }, + { -1, -1, -1 } + } + }; + + public static readonly string[] InputImages = + [ + TestImages.Bmp.Car, + TestImages.Png.CalliphoraPartial, + TestImages.Png.Blur + ]; + + [Theory] + [WithFileCollection(nameof(InputImages), nameof(Values), PixelTypes.Rgba32)] + public void OnFullImage(TestImageProvider provider, DenseMatrix value) + where TPixel : unmanaged, IPixel + => provider.RunValidatingProcessorTest( + x => x.Convolve(value), + string.Join('_', value.Data), + ValidatorComparer); + + [Theory] + [WithFileCollection(nameof(InputImages), nameof(Values), PixelTypes.Rgba32)] + public void InBox(TestImageProvider provider, DenseMatrix value) + where TPixel : unmanaged, IPixel + => provider.RunRectangleConstrainedValidatingProcessorTest( + (x, rect) => x.Convolve(rect, value), + string.Join('_', value.Data), + ValidatorComparer); +} From 065c1f93cc577540d9162f654dc5470ccf1cb6fd Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 23 Aug 2024 22:11:28 +1000 Subject: [PATCH 2/3] Add test output --- .../Processing/Processors/Convolution/ConvolutionTests.cs | 1 + ...Box_Rgba32_CalliphoraPartial_-1_-1_-1_-1_16_-1_-1_-1_-1.png | 3 +++ .../InBox_Rgba32_Car_-1_-1_-1_-1_16_-1_-1_-1_-1.png | 3 +++ .../InBox_Rgba32_blur_-1_-1_-1_-1_16_-1_-1_-1_-1.png | 3 +++ ...age_Rgba32_CalliphoraPartial_-1_-1_-1_-1_16_-1_-1_-1_-1.png | 3 +++ .../OnFullImage_Rgba32_Car_-1_-1_-1_-1_16_-1_-1_-1_-1.png | 3 +++ .../OnFullImage_Rgba32_blur_-1_-1_-1_-1_16_-1_-1_-1_-1.png | 3 +++ 7 files changed, 19 insertions(+) create mode 100644 tests/Images/External/ReferenceOutput/Convolution/ConvolutionTests/InBox_Rgba32_CalliphoraPartial_-1_-1_-1_-1_16_-1_-1_-1_-1.png create mode 100644 tests/Images/External/ReferenceOutput/Convolution/ConvolutionTests/InBox_Rgba32_Car_-1_-1_-1_-1_16_-1_-1_-1_-1.png create mode 100644 tests/Images/External/ReferenceOutput/Convolution/ConvolutionTests/InBox_Rgba32_blur_-1_-1_-1_-1_16_-1_-1_-1_-1.png create mode 100644 tests/Images/External/ReferenceOutput/Convolution/ConvolutionTests/OnFullImage_Rgba32_CalliphoraPartial_-1_-1_-1_-1_16_-1_-1_-1_-1.png create mode 100644 tests/Images/External/ReferenceOutput/Convolution/ConvolutionTests/OnFullImage_Rgba32_Car_-1_-1_-1_-1_16_-1_-1_-1_-1.png create mode 100644 tests/Images/External/ReferenceOutput/Convolution/ConvolutionTests/OnFullImage_Rgba32_blur_-1_-1_-1_-1_16_-1_-1_-1_-1.png diff --git a/tests/ImageSharp.Tests/Processing/Processors/Convolution/ConvolutionTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Convolution/ConvolutionTests.cs index 46e9ede2ad..0cb56b732d 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Convolution/ConvolutionTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Convolution/ConvolutionTests.cs @@ -7,6 +7,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution; +[GroupOutput("Convolution")] public class ConvolutionTests { private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.05F); diff --git a/tests/Images/External/ReferenceOutput/Convolution/ConvolutionTests/InBox_Rgba32_CalliphoraPartial_-1_-1_-1_-1_16_-1_-1_-1_-1.png b/tests/Images/External/ReferenceOutput/Convolution/ConvolutionTests/InBox_Rgba32_CalliphoraPartial_-1_-1_-1_-1_16_-1_-1_-1_-1.png new file mode 100644 index 0000000000..ffa8e7cd8a --- /dev/null +++ b/tests/Images/External/ReferenceOutput/Convolution/ConvolutionTests/InBox_Rgba32_CalliphoraPartial_-1_-1_-1_-1_16_-1_-1_-1_-1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b311f117167e17b4611d525aef57146a6757db08f2a36fa093f45f237df9e1a2 +size 326809 diff --git a/tests/Images/External/ReferenceOutput/Convolution/ConvolutionTests/InBox_Rgba32_Car_-1_-1_-1_-1_16_-1_-1_-1_-1.png b/tests/Images/External/ReferenceOutput/Convolution/ConvolutionTests/InBox_Rgba32_Car_-1_-1_-1_-1_16_-1_-1_-1_-1.png new file mode 100644 index 0000000000..682fd8b49b --- /dev/null +++ b/tests/Images/External/ReferenceOutput/Convolution/ConvolutionTests/InBox_Rgba32_Car_-1_-1_-1_-1_16_-1_-1_-1_-1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e0eabd5f7dd2f258d04d3f1db8ee4d958754541e0009ae183d6e512f408e3201 +size 249497 diff --git a/tests/Images/External/ReferenceOutput/Convolution/ConvolutionTests/InBox_Rgba32_blur_-1_-1_-1_-1_16_-1_-1_-1_-1.png b/tests/Images/External/ReferenceOutput/Convolution/ConvolutionTests/InBox_Rgba32_blur_-1_-1_-1_-1_16_-1_-1_-1_-1.png new file mode 100644 index 0000000000..55d592a60f --- /dev/null +++ b/tests/Images/External/ReferenceOutput/Convolution/ConvolutionTests/InBox_Rgba32_blur_-1_-1_-1_-1_16_-1_-1_-1_-1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:048b4a1679852d34d6f74f01b750461ec9db2d3e7e3aa3d2d89d48e5725aa560 +size 142367 diff --git a/tests/Images/External/ReferenceOutput/Convolution/ConvolutionTests/OnFullImage_Rgba32_CalliphoraPartial_-1_-1_-1_-1_16_-1_-1_-1_-1.png b/tests/Images/External/ReferenceOutput/Convolution/ConvolutionTests/OnFullImage_Rgba32_CalliphoraPartial_-1_-1_-1_-1_16_-1_-1_-1_-1.png new file mode 100644 index 0000000000..f01496ad76 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/Convolution/ConvolutionTests/OnFullImage_Rgba32_CalliphoraPartial_-1_-1_-1_-1_16_-1_-1_-1_-1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a6f63d117433a93e30b9b41f68f676bb53eb1761f3c665f178ef07ec2c9626c3 +size 338527 diff --git a/tests/Images/External/ReferenceOutput/Convolution/ConvolutionTests/OnFullImage_Rgba32_Car_-1_-1_-1_-1_16_-1_-1_-1_-1.png b/tests/Images/External/ReferenceOutput/Convolution/ConvolutionTests/OnFullImage_Rgba32_Car_-1_-1_-1_-1_16_-1_-1_-1_-1.png new file mode 100644 index 0000000000..99e9737b51 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/Convolution/ConvolutionTests/OnFullImage_Rgba32_Car_-1_-1_-1_-1_16_-1_-1_-1_-1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d72a3994fc6dcc461da3af5176d5bf62c95b7abeffcbf5547172377db3b0d9ac +size 287158 diff --git a/tests/Images/External/ReferenceOutput/Convolution/ConvolutionTests/OnFullImage_Rgba32_blur_-1_-1_-1_-1_16_-1_-1_-1_-1.png b/tests/Images/External/ReferenceOutput/Convolution/ConvolutionTests/OnFullImage_Rgba32_blur_-1_-1_-1_-1_16_-1_-1_-1_-1.png new file mode 100644 index 0000000000..ecc09e70ff --- /dev/null +++ b/tests/Images/External/ReferenceOutput/Convolution/ConvolutionTests/OnFullImage_Rgba32_blur_-1_-1_-1_-1_16_-1_-1_-1_-1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b3fd7906f11e87a2b0043cde179317f53e9a2bf02d9e64763be8f9923d60f057 +size 257492 From 3bd1efbbf819083e2ec49ebeddc3ce1b7e943fbc Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 19 Nov 2024 13:29:02 +1000 Subject: [PATCH 3/3] Normalize parameter order. --- .../Convolution/BokehBlurExtensions.cs | 8 +- .../Convolution/BoxBlurExtensions.cs | 4 +- .../Convolution/DetectEdgesExtensions.cs | 124 +++++++----------- .../Convolution/GaussianBlurExtensions.cs | 21 +-- .../Convolution/GaussianSharpenExtensions.cs | 28 ++-- .../Convolution/MedianBlurExtensions.cs | 17 ++- .../Processing/Convolution/DetectEdgesTest.cs | 12 +- .../Convolution/GaussianBlurTest.cs | 8 +- .../Convolution/GaussianSharpenTest.cs | 8 +- .../Processing/Convolution/MedianBlurTest.cs | 6 +- .../Processors/Convolution/BokehBlurTest.cs | 3 +- .../Convolution/GaussianBlurTest.cs | 2 +- .../Convolution/GaussianSharpenTest.cs | 2 +- .../Processors/Convolution/MedianBlurTest.cs | 2 +- 14 files changed, 113 insertions(+), 132 deletions(-) diff --git a/src/ImageSharp/Processing/Extensions/Convolution/BokehBlurExtensions.cs b/src/ImageSharp/Processing/Extensions/Convolution/BokehBlurExtensions.cs index e3e6f13ed6..71252e0bb0 100644 --- a/src/ImageSharp/Processing/Extensions/Convolution/BokehBlurExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/Convolution/BokehBlurExtensions.cs @@ -44,13 +44,13 @@ public static IImageProcessingContext BokehBlur(this IImageProcessingContext sou /// Applies a bokeh blur to the image. /// /// The current image processing context. - /// The 'radius' value representing the size of the area to sample. - /// The 'components' value representing the number of kernels to use to approximate the bokeh effect. - /// The gamma highlight factor to use to emphasize bright spots in the source image /// /// The structure that specifies the portion of the image object to alter. /// + /// The 'radius' value representing the size of the area to sample. + /// The 'components' value representing the number of kernels to use to approximate the bokeh effect. + /// The gamma highlight factor to use to emphasize bright spots in the source image /// The . - public static IImageProcessingContext BokehBlur(this IImageProcessingContext source, int radius, int components, float gamma, Rectangle rectangle) + public static IImageProcessingContext BokehBlur(this IImageProcessingContext source, Rectangle rectangle, int radius, int components, float gamma) => source.ApplyProcessor(new BokehBlurProcessor(radius, components, gamma), rectangle); } diff --git a/src/ImageSharp/Processing/Extensions/Convolution/BoxBlurExtensions.cs b/src/ImageSharp/Processing/Extensions/Convolution/BoxBlurExtensions.cs index 11a7fc6a7a..73e40b57aa 100644 --- a/src/ImageSharp/Processing/Extensions/Convolution/BoxBlurExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/Convolution/BoxBlurExtensions.cs @@ -44,10 +44,10 @@ public static IImageProcessingContext BoxBlur(this IImageProcessingContext sourc /// Applies a box blur to the image. /// /// The current image processing context. - /// The 'radius' value representing the size of the area to sample. /// /// The structure that specifies the portion of the image object to alter. /// + /// The 'radius' value representing the size of the area to sample. /// /// The to use when mapping the pixels outside of the border, in X direction. /// @@ -57,8 +57,8 @@ public static IImageProcessingContext BoxBlur(this IImageProcessingContext sourc /// The . public static IImageProcessingContext BoxBlur( this IImageProcessingContext source, - int radius, Rectangle rectangle, + int radius, BorderWrappingMode borderWrapModeX, BorderWrappingMode borderWrapModeY) => source.ApplyProcessor(new BoxBlurProcessor(radius, borderWrapModeX, borderWrapModeY), rectangle); diff --git a/src/ImageSharp/Processing/Extensions/Convolution/DetectEdgesExtensions.cs b/src/ImageSharp/Processing/Extensions/Convolution/DetectEdgesExtensions.cs index b044c3966f..c8fb230559 100644 --- a/src/ImageSharp/Processing/Extensions/Convolution/DetectEdgesExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/Convolution/DetectEdgesExtensions.cs @@ -16,8 +16,8 @@ public static class DetectEdgesExtensions /// /// The current image processing context. /// The . - public static IImageProcessingContext DetectEdges(this IImageProcessingContext source) => - DetectEdges(source, KnownEdgeDetectorKernels.Sobel); + public static IImageProcessingContext DetectEdges(this IImageProcessingContext source) + => DetectEdges(source, KnownEdgeDetectorKernels.Sobel); /// /// Detects any edges within the image. @@ -28,10 +28,8 @@ public static IImageProcessingContext DetectEdges(this IImageProcessingContext s /// The structure that specifies the portion of the image object to alter. /// /// The . - public static IImageProcessingContext DetectEdges( - this IImageProcessingContext source, - Rectangle rectangle) => - DetectEdges(source, KnownEdgeDetectorKernels.Sobel, rectangle); + public static IImageProcessingContext DetectEdges(this IImageProcessingContext source, Rectangle rectangle) + => DetectEdges(source, rectangle, KnownEdgeDetectorKernels.Sobel); /// /// Detects any edges within the image operating in grayscale mode. @@ -39,10 +37,8 @@ public static IImageProcessingContext DetectEdges( /// The current image processing context. /// The 2D edge detector kernel. /// The . - public static IImageProcessingContext DetectEdges( - this IImageProcessingContext source, - EdgeDetector2DKernel kernel) => - DetectEdges(source, kernel, true); + public static IImageProcessingContext DetectEdges(this IImageProcessingContext source, EdgeDetector2DKernel kernel) + => DetectEdges(source, kernel, true); /// /// Detects any edges within the image using a . @@ -57,49 +53,41 @@ public static IImageProcessingContext DetectEdges( this IImageProcessingContext source, EdgeDetector2DKernel kernel, bool grayscale) - { - var processor = new EdgeDetector2DProcessor(kernel, grayscale); - source.ApplyProcessor(processor); - return source; - } + => source.ApplyProcessor(new EdgeDetector2DProcessor(kernel, grayscale)); /// /// Detects any edges within the image operating in grayscale mode. /// /// The current image processing context. - /// The 2D edge detector kernel. /// /// The structure that specifies the portion of the image object to alter. /// + /// The 2D edge detector kernel. /// The . public static IImageProcessingContext DetectEdges( this IImageProcessingContext source, - EdgeDetector2DKernel kernel, - Rectangle rectangle) => - DetectEdges(source, kernel, true, rectangle); + Rectangle rectangle, + EdgeDetector2DKernel kernel) + => DetectEdges(source, rectangle, kernel, true); /// /// Detects any edges within the image using a . /// /// The current image processing context. + /// + /// The structure that specifies the portion of the image object to alter. + /// /// The 2D edge detector kernel. /// /// Whether to convert the image to grayscale before performing edge detection. /// - /// - /// The structure that specifies the portion of the image object to alter. - /// /// The . public static IImageProcessingContext DetectEdges( this IImageProcessingContext source, + Rectangle rectangle, EdgeDetector2DKernel kernel, - bool grayscale, - Rectangle rectangle) - { - var processor = new EdgeDetector2DProcessor(kernel, grayscale); - source.ApplyProcessor(processor, rectangle); - return source; - } + bool grayscale) + => source.ApplyProcessor(new EdgeDetector2DProcessor(kernel, grayscale), rectangle); /// /// Detects any edges within the image operating in grayscale mode. @@ -107,10 +95,8 @@ public static IImageProcessingContext DetectEdges( /// The current image processing context. /// The edge detector kernel. /// The . - public static IImageProcessingContext DetectEdges( - this IImageProcessingContext source, - EdgeDetectorKernel kernel) => - DetectEdges(source, kernel, true); + public static IImageProcessingContext DetectEdges(this IImageProcessingContext source, EdgeDetectorKernel kernel) + => DetectEdges(source, kernel, true); /// /// Detects any edges within the image using a . @@ -125,66 +111,56 @@ public static IImageProcessingContext DetectEdges( this IImageProcessingContext source, EdgeDetectorKernel kernel, bool grayscale) - { - var processor = new EdgeDetectorProcessor(kernel, grayscale); - source.ApplyProcessor(processor); - return source; - } + => source.ApplyProcessor(new EdgeDetectorProcessor(kernel, grayscale)); /// /// Detects any edges within the image operating in grayscale mode. /// /// The current image processing context. - /// The edge detector kernel. /// /// The structure that specifies the portion of the image object to alter. /// + /// The edge detector kernel. /// The . public static IImageProcessingContext DetectEdges( this IImageProcessingContext source, - EdgeDetectorKernel kernel, - Rectangle rectangle) => - DetectEdges(source, kernel, true, rectangle); + Rectangle rectangle, + EdgeDetectorKernel kernel) + => DetectEdges(source, rectangle, kernel, true); /// /// Detects any edges within the image using a . /// /// The current image processing context. + /// + /// The structure that specifies the portion of the image object to alter. + /// /// The edge detector kernel. /// /// Whether to convert the image to grayscale before performing edge detection. /// - /// - /// The structure that specifies the portion of the image object to alter. - /// /// The . public static IImageProcessingContext DetectEdges( this IImageProcessingContext source, + Rectangle rectangle, EdgeDetectorKernel kernel, - bool grayscale, - Rectangle rectangle) - { - var processor = new EdgeDetectorProcessor(kernel, grayscale); - source.ApplyProcessor(processor, rectangle); - return source; - } + bool grayscale) + => source.ApplyProcessor(new EdgeDetectorProcessor(kernel, grayscale), rectangle); /// /// Detects any edges within the image operating in grayscale mode. /// /// The current image processing context. - /// Thecompass edge detector kernel. + /// The compass edge detector kernel. /// The . - public static IImageProcessingContext DetectEdges( - this IImageProcessingContext source, - EdgeDetectorCompassKernel kernel) => - DetectEdges(source, kernel, true); + public static IImageProcessingContext DetectEdges(this IImageProcessingContext source, EdgeDetectorCompassKernel kernel) + => DetectEdges(source, kernel, true); /// /// Detects any edges within the image using a . /// /// The current image processing context. - /// Thecompass edge detector kernel. + /// The compass edge detector kernel. /// /// Whether to convert the image to grayscale before performing edge detection. /// @@ -193,47 +169,39 @@ public static IImageProcessingContext DetectEdges( this IImageProcessingContext source, EdgeDetectorCompassKernel kernel, bool grayscale) - { - var processor = new EdgeDetectorCompassProcessor(kernel, grayscale); - source.ApplyProcessor(processor); - return source; - } + => source.ApplyProcessor(new EdgeDetectorCompassProcessor(kernel, grayscale)); /// /// Detects any edges within the image operating in grayscale mode. /// /// The current image processing context. - /// Thecompass edge detector kernel. /// /// The structure that specifies the portion of the image object to alter. /// + /// The compass edge detector kernel. /// The . public static IImageProcessingContext DetectEdges( this IImageProcessingContext source, - EdgeDetectorCompassKernel kernel, - Rectangle rectangle) => - DetectEdges(source, kernel, true, rectangle); + Rectangle rectangle, + EdgeDetectorCompassKernel kernel) + => DetectEdges(source, rectangle, kernel, true); /// /// Detects any edges within the image using a . /// /// The current image processing context. - /// Thecompass edge detector kernel. - /// - /// Whether to convert the image to grayscale before performing edge detection. - /// /// /// The structure that specifies the portion of the image object to alter. /// + /// The compass edge detector kernel. + /// + /// Whether to convert the image to grayscale before performing edge detection. + /// /// The . public static IImageProcessingContext DetectEdges( this IImageProcessingContext source, + Rectangle rectangle, EdgeDetectorCompassKernel kernel, - bool grayscale, - Rectangle rectangle) - { - var processor = new EdgeDetectorCompassProcessor(kernel, grayscale); - source.ApplyProcessor(processor, rectangle); - return source; - } + bool grayscale) + => source.ApplyProcessor(new EdgeDetectorCompassProcessor(kernel, grayscale), rectangle); } diff --git a/src/ImageSharp/Processing/Extensions/Convolution/GaussianBlurExtensions.cs b/src/ImageSharp/Processing/Extensions/Convolution/GaussianBlurExtensions.cs index 64e0be3eac..d406bf8d10 100644 --- a/src/ImageSharp/Processing/Extensions/Convolution/GaussianBlurExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/Convolution/GaussianBlurExtensions.cs @@ -32,22 +32,25 @@ public static IImageProcessingContext GaussianBlur(this IImageProcessingContext /// Applies a Gaussian blur to the image. /// /// The current image processing context. - /// The 'sigma' value representing the weight of the blur. /// /// The structure that specifies the portion of the image object to alter. /// + /// The 'sigma' value representing the weight of the blur. /// The . - public static IImageProcessingContext GaussianBlur(this IImageProcessingContext source, float sigma, Rectangle rectangle) + public static IImageProcessingContext GaussianBlur( + this IImageProcessingContext source, + Rectangle rectangle, + float sigma) => source.ApplyProcessor(new GaussianBlurProcessor(sigma), rectangle); /// /// Applies a Gaussian blur to the image. /// /// The current image processing context. - /// The 'sigma' value representing the weight of the blur. /// /// The structure that specifies the portion of the image object to alter. /// + /// The 'sigma' value representing the weight of the blur. /// /// The to use when mapping the pixels outside of the border, in X direction. /// @@ -55,9 +58,11 @@ public static IImageProcessingContext GaussianBlur(this IImageProcessingContext /// The to use when mapping the pixels outside of the border, in Y direction. /// /// The . - public static IImageProcessingContext GaussianBlur(this IImageProcessingContext source, float sigma, Rectangle rectangle, BorderWrappingMode borderWrapModeX, BorderWrappingMode borderWrapModeY) - { - GaussianBlurProcessor processor = new(sigma, borderWrapModeX, borderWrapModeY); - return source.ApplyProcessor(processor, rectangle); - } + public static IImageProcessingContext GaussianBlur( + this IImageProcessingContext source, + Rectangle rectangle, + float sigma, + BorderWrappingMode borderWrapModeX, + BorderWrappingMode borderWrapModeY) + => source.ApplyProcessor(new GaussianBlurProcessor(sigma, borderWrapModeX, borderWrapModeY), rectangle); } diff --git a/src/ImageSharp/Processing/Extensions/Convolution/GaussianSharpenExtensions.cs b/src/ImageSharp/Processing/Extensions/Convolution/GaussianSharpenExtensions.cs index 4a94df0963..9470cdbdc0 100644 --- a/src/ImageSharp/Processing/Extensions/Convolution/GaussianSharpenExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/Convolution/GaussianSharpenExtensions.cs @@ -16,8 +16,8 @@ public static class GaussianSharpenExtensions /// /// The current image processing context. /// The . - public static IImageProcessingContext GaussianSharpen(this IImageProcessingContext source) => - source.ApplyProcessor(new GaussianSharpenProcessor()); + public static IImageProcessingContext GaussianSharpen(this IImageProcessingContext source) + => source.ApplyProcessor(new GaussianSharpenProcessor()); /// /// Applies a Gaussian sharpening filter to the image. @@ -25,32 +25,32 @@ public static IImageProcessingContext GaussianSharpen(this IImageProcessingConte /// The current image processing context. /// The 'sigma' value representing the weight of the blur. /// The . - public static IImageProcessingContext GaussianSharpen(this IImageProcessingContext source, float sigma) => - source.ApplyProcessor(new GaussianSharpenProcessor(sigma)); + public static IImageProcessingContext GaussianSharpen(this IImageProcessingContext source, float sigma) + => source.ApplyProcessor(new GaussianSharpenProcessor(sigma)); /// /// Applies a Gaussian sharpening filter to the image. /// /// The current image processing context. - /// The 'sigma' value representing the weight of the blur. /// /// The structure that specifies the portion of the image object to alter. /// + /// The 'sigma' value representing the weight of the blur. /// The . public static IImageProcessingContext GaussianSharpen( this IImageProcessingContext source, - float sigma, - Rectangle rectangle) => + Rectangle rectangle, + float sigma) => source.ApplyProcessor(new GaussianSharpenProcessor(sigma), rectangle); /// /// Applies a Gaussian sharpening filter to the image. /// /// The current image processing context. - /// The 'sigma' value representing the weight of the blur. /// /// The structure that specifies the portion of the image object to alter. /// + /// The 'sigma' value representing the weight of the blur. /// /// The to use when mapping the pixels outside of the border, in X direction. /// @@ -58,9 +58,11 @@ public static IImageProcessingContext GaussianSharpen( /// The to use when mapping the pixels outside of the border, in Y direction. /// /// The . - public static IImageProcessingContext GaussianSharpen(this IImageProcessingContext source, float sigma, Rectangle rectangle, BorderWrappingMode borderWrapModeX, BorderWrappingMode borderWrapModeY) - { - var processor = new GaussianSharpenProcessor(sigma, borderWrapModeX, borderWrapModeY); - return source.ApplyProcessor(processor, rectangle); - } + public static IImageProcessingContext GaussianSharpen( + this IImageProcessingContext source, + Rectangle rectangle, + float sigma, + BorderWrappingMode borderWrapModeX, + BorderWrappingMode borderWrapModeY) + => source.ApplyProcessor(new GaussianSharpenProcessor(sigma, borderWrapModeX, borderWrapModeY), rectangle); } diff --git a/src/ImageSharp/Processing/Extensions/Convolution/MedianBlurExtensions.cs b/src/ImageSharp/Processing/Extensions/Convolution/MedianBlurExtensions.cs index a08a398b75..bc6fef62a6 100644 --- a/src/ImageSharp/Processing/Extensions/Convolution/MedianBlurExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/Convolution/MedianBlurExtensions.cs @@ -20,21 +20,28 @@ public static class MedianBlurExtensions /// Whether the filter is applied to alpha as well as the color channels. /// /// The . - public static IImageProcessingContext MedianBlur(this IImageProcessingContext source, int radius, bool preserveAlpha) + public static IImageProcessingContext MedianBlur( + this IImageProcessingContext source, + int radius, + bool preserveAlpha) => source.ApplyProcessor(new MedianBlurProcessor(radius, preserveAlpha)); /// /// Applies a median blur on the image. /// /// The current image processing context. + /// + /// The structure that specifies the portion of the image object to alter. + /// /// The radius of the area to find the median for. /// /// Whether the filter is applied to alpha as well as the color channels. /// - /// - /// The structure that specifies the portion of the image object to alter. - /// /// The . - public static IImageProcessingContext MedianBlur(this IImageProcessingContext source, int radius, bool preserveAlpha, Rectangle rectangle) + public static IImageProcessingContext MedianBlur( + this IImageProcessingContext source, + Rectangle rectangle, + int radius, + bool preserveAlpha) => source.ApplyProcessor(new MedianBlurProcessor(radius, preserveAlpha), rectangle); } diff --git a/tests/ImageSharp.Tests/Processing/Convolution/DetectEdgesTest.cs b/tests/ImageSharp.Tests/Processing/Convolution/DetectEdgesTest.cs index 2a1273ebb7..713daa0e9f 100644 --- a/tests/ImageSharp.Tests/Processing/Convolution/DetectEdgesTest.cs +++ b/tests/ImageSharp.Tests/Processing/Convolution/DetectEdgesTest.cs @@ -59,7 +59,7 @@ public void DetectEdges_EdgeDetector2DProcessor_DefaultGrayScale_Set(EdgeDetecto [MemberData(nameof(EdgeDetector2DKernelData))] public void DetectEdges_Rect_EdgeDetector2DProcessor_DefaultGrayScale_Set(EdgeDetector2DKernel kernel, bool _) { - this.operations.DetectEdges(kernel, this.rect); + this.operations.DetectEdges(this.rect, kernel); EdgeDetector2DProcessor processor = this.Verify(this.rect); Assert.True(processor.Grayscale); @@ -81,7 +81,7 @@ public void DetectEdges_EdgeDetector2DProcessorSet(EdgeDetector2DKernel kernel, [MemberData(nameof(EdgeDetector2DKernelData))] public void DetectEdges_Rect_EdgeDetector2DProcessorSet(EdgeDetector2DKernel kernel, bool grayscale) { - this.operations.DetectEdges(kernel, grayscale, this.rect); + this.operations.DetectEdges(this.rect, kernel, grayscale); EdgeDetector2DProcessor processor = this.Verify(this.rect); Assert.Equal(grayscale, processor.Grayscale); @@ -114,7 +114,7 @@ public void DetectEdges_EdgeDetectorProcessor_DefaultGrayScale_Set(EdgeDetectorK [MemberData(nameof(EdgeDetectorKernelData))] public void DetectEdges_Rect_EdgeDetectorProcessor_DefaultGrayScale_Set(EdgeDetectorKernel kernel, bool _) { - this.operations.DetectEdges(kernel, this.rect); + this.operations.DetectEdges(this.rect, kernel); EdgeDetectorProcessor processor = this.Verify(this.rect); Assert.True(processor.Grayscale); @@ -136,7 +136,7 @@ public void DetectEdges_EdgeDetectorProcessorSet(EdgeDetectorKernel kernel, bool [MemberData(nameof(EdgeDetectorKernelData))] public void DetectEdges_Rect_EdgeDetectorProcessorSet(EdgeDetectorKernel kernel, bool grayscale) { - this.operations.DetectEdges(kernel, grayscale, this.rect); + this.operations.DetectEdges(this.rect, kernel, grayscale); EdgeDetectorProcessor processor = this.Verify(this.rect); Assert.Equal(grayscale, processor.Grayscale); @@ -167,7 +167,7 @@ public void DetectEdges_EdgeDetectorCompassProcessor_DefaultGrayScale_Set(EdgeDe [MemberData(nameof(EdgeDetectorCompassKernelData))] public void DetectEdges_Rect_EdgeDetectorCompassProcessor_DefaultGrayScale_Set(EdgeDetectorCompassKernel kernel, bool _) { - this.operations.DetectEdges(kernel, this.rect); + this.operations.DetectEdges(this.rect, kernel); EdgeDetectorCompassProcessor processor = this.Verify(this.rect); Assert.True(processor.Grayscale); @@ -189,7 +189,7 @@ public void DetectEdges_EdgeDetectorCompassProcessorSet(EdgeDetectorCompassKerne [MemberData(nameof(EdgeDetectorCompassKernelData))] public void DetectEdges_Rect_EdgeDetectorCompassProcessorSet(EdgeDetectorCompassKernel kernel, bool grayscale) { - this.operations.DetectEdges(kernel, grayscale, this.rect); + this.operations.DetectEdges(this.rect, kernel, grayscale); EdgeDetectorCompassProcessor processor = this.Verify(this.rect); Assert.Equal(grayscale, processor.Grayscale); diff --git a/tests/ImageSharp.Tests/Processing/Convolution/GaussianBlurTest.cs b/tests/ImageSharp.Tests/Processing/Convolution/GaussianBlurTest.cs index 5142791603..410862ebfa 100644 --- a/tests/ImageSharp.Tests/Processing/Convolution/GaussianBlurTest.cs +++ b/tests/ImageSharp.Tests/Processing/Convolution/GaussianBlurTest.cs @@ -13,7 +13,7 @@ public class GaussianBlurTest : BaseImageOperationsExtensionTest public void GaussianBlur_GaussianBlurProcessorDefaultsSet() { this.operations.GaussianBlur(); - var processor = this.Verify(); + GaussianBlurProcessor processor = this.Verify(); Assert.Equal(3f, processor.Sigma); } @@ -22,7 +22,7 @@ public void GaussianBlur_GaussianBlurProcessorDefaultsSet() public void GaussianBlur_amount_GaussianBlurProcessorDefaultsSet() { this.operations.GaussianBlur(0.2f); - var processor = this.Verify(); + GaussianBlurProcessor processor = this.Verify(); Assert.Equal(.2f, processor.Sigma); } @@ -30,8 +30,8 @@ public void GaussianBlur_amount_GaussianBlurProcessorDefaultsSet() [Fact] public void GaussianBlur_amount_rect_GaussianBlurProcessorDefaultsSet() { - this.operations.GaussianBlur(0.6f, this.rect); - var processor = this.Verify(this.rect); + this.operations.GaussianBlur(this.rect, 0.6f); + GaussianBlurProcessor processor = this.Verify(this.rect); Assert.Equal(.6f, processor.Sigma); } diff --git a/tests/ImageSharp.Tests/Processing/Convolution/GaussianSharpenTest.cs b/tests/ImageSharp.Tests/Processing/Convolution/GaussianSharpenTest.cs index b48ebac0dc..fd9e64c467 100644 --- a/tests/ImageSharp.Tests/Processing/Convolution/GaussianSharpenTest.cs +++ b/tests/ImageSharp.Tests/Processing/Convolution/GaussianSharpenTest.cs @@ -13,7 +13,7 @@ public class GaussianSharpenTest : BaseImageOperationsExtensionTest public void GaussianSharpen_GaussianSharpenProcessorDefaultsSet() { this.operations.GaussianSharpen(); - var processor = this.Verify(); + GaussianSharpenProcessor processor = this.Verify(); Assert.Equal(3f, processor.Sigma); } @@ -22,7 +22,7 @@ public void GaussianSharpen_GaussianSharpenProcessorDefaultsSet() public void GaussianSharpen_amount_GaussianSharpenProcessorDefaultsSet() { this.operations.GaussianSharpen(0.2f); - var processor = this.Verify(); + GaussianSharpenProcessor processor = this.Verify(); Assert.Equal(.2f, processor.Sigma); } @@ -30,8 +30,8 @@ public void GaussianSharpen_amount_GaussianSharpenProcessorDefaultsSet() [Fact] public void GaussianSharpen_amount_rect_GaussianSharpenProcessorDefaultsSet() { - this.operations.GaussianSharpen(0.6f, this.rect); - var processor = this.Verify(this.rect); + this.operations.GaussianSharpen(this.rect, 0.6f); + GaussianSharpenProcessor processor = this.Verify(this.rect); Assert.Equal(.6f, processor.Sigma); } diff --git a/tests/ImageSharp.Tests/Processing/Convolution/MedianBlurTest.cs b/tests/ImageSharp.Tests/Processing/Convolution/MedianBlurTest.cs index dc497628fb..77fbc70823 100644 --- a/tests/ImageSharp.Tests/Processing/Convolution/MedianBlurTest.cs +++ b/tests/ImageSharp.Tests/Processing/Convolution/MedianBlurTest.cs @@ -13,7 +13,7 @@ public class MedianBlurTest : BaseImageOperationsExtensionTest public void Median_radius_MedianProcessorDefaultsSet() { this.operations.MedianBlur(3, true); - var processor = this.Verify(); + MedianBlurProcessor processor = this.Verify(); Assert.Equal(3, processor.Radius); Assert.True(processor.PreserveAlpha); @@ -22,8 +22,8 @@ public void Median_radius_MedianProcessorDefaultsSet() [Fact] public void Median_radius_rect_MedianProcessorDefaultsSet() { - this.operations.MedianBlur(5, false, this.rect); - var processor = this.Verify(this.rect); + this.operations.MedianBlur(this.rect, 5, false); + MedianBlurProcessor processor = this.Verify(this.rect); Assert.Equal(5, processor.Radius); Assert.False(processor.PreserveAlpha); diff --git a/tests/ImageSharp.Tests/Processing/Processors/Convolution/BokehBlurTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Convolution/BokehBlurTest.cs index c94983ecd5..f045c981eb 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Convolution/BokehBlurTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Convolution/BokehBlurTest.cs @@ -3,7 +3,6 @@ using System.Globalization; using System.Text.RegularExpressions; -using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Convolution; @@ -165,7 +164,7 @@ static void RunTest(string arg1, string arg2) { Size size = x.GetCurrentSize(); Rectangle bounds = new(10, 10, size.Width / 2, size.Height / 2); - x.BokehBlur(value.Radius, value.Components, value.Gamma, bounds); + x.BokehBlur(bounds, value.Radius, value.Components, value.Gamma); }, testOutputDetails: value.ToString(), ImageComparer.TolerantPercentage(0.05f), diff --git a/tests/ImageSharp.Tests/Processing/Processors/Convolution/GaussianBlurTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Convolution/GaussianBlurTest.cs index 47ccaa46cd..0728bb5d84 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Convolution/GaussianBlurTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Convolution/GaussianBlurTest.cs @@ -12,5 +12,5 @@ public class GaussianBlurTest : Basic1ParameterConvolutionTests protected override void Apply(IImageProcessingContext ctx, int value) => ctx.GaussianBlur(value); protected override void Apply(IImageProcessingContext ctx, int value, Rectangle bounds) => - ctx.GaussianBlur(value, bounds); + ctx.GaussianBlur(bounds, value); } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Convolution/GaussianSharpenTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Convolution/GaussianSharpenTest.cs index 96c58f7010..c6b7c82363 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Convolution/GaussianSharpenTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Convolution/GaussianSharpenTest.cs @@ -12,5 +12,5 @@ public class GaussianSharpenTest : Basic1ParameterConvolutionTests protected override void Apply(IImageProcessingContext ctx, int value) => ctx.GaussianSharpen(value); protected override void Apply(IImageProcessingContext ctx, int value, Rectangle bounds) => - ctx.GaussianSharpen(value, bounds); + ctx.GaussianSharpen(bounds, value); } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Convolution/MedianBlurTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Convolution/MedianBlurTest.cs index 7cc0ef9d50..048b843580 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Convolution/MedianBlurTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Convolution/MedianBlurTest.cs @@ -12,5 +12,5 @@ public class MedianBlurTest : Basic1ParameterConvolutionTests protected override void Apply(IImageProcessingContext ctx, int value) => ctx.MedianBlur(value, true); protected override void Apply(IImageProcessingContext ctx, int value, Rectangle bounds) => - ctx.MedianBlur(value, true, bounds); + ctx.MedianBlur(bounds, value, true); }