Browse Source

Merge pull request #2797 from SixLabors/js/convolution-api

Expose Convolution Api
pull/2842/head
James Jackson-South 2 years ago
committed by GitHub
parent
commit
1ec479e404
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 8
      src/ImageSharp/Processing/Extensions/Convolution/BokehBlurExtensions.cs
  2. 14
      src/ImageSharp/Processing/Extensions/Convolution/BoxBlurExtensions.cs
  3. 89
      src/ImageSharp/Processing/Extensions/Convolution/ConvolutionExtensions.cs
  4. 124
      src/ImageSharp/Processing/Extensions/Convolution/DetectEdgesExtensions.cs
  5. 21
      src/ImageSharp/Processing/Extensions/Convolution/GaussianBlurExtensions.cs
  6. 28
      src/ImageSharp/Processing/Extensions/Convolution/GaussianSharpenExtensions.cs
  7. 17
      src/ImageSharp/Processing/Extensions/Convolution/MedianBlurExtensions.cs
  8. 6
      src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs
  9. 54
      src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs
  10. 79
      src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs
  11. 44
      src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs
  12. 8
      src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs
  13. 2
      src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor{TPixel}.cs
  14. 20
      src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor{TPixel}.cs
  15. 18
      src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor{TPixel}.cs
  16. 12
      tests/ImageSharp.Tests/Processing/Convolution/DetectEdgesTest.cs
  17. 8
      tests/ImageSharp.Tests/Processing/Convolution/GaussianBlurTest.cs
  18. 8
      tests/ImageSharp.Tests/Processing/Convolution/GaussianSharpenTest.cs
  19. 6
      tests/ImageSharp.Tests/Processing/Convolution/MedianBlurTest.cs
  20. 3
      tests/ImageSharp.Tests/Processing/Processors/Convolution/BokehBlurTest.cs
  21. 50
      tests/ImageSharp.Tests/Processing/Processors/Convolution/ConvolutionTests.cs
  22. 2
      tests/ImageSharp.Tests/Processing/Processors/Convolution/GaussianBlurTest.cs
  23. 2
      tests/ImageSharp.Tests/Processing/Processors/Convolution/GaussianSharpenTest.cs
  24. 2
      tests/ImageSharp.Tests/Processing/Processors/Convolution/MedianBlurTest.cs
  25. 3
      tests/Images/External/ReferenceOutput/Convolution/ConvolutionTests/InBox_Rgba32_CalliphoraPartial_-1_-1_-1_-1_16_-1_-1_-1_-1.png
  26. 3
      tests/Images/External/ReferenceOutput/Convolution/ConvolutionTests/InBox_Rgba32_Car_-1_-1_-1_-1_16_-1_-1_-1_-1.png
  27. 3
      tests/Images/External/ReferenceOutput/Convolution/ConvolutionTests/InBox_Rgba32_blur_-1_-1_-1_-1_16_-1_-1_-1_-1.png
  28. 3
      tests/Images/External/ReferenceOutput/Convolution/ConvolutionTests/OnFullImage_Rgba32_CalliphoraPartial_-1_-1_-1_-1_16_-1_-1_-1_-1.png
  29. 3
      tests/Images/External/ReferenceOutput/Convolution/ConvolutionTests/OnFullImage_Rgba32_Car_-1_-1_-1_-1_16_-1_-1_-1_-1.png
  30. 3
      tests/Images/External/ReferenceOutput/Convolution/ConvolutionTests/OnFullImage_Rgba32_blur_-1_-1_-1_-1_16_-1_-1_-1_-1.png

8
src/ImageSharp/Processing/Extensions/Convolution/BokehBlurExtensions.cs

@ -44,13 +44,13 @@ public static class BokehBlurExtensions
/// Applies a bokeh blur to the image.
/// </summary>
/// <param name="source">The current image processing context.</param>
/// <param name="radius">The 'radius' value representing the size of the area to sample.</param>
/// <param name="components">The 'components' value representing the number of kernels to use to approximate the bokeh effect.</param>
/// <param name="gamma">The gamma highlight factor to use to emphasize bright spots in the source image</param>
/// <param name="rectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter.
/// </param>
/// <param name="radius">The 'radius' value representing the size of the area to sample.</param>
/// <param name="components">The 'components' value representing the number of kernels to use to approximate the bokeh effect.</param>
/// <param name="gamma">The gamma highlight factor to use to emphasize bright spots in the source image</param>
/// <returns>The <see cref="IImageProcessingContext"/>.</returns>
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);
}

14
src/ImageSharp/Processing/Extensions/Convolution/BoxBlurExtensions.cs

@ -44,10 +44,10 @@ public static class BoxBlurExtensions
/// Applies a box blur to the image.
/// </summary>
/// <param name="source">The current image processing context.</param>
/// <param name="radius">The 'radius' value representing the size of the area to sample.</param>
/// <param name="rectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter.
/// </param>
/// <param name="radius">The 'radius' value representing the size of the area to sample.</param>
/// <param name="borderWrapModeX">
/// The <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in X direction.
/// </param>
@ -55,9 +55,11 @@ public static class BoxBlurExtensions
/// The <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in Y direction.
/// </param>
/// <returns>The <see cref="IImageProcessingContext"/>.</returns>
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,
Rectangle rectangle,
int radius,
BorderWrappingMode borderWrapModeX,
BorderWrappingMode borderWrapModeY)
=> source.ApplyProcessor(new BoxBlurProcessor(radius, borderWrapModeX, borderWrapModeY), rectangle);
}

89
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;
/// <summary>
/// Defines general convolution extensions to apply on an <see cref="Image"/>
/// using Mutate/Clone.
/// </summary>
public static class ConvolutionExtensions
{
/// <summary>
/// Applies a convolution filter to the image.
/// </summary>
/// <param name="source">The current image processing context.</param>
/// <param name="kernelXY">The convolution kernel to apply.</param>
/// <returns>The <see cref="IImageProcessingContext"/>.</returns>
public static IImageProcessingContext Convolve(this IImageProcessingContext source, DenseMatrix<float> kernelXY)
=> Convolve(source, kernelXY, false);
/// <summary>
/// Applies a convolution filter to the image.
/// </summary>
/// <param name="source">The current image processing context.</param>
/// <param name="kernelXY">The convolution kernel to apply.</param>
/// <param name="preserveAlpha">Whether the convolution filter is applied to alpha as well as the color channels.</param>
/// <returns>The <see cref="IImageProcessingContext"/>.</returns>
public static IImageProcessingContext Convolve(this IImageProcessingContext source, DenseMatrix<float> kernelXY, bool preserveAlpha)
=> Convolve(source, kernelXY, preserveAlpha, BorderWrappingMode.Repeat, BorderWrappingMode.Repeat);
/// <summary>
/// Applies a convolution filter to the image.
/// </summary>
/// <param name="source">The current image processing context.</param>
/// <param name="kernelXY">The convolution kernel to apply.</param>
/// <param name="preserveAlpha">Whether the convolution filter is applied to alpha as well as the color channels.</param>
/// <param name="borderWrapModeX">The <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in X direction.</param>
/// <param name="borderWrapModeY">The <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in Y direction.</param>
/// <returns>The <see cref="IImageProcessingContext"/>.</returns>
public static IImageProcessingContext Convolve(
this IImageProcessingContext source,
DenseMatrix<float> kernelXY,
bool preserveAlpha,
BorderWrappingMode borderWrapModeX,
BorderWrappingMode borderWrapModeY)
=> source.ApplyProcessor(new ConvolutionProcessor(kernelXY, preserveAlpha, borderWrapModeX, borderWrapModeY));
/// <summary>
/// Applies a convolution filter to the image.
/// </summary>
/// <param name="source">The current image processing context.</param>
/// <param name="rectangle">The rectangle structure that specifies the portion of the image object to alter.</param>
/// <param name="kernelXY">The convolution kernel to apply.</param>
/// <returns>The <see cref="IImageProcessingContext"/>.</returns>
public static IImageProcessingContext Convolve(this IImageProcessingContext source, Rectangle rectangle, DenseMatrix<float> kernelXY)
=> Convolve(source, rectangle, kernelXY, false);
/// <summary>
/// Applies a convolution filter to the image.
/// </summary>
/// <param name="source">The current image processing context.</param>
/// <param name="rectangle">The rectangle structure that specifies the portion of the image object to alter.</param>
/// <param name="kernelXY">The convolution kernel to apply.</param>
/// <param name="preserveAlpha">Whether the convolution filter is applied to alpha as well as the color channels.</param>
/// <returns>The <see cref="IImageProcessingContext"/>.</returns>
public static IImageProcessingContext Convolve(this IImageProcessingContext source, Rectangle rectangle, DenseMatrix<float> kernelXY, bool preserveAlpha)
=> Convolve(source, rectangle, kernelXY, preserveAlpha, BorderWrappingMode.Repeat, BorderWrappingMode.Repeat);
/// <summary>
/// Applies a convolution filter to the image.
/// </summary>
/// <param name="source">The current image processing context.</param>
/// <param name="rectangle">The rectangle structure that specifies the portion of the image object to alter.</param>
/// <param name="kernelXY">The convolution kernel to apply.</param>
/// <param name="preserveAlpha">Whether the convolution filter is applied to alpha as well as the color channels.</param>
/// <param name="borderWrapModeX">The <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in X direction.</param>
/// <param name="borderWrapModeY">The <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in Y direction.</param>
/// <returns>The <see cref="IImageProcessingContext"/>.</returns>
public static IImageProcessingContext Convolve(
this IImageProcessingContext source,
Rectangle rectangle,
DenseMatrix<float> kernelXY,
bool preserveAlpha,
BorderWrappingMode borderWrapModeX,
BorderWrappingMode borderWrapModeY)
=> source.ApplyProcessor(new ConvolutionProcessor(kernelXY, preserveAlpha, borderWrapModeX, borderWrapModeY), rectangle);
}

124
src/ImageSharp/Processing/Extensions/Convolution/DetectEdgesExtensions.cs

@ -16,8 +16,8 @@ public static class DetectEdgesExtensions
/// </summary>
/// <param name="source">The current image processing context.</param>
/// <returns>The <see cref="IImageProcessingContext"/>.</returns>
public static IImageProcessingContext DetectEdges(this IImageProcessingContext source) =>
DetectEdges(source, KnownEdgeDetectorKernels.Sobel);
public static IImageProcessingContext DetectEdges(this IImageProcessingContext source)
=> DetectEdges(source, KnownEdgeDetectorKernels.Sobel);
/// <summary>
/// Detects any edges within the image.
@ -28,10 +28,8 @@ public static class DetectEdgesExtensions
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter.
/// </param>
/// <returns>The <see cref="IImageProcessingContext"/>.</returns>
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);
/// <summary>
/// Detects any edges within the image operating in grayscale mode.
@ -39,10 +37,8 @@ public static class DetectEdgesExtensions
/// <param name="source">The current image processing context.</param>
/// <param name="kernel">The 2D edge detector kernel.</param>
/// <returns>The <see cref="IImageProcessingContext"/>.</returns>
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);
/// <summary>
/// Detects any edges within the image using a <see cref="EdgeDetector2DKernel"/>.
@ -57,49 +53,41 @@ public static class DetectEdgesExtensions
this IImageProcessingContext source,
EdgeDetector2DKernel kernel,
bool grayscale)
{
var processor = new EdgeDetector2DProcessor(kernel, grayscale);
source.ApplyProcessor(processor);
return source;
}
=> source.ApplyProcessor(new EdgeDetector2DProcessor(kernel, grayscale));
/// <summary>
/// Detects any edges within the image operating in grayscale mode.
/// </summary>
/// <param name="source">The current image processing context.</param>
/// <param name="kernel">The 2D edge detector kernel.</param>
/// <param name="rectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter.
/// </param>
/// <param name="kernel">The 2D edge detector kernel.</param>
/// <returns>The <see cref="IImageProcessingContext"/>.</returns>
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);
/// <summary>
/// Detects any edges within the image using a <see cref="EdgeDetector2DKernel"/>.
/// </summary>
/// <param name="source">The current image processing context.</param>
/// <param name="rectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter.
/// </param>
/// <param name="kernel">The 2D edge detector kernel.</param>
/// <param name="grayscale">
/// Whether to convert the image to grayscale before performing edge detection.
/// </param>
/// <param name="rectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter.
/// </param>
/// <returns>The <see cref="IImageProcessingContext"/>.</returns>
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);
/// <summary>
/// Detects any edges within the image operating in grayscale mode.
@ -107,10 +95,8 @@ public static class DetectEdgesExtensions
/// <param name="source">The current image processing context.</param>
/// <param name="kernel">The edge detector kernel.</param>
/// <returns>The <see cref="IImageProcessingContext"/>.</returns>
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);
/// <summary>
/// Detects any edges within the image using a <see cref="EdgeDetectorKernel"/>.
@ -125,66 +111,56 @@ public static class DetectEdgesExtensions
this IImageProcessingContext source,
EdgeDetectorKernel kernel,
bool grayscale)
{
var processor = new EdgeDetectorProcessor(kernel, grayscale);
source.ApplyProcessor(processor);
return source;
}
=> source.ApplyProcessor(new EdgeDetectorProcessor(kernel, grayscale));
/// <summary>
/// Detects any edges within the image operating in grayscale mode.
/// </summary>
/// <param name="source">The current image processing context.</param>
/// <param name="kernel">The edge detector kernel.</param>
/// <param name="rectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter.
/// </param>
/// <param name="kernel">The edge detector kernel.</param>
/// <returns>The <see cref="IImageProcessingContext"/>.</returns>
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);
/// <summary>
/// Detects any edges within the image using a <see cref="EdgeDetectorKernel"/>.
/// </summary>
/// <param name="source">The current image processing context.</param>
/// <param name="rectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter.
/// </param>
/// <param name="kernel">The edge detector kernel.</param>
/// <param name="grayscale">
/// Whether to convert the image to grayscale before performing edge detection.
/// </param>
/// <param name="rectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter.
/// </param>
/// <returns>The <see cref="IImageProcessingContext"/>.</returns>
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);
/// <summary>
/// Detects any edges within the image operating in grayscale mode.
/// </summary>
/// <param name="source">The current image processing context.</param>
/// <param name="kernel">Thecompass edge detector kernel.</param>
/// <param name="kernel">The compass edge detector kernel.</param>
/// <returns>The <see cref="IImageProcessingContext"/>.</returns>
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);
/// <summary>
/// Detects any edges within the image using a <see cref="EdgeDetectorCompassKernel"/>.
/// </summary>
/// <param name="source">The current image processing context.</param>
/// <param name="kernel">Thecompass edge detector kernel.</param>
/// <param name="kernel">The compass edge detector kernel.</param>
/// <param name="grayscale">
/// Whether to convert the image to grayscale before performing edge detection.
/// </param>
@ -193,47 +169,39 @@ public static class DetectEdgesExtensions
this IImageProcessingContext source,
EdgeDetectorCompassKernel kernel,
bool grayscale)
{
var processor = new EdgeDetectorCompassProcessor(kernel, grayscale);
source.ApplyProcessor(processor);
return source;
}
=> source.ApplyProcessor(new EdgeDetectorCompassProcessor(kernel, grayscale));
/// <summary>
/// Detects any edges within the image operating in grayscale mode.
/// </summary>
/// <param name="source">The current image processing context.</param>
/// <param name="kernel">Thecompass edge detector kernel.</param>
/// <param name="rectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter.
/// </param>
/// <param name="kernel">The compass edge detector kernel.</param>
/// <returns>The <see cref="IImageProcessingContext"/>.</returns>
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);
/// <summary>
/// Detects any edges within the image using a <see cref="EdgeDetectorCompassKernel"/>.
/// </summary>
/// <param name="source">The current image processing context.</param>
/// <param name="kernel">Thecompass edge detector kernel.</param>
/// <param name="grayscale">
/// Whether to convert the image to grayscale before performing edge detection.
/// </param>
/// <param name="rectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter.
/// </param>
/// <param name="kernel">The compass edge detector kernel.</param>
/// <param name="grayscale">
/// Whether to convert the image to grayscale before performing edge detection.
/// </param>
/// <returns>The <see cref="IImageProcessingContext"/>.</returns>
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);
}

21
src/ImageSharp/Processing/Extensions/Convolution/GaussianBlurExtensions.cs

@ -32,22 +32,25 @@ public static class GaussianBlurExtensions
/// Applies a Gaussian blur to the image.
/// </summary>
/// <param name="source">The current image processing context.</param>
/// <param name="sigma">The 'sigma' value representing the weight of the blur.</param>
/// <param name="rectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter.
/// </param>
/// <param name="sigma">The 'sigma' value representing the weight of the blur.</param>
/// <returns>The <see cref="IImageProcessingContext"/>.</returns>
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);
/// <summary>
/// Applies a Gaussian blur to the image.
/// </summary>
/// <param name="source">The current image processing context.</param>
/// <param name="sigma">The 'sigma' value representing the weight of the blur.</param>
/// <param name="rectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter.
/// </param>
/// <param name="sigma">The 'sigma' value representing the weight of the blur.</param>
/// <param name="borderWrapModeX">
/// The <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in X direction.
/// </param>
@ -55,9 +58,11 @@ public static class GaussianBlurExtensions
/// The <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in Y direction.
/// </param>
/// <returns>The <see cref="IImageProcessingContext"/>.</returns>
public static IImageProcessingContext GaussianBlur(this IImageProcessingContext source, float sigma, Rectangle rectangle, BorderWrappingMode borderWrapModeX, BorderWrappingMode borderWrapModeY)
{
var processor = new GaussianBlurProcessor(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);
}

28
src/ImageSharp/Processing/Extensions/Convolution/GaussianSharpenExtensions.cs

@ -16,8 +16,8 @@ public static class GaussianSharpenExtensions
/// </summary>
/// <param name="source">The current image processing context.</param>
/// <returns>The <see cref="IImageProcessingContext"/>.</returns>
public static IImageProcessingContext GaussianSharpen(this IImageProcessingContext source) =>
source.ApplyProcessor(new GaussianSharpenProcessor());
public static IImageProcessingContext GaussianSharpen(this IImageProcessingContext source)
=> source.ApplyProcessor(new GaussianSharpenProcessor());
/// <summary>
/// Applies a Gaussian sharpening filter to the image.
@ -25,32 +25,32 @@ public static class GaussianSharpenExtensions
/// <param name="source">The current image processing context.</param>
/// <param name="sigma">The 'sigma' value representing the weight of the blur.</param>
/// <returns>The <see cref="IImageProcessingContext"/>.</returns>
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));
/// <summary>
/// Applies a Gaussian sharpening filter to the image.
/// </summary>
/// <param name="source">The current image processing context.</param>
/// <param name="sigma">The 'sigma' value representing the weight of the blur.</param>
/// <param name="rectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter.
/// </param>
/// <param name="sigma">The 'sigma' value representing the weight of the blur.</param>
/// <returns>The <see cref="IImageProcessingContext"/>.</returns>
public static IImageProcessingContext GaussianSharpen(
this IImageProcessingContext source,
float sigma,
Rectangle rectangle) =>
Rectangle rectangle,
float sigma) =>
source.ApplyProcessor(new GaussianSharpenProcessor(sigma), rectangle);
/// <summary>
/// Applies a Gaussian sharpening filter to the image.
/// </summary>
/// <param name="source">The current image processing context.</param>
/// <param name="sigma">The 'sigma' value representing the weight of the blur.</param>
/// <param name="rectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter.
/// </param>
/// <param name="sigma">The 'sigma' value representing the weight of the blur.</param>
/// <param name="borderWrapModeX">
/// The <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in X direction.
/// </param>
@ -58,9 +58,11 @@ public static class GaussianSharpenExtensions
/// The <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in Y direction.
/// </param>
/// <returns>The <see cref="IImageProcessingContext"/>.</returns>
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);
}

17
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.
/// </param>
/// <returns>The <see cref="IImageProcessingContext"/>.</returns>
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));
/// <summary>
/// Applies a median blur on the image.
/// </summary>
/// <param name="source">The current image processing context.</param>
/// <param name="rectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter.
/// </param>
/// <param name="radius">The radius of the area to find the median for.</param>
/// <param name="preserveAlpha">
/// Whether the filter is applied to alpha as well as the color channels.
/// </param>
/// <param name="rectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter.
/// </param>
/// <returns>The <see cref="IImageProcessingContext"/>.</returns>
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);
}

6
src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs

@ -62,14 +62,14 @@ internal class Convolution2DProcessor<TPixel> : ImageProcessor<TPixel>
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<TPixel>(
Convolution2DRowOperation<TPixel> operation = new(
interest,
targetPixels,
source.PixelBuffer,

54
src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs

@ -35,18 +35,48 @@ internal class Convolution2PassProcessor<TPixel> : ImageProcessor<TPixel>
Rectangle sourceRectangle,
BorderWrappingMode borderWrapModeX,
BorderWrappingMode borderWrapModeY)
: this(configuration, kernel, kernel, preserveAlpha, source, sourceRectangle, borderWrapModeX, borderWrapModeY)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Convolution2PassProcessor{TPixel}"/> class.
/// </summary>
/// <param name="configuration">The configuration which allows altering default behaviour or extending the library.</param>
/// <param name="kernelX">The 1D convolution kernel. X Direction</param>
/// <param name="kernelY">The 1D convolution kernel. Y Direction</param>
/// <param name="preserveAlpha">Whether the convolution filter is applied to alpha as well as the color channels.</param>
/// <param name="source">The source <see cref="Image{TPixel}"/> for the current processor instance.</param>
/// <param name="sourceRectangle">The source area to process for the current processor instance.</param>
/// <param name="borderWrapModeX">The <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in X direction.</param>
/// <param name="borderWrapModeY">The <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in Y direction.</param>
public Convolution2PassProcessor(
Configuration configuration,
float[] kernelX,
float[] kernelY,
bool preserveAlpha,
Image<TPixel> 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;
}
/// <summary>
/// Gets the convolution kernel.
/// Gets the convolution kernel. X direction.
/// </summary>
public float[] KernelX { get; }
/// <summary>
/// Gets the convolution kernel. Y direction.
/// </summary>
public float[] Kernel { get; }
public float[] KernelY { get; }
/// <summary>
/// Gets a value indicating whether the convolution filter is applied to alpha as well as the color channels.
@ -68,21 +98,21 @@ internal class Convolution2PassProcessor<TPixel> : ImageProcessor<TPixel>
{
using Buffer2D<TPixel> firstPassPixels = this.Configuration.MemoryAllocator.Allocate2D<TPixel>(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 @@ internal class Convolution2PassProcessor<TPixel> : ImageProcessor<TPixel>
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 @@ internal class Convolution2PassProcessor<TPixel> : ImageProcessor<TPixel>
}
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int GetRequiredBufferLength(Rectangle bounds)
=> 2 * bounds.Width;
@ -306,7 +336,7 @@ internal class Convolution2PassProcessor<TPixel> : ImageProcessor<TPixel>
}
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int GetRequiredBufferLength(Rectangle bounds)
=> 2 * bounds.Width;

79
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;
/// <summary>
/// Defines a processor that uses a 2 dimensional matrix to perform convolution against an image.
/// </summary>
public class ConvolutionProcessor : IImageProcessor
{
/// <summary>
/// Initializes a new instance of the <see cref="ConvolutionProcessor"/> class.
/// </summary>
/// <param name="kernelXY">The 2d gradient operator.</param>
/// <param name="preserveAlpha">Whether the convolution filter is applied to alpha as well as the color channels.</param>
/// <param name="borderWrapModeX">The <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in X direction.</param>
/// <param name="borderWrapModeY">The <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in Y direction.</param>
public ConvolutionProcessor(
in DenseMatrix<float> kernelXY,
bool preserveAlpha,
BorderWrappingMode borderWrapModeX,
BorderWrappingMode borderWrapModeY)
{
this.KernelXY = kernelXY;
this.PreserveAlpha = preserveAlpha;
this.BorderWrapModeX = borderWrapModeX;
this.BorderWrapModeY = borderWrapModeY;
}
/// <summary>
/// Gets the 2d convolution kernel.
/// </summary>
public DenseMatrix<float> KernelXY { get; }
/// <summary>
/// Gets a value indicating whether the convolution filter is applied to alpha as well as the color channels.
/// </summary>
public bool PreserveAlpha { get; }
/// <summary>
/// Gets the <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in X direction.
/// </summary>
public BorderWrappingMode BorderWrapModeX { get; }
/// <summary>
/// Gets the <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in Y direction.
/// </summary>
public BorderWrappingMode BorderWrapModeY { get; }
/// <inheritdoc/>
public IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Configuration configuration, Image<TPixel> source, Rectangle sourceRectangle)
where TPixel : unmanaged,
IPixel<TPixel>
{
if (this.KernelXY.TryGetLinearlySeparableComponents(out float[]? kernelX, out float[]? kernelY))
{
return new Convolution2PassProcessor<TPixel>(
configuration,
kernelX,
kernelY,
this.PreserveAlpha,
source,
sourceRectangle,
this.BorderWrapModeX,
this.BorderWrapModeY);
}
return new ConvolutionProcessor<TPixel>(
configuration,
this.KernelXY,
this.PreserveAlpha,
source,
sourceRectangle,
this.BorderWrapModeX,
this.BorderWrapModeY);
}
}

44
src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs

@ -31,10 +31,34 @@ internal class ConvolutionProcessor<TPixel> : ImageProcessor<TPixel>
bool preserveAlpha,
Image<TPixel> source,
Rectangle sourceRectangle)
: this(configuration, kernelXY, preserveAlpha, source, sourceRectangle, BorderWrappingMode.Repeat, BorderWrappingMode.Repeat)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ConvolutionProcessor{TPixel}"/> class.
/// </summary>
/// <param name="configuration">The configuration which allows altering default behaviour or extending the library.</param>
/// <param name="kernelXY">The 2d gradient operator.</param>
/// <param name="preserveAlpha">Whether the convolution filter is applied to alpha as well as the color channels.</param>
/// <param name="source">The source <see cref="Image{TPixel}"/> for the current processor instance.</param>
/// <param name="sourceRectangle">The source area to process for the current processor instance.</param>
/// <param name="borderWrapModeX">The <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in X direction.</param>
/// <param name="borderWrapModeY">The <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in Y direction.</param>
public ConvolutionProcessor(
Configuration configuration,
in DenseMatrix<float> kernelXY,
bool preserveAlpha,
Image<TPixel> source,
Rectangle sourceRectangle,
BorderWrappingMode borderWrapModeX,
BorderWrappingMode borderWrapModeY)
: base(configuration, source, sourceRectangle)
{
this.KernelXY = kernelXY;
this.PreserveAlpha = preserveAlpha;
this.BorderWrapModeX = borderWrapModeX;
this.BorderWrapModeY = borderWrapModeY;
}
/// <summary>
@ -47,6 +71,16 @@ internal class ConvolutionProcessor<TPixel> : ImageProcessor<TPixel>
/// </summary>
public bool PreserveAlpha { get; }
/// <summary>
/// Gets the <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in X direction.
/// </summary>
public BorderWrappingMode BorderWrapModeX { get; }
/// <summary>
/// Gets the <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in Y direction.
/// </summary>
public BorderWrappingMode BorderWrapModeY { get; }
/// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> source)
{
@ -55,13 +89,13 @@ internal class ConvolutionProcessor<TPixel> : ImageProcessor<TPixel>
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<RowOperation, Vector4>(
this.Configuration,
interest,
@ -121,7 +155,7 @@ internal class ConvolutionProcessor<TPixel> : ImageProcessor<TPixel>
ref Vector4 targetRowRef = ref MemoryMarshal.GetReference(span);
Span<TPixel> 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);

8
src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs

@ -58,12 +58,12 @@ internal class EdgeDetectorCompassProcessor<TPixel> : ImageProcessor<TPixel>
/// <inheritdoc />
protected override void OnFrameApply(ImageFrame<TPixel> 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<TPixel> cleanCopy = source.Clone();
using (var processor = new ConvolutionProcessor<TPixel>(this.Configuration, in this.kernels[0], true, this.Source, interest))
using (ConvolutionProcessor<TPixel> processor = new(this.Configuration, in this.kernels[0], true, this.Source, interest))
{
processor.Apply(source);
}
@ -78,12 +78,12 @@ internal class EdgeDetectorCompassProcessor<TPixel> : ImageProcessor<TPixel>
{
using ImageFrame<TPixel> pass = cleanCopy.Clone();
using (var processor = new ConvolutionProcessor<TPixel>(this.Configuration, in this.kernels[i], true, this.Source, interest))
using (ConvolutionProcessor<TPixel> 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,

2
src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor{TPixel}.cs

@ -53,7 +53,7 @@ internal class EdgeDetectorProcessor<TPixel> : ImageProcessor<TPixel>
/// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> source)
{
using var processor = new ConvolutionProcessor<TPixel>(this.Configuration, in this.kernelXY, true, this.Source, this.SourceRectangle);
using ConvolutionProcessor<TPixel> processor = new(this.Configuration, in this.kernelXY, true, this.Source, this.SourceRectangle);
processor.Apply(source);
}
}

20
src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor{TPixel}.cs

@ -12,24 +12,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution;
internal class GaussianBlurProcessor<TPixel> : ImageProcessor<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
/// <summary>
/// Initializes a new instance of the <see cref="GaussianBlurProcessor{TPixel}"/> class.
/// </summary>
/// <param name="configuration">The configuration which allows altering default behaviour or extending the library.</param>
/// <param name="definition">The <see cref="GaussianBlurProcessor"/> defining the processor parameters.</param>
/// <param name="source">The source <see cref="Image{TPixel}"/> for the current processor instance.</param>
/// <param name="sourceRectangle">The source area to process for the current processor instance.</param>
public GaussianBlurProcessor(
Configuration configuration,
GaussianBlurProcessor definition,
Image<TPixel> source,
Rectangle sourceRectangle)
: base(configuration, source, sourceRectangle)
{
int kernelSize = (definition.Radius * 2) + 1;
this.Kernel = ConvolutionProcessorHelpers.CreateGaussianBlurKernel(kernelSize, definition.Sigma);
}
/// <summary>
/// Initializes a new instance of the <see cref="GaussianBlurProcessor{TPixel}"/> class.
/// </summary>
@ -72,7 +54,7 @@ internal class GaussianBlurProcessor<TPixel> : ImageProcessor<TPixel>
/// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> source)
{
using var processor = new Convolution2PassProcessor<TPixel>(this.Configuration, this.Kernel, false, this.Source, this.SourceRectangle, this.BorderWrapModeX, this.BorderWrapModeY);
using Convolution2PassProcessor<TPixel> processor = new(this.Configuration, this.Kernel, false, this.Source, this.SourceRectangle, this.BorderWrapModeX, this.BorderWrapModeY);
processor.Apply(source);
}

18
src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor{TPixel}.cs

@ -12,22 +12,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution;
internal class GaussianSharpenProcessor<TPixel> : ImageProcessor<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
/// <summary>
/// Initializes a new instance of the <see cref="GaussianSharpenProcessor{TPixel}"/> class.
/// </summary>
/// <param name="configuration">The configuration which allows altering default behaviour or extending the library.</param>
/// <param name="definition">The <see cref="GaussianSharpenProcessor"/> defining the processor parameters.</param>
/// <param name="source">The source <see cref="Image{TPixel}"/> for the current processor instance.</param>
/// <param name="sourceRectangle">The source area to process for the current processor instance.</param>
public GaussianSharpenProcessor(
Configuration configuration,
GaussianSharpenProcessor definition,
Image<TPixel> source,
Rectangle sourceRectangle)
: this(configuration, definition, source, sourceRectangle, BorderWrappingMode.Repeat, BorderWrappingMode.Repeat)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="GaussianSharpenProcessor{TPixel}"/> class.
/// </summary>
@ -70,7 +54,7 @@ internal class GaussianSharpenProcessor<TPixel> : ImageProcessor<TPixel>
/// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> source)
{
using var processor = new Convolution2PassProcessor<TPixel>(this.Configuration, this.Kernel, false, this.Source, this.SourceRectangle, this.BorderWrapModeX, this.BorderWrapModeY);
using Convolution2PassProcessor<TPixel> processor = new(this.Configuration, this.Kernel, false, this.Source, this.SourceRectangle, this.BorderWrapModeX, this.BorderWrapModeY);
processor.Apply(source);
}

12
tests/ImageSharp.Tests/Processing/Convolution/DetectEdgesTest.cs

@ -59,7 +59,7 @@ public class DetectEdgesTest : BaseImageOperationsExtensionTest
[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<EdgeDetector2DProcessor>(this.rect);
Assert.True(processor.Grayscale);
@ -81,7 +81,7 @@ public class DetectEdgesTest : BaseImageOperationsExtensionTest
[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<EdgeDetector2DProcessor>(this.rect);
Assert.Equal(grayscale, processor.Grayscale);
@ -114,7 +114,7 @@ public class DetectEdgesTest : BaseImageOperationsExtensionTest
[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<EdgeDetectorProcessor>(this.rect);
Assert.True(processor.Grayscale);
@ -136,7 +136,7 @@ public class DetectEdgesTest : BaseImageOperationsExtensionTest
[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<EdgeDetectorProcessor>(this.rect);
Assert.Equal(grayscale, processor.Grayscale);
@ -167,7 +167,7 @@ public class DetectEdgesTest : BaseImageOperationsExtensionTest
[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<EdgeDetectorCompassProcessor>(this.rect);
Assert.True(processor.Grayscale);
@ -189,7 +189,7 @@ public class DetectEdgesTest : BaseImageOperationsExtensionTest
[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<EdgeDetectorCompassProcessor>(this.rect);
Assert.Equal(grayscale, processor.Grayscale);

8
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>();
GaussianBlurProcessor processor = this.Verify<GaussianBlurProcessor>();
Assert.Equal(3f, processor.Sigma);
}
@ -22,7 +22,7 @@ public class GaussianBlurTest : BaseImageOperationsExtensionTest
public void GaussianBlur_amount_GaussianBlurProcessorDefaultsSet()
{
this.operations.GaussianBlur(0.2f);
var processor = this.Verify<GaussianBlurProcessor>();
GaussianBlurProcessor processor = this.Verify<GaussianBlurProcessor>();
Assert.Equal(.2f, processor.Sigma);
}
@ -30,8 +30,8 @@ public class GaussianBlurTest : BaseImageOperationsExtensionTest
[Fact]
public void GaussianBlur_amount_rect_GaussianBlurProcessorDefaultsSet()
{
this.operations.GaussianBlur(0.6f, this.rect);
var processor = this.Verify<GaussianBlurProcessor>(this.rect);
this.operations.GaussianBlur(this.rect, 0.6f);
GaussianBlurProcessor processor = this.Verify<GaussianBlurProcessor>(this.rect);
Assert.Equal(.6f, processor.Sigma);
}

8
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>();
GaussianSharpenProcessor processor = this.Verify<GaussianSharpenProcessor>();
Assert.Equal(3f, processor.Sigma);
}
@ -22,7 +22,7 @@ public class GaussianSharpenTest : BaseImageOperationsExtensionTest
public void GaussianSharpen_amount_GaussianSharpenProcessorDefaultsSet()
{
this.operations.GaussianSharpen(0.2f);
var processor = this.Verify<GaussianSharpenProcessor>();
GaussianSharpenProcessor processor = this.Verify<GaussianSharpenProcessor>();
Assert.Equal(.2f, processor.Sigma);
}
@ -30,8 +30,8 @@ public class GaussianSharpenTest : BaseImageOperationsExtensionTest
[Fact]
public void GaussianSharpen_amount_rect_GaussianSharpenProcessorDefaultsSet()
{
this.operations.GaussianSharpen(0.6f, this.rect);
var processor = this.Verify<GaussianSharpenProcessor>(this.rect);
this.operations.GaussianSharpen(this.rect, 0.6f);
GaussianSharpenProcessor processor = this.Verify<GaussianSharpenProcessor>(this.rect);
Assert.Equal(.6f, processor.Sigma);
}

6
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>();
MedianBlurProcessor processor = this.Verify<MedianBlurProcessor>();
Assert.Equal(3, processor.Radius);
Assert.True(processor.PreserveAlpha);
@ -22,8 +22,8 @@ public class MedianBlurTest : BaseImageOperationsExtensionTest
[Fact]
public void Median_radius_rect_MedianProcessorDefaultsSet()
{
this.operations.MedianBlur(5, false, this.rect);
var processor = this.Verify<MedianBlurProcessor>(this.rect);
this.operations.MedianBlur(this.rect, 5, false);
MedianBlurProcessor processor = this.Verify<MedianBlurProcessor>(this.rect);
Assert.Equal(5, processor.Radius);
Assert.False(processor.PreserveAlpha);

3
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 @@ public class BokehBlurTest
{
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),

50
tests/ImageSharp.Tests/Processing/Processors/Convolution/ConvolutionTests.cs

@ -0,0 +1,50 @@
// 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;
[GroupOutput("Convolution")]
public class ConvolutionTests
{
private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.05F);
public static readonly TheoryData<DenseMatrix<float>> Values = new TheoryData<DenseMatrix<float>>
{
// 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<TPixel>(TestImageProvider<TPixel> provider, DenseMatrix<float> value)
where TPixel : unmanaged, IPixel<TPixel>
=> provider.RunValidatingProcessorTest(
x => x.Convolve(value),
string.Join('_', value.Data),
ValidatorComparer);
[Theory]
[WithFileCollection(nameof(InputImages), nameof(Values), PixelTypes.Rgba32)]
public void InBox<TPixel>(TestImageProvider<TPixel> provider, DenseMatrix<float> value)
where TPixel : unmanaged, IPixel<TPixel>
=> provider.RunRectangleConstrainedValidatingProcessorTest(
(x, rect) => x.Convolve(rect, value),
string.Join('_', value.Data),
ValidatorComparer);
}

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

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

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

3
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

3
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

3
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

3
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

3
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

3
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
Loading…
Cancel
Save