Browse Source

Merge pull request #1299 from SixLabors/js/better-edge-detection

Better Edge Detection API
pull/1574/head
James Jackson-South 6 years ago
committed by GitHub
parent
commit
ace7cf814b
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      src/ImageSharp/ImageSharp.csproj
  2. 61
      src/ImageSharp/Processing/EdgeDetectionOperators.cs
  3. 249
      src/ImageSharp/Processing/Extensions/Convolution/DetectEdgesExtensions.cs
  4. 63
      src/ImageSharp/Processing/KnownEdgeDetectorKernels.cs
  5. 42
      src/ImageSharp/Processing/Processors/Convolution/EdgeDetector2DProcessor.cs
  6. 43
      src/ImageSharp/Processing/Processors/Convolution/EdgeDetector2DProcessor{TPixel}.cs
  7. 42
      src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor.cs
  8. 32
      src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs
  9. 25
      src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor.cs
  10. 28
      src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor{TPixel}.cs
  11. 31
      src/ImageSharp/Processing/Processors/Convolution/KayyaliProcessor.cs
  12. 55
      src/ImageSharp/Processing/Processors/Convolution/Kernels/CompassKernels.cs
  13. 103
      src/ImageSharp/Processing/Processors/Convolution/Kernels/EdgeDetector2DKernel.cs
  14. 163
      src/ImageSharp/Processing/Processors/Convolution/Kernels/EdgeDetectorCompassKernel.cs
  15. 78
      src/ImageSharp/Processing/Processors/Convolution/Kernels/EdgeDetectorKernel.cs
  16. 0
      src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/KayyaliKernels.cs
  17. 25
      src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/KirschKernels.cs
  18. 0
      src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/LaplacianKernelFactory.cs
  19. 8
      src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/LaplacianKernels.cs
  20. 0
      src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/PrewittKernels.cs
  21. 0
      src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/RobertsCrossKernels.cs
  22. 23
      src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/RobinsonKernels.cs
  23. 0
      src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/ScharrKernels.cs
  24. 0
      src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/SobelKernels.cs
  25. 25
      src/ImageSharp/Processing/Processors/Convolution/KirschProcessor.cs
  26. 25
      src/ImageSharp/Processing/Processors/Convolution/Laplacian3x3Processor.cs
  27. 25
      src/ImageSharp/Processing/Processors/Convolution/Laplacian5x5Processor.cs
  28. 25
      src/ImageSharp/Processing/Processors/Convolution/LaplacianOfGaussianProcessor.cs
  29. 31
      src/ImageSharp/Processing/Processors/Convolution/PrewittProcessor.cs
  30. 31
      src/ImageSharp/Processing/Processors/Convolution/RobertsCrossProcessor.cs
  31. 25
      src/ImageSharp/Processing/Processors/Convolution/RobinsonProcessor.cs
  32. 31
      src/ImageSharp/Processing/Processors/Convolution/ScharrProcessor.cs
  33. 31
      src/ImageSharp/Processing/Processors/Convolution/SobelProcessor.cs
  34. 2
      src/ImageSharp/Processing/Processors/Transforms/EntropyCropProcessor{TPixel}.cs
  35. 22
      tests/ImageSharp.Benchmarks/Samplers/DetectEdges.cs
  36. 198
      tests/ImageSharp.Tests/Processing/Convolution/DetectEdgesTest.cs
  37. 81
      tests/ImageSharp.Tests/Processing/Convolution/Processors/EdgeDetectorKernelTests.cs
  38. 116
      tests/ImageSharp.Tests/Processing/Processors/Convolution/DetectEdgesTest.cs
  39. 2
      tests/Images/External

2
src/ImageSharp/ImageSharp.csproj

@ -20,7 +20,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub"/>
<PackageReference Include="Microsoft.SourceLink.GitHub" />
<PackageReference Include="MinVer" PrivateAssets="All" />
</ItemGroup>

61
src/ImageSharp/Processing/EdgeDetectionOperators.cs

@ -1,61 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Processing
{
/// <summary>
/// Enumerates the various types of defined edge detection filters.
/// </summary>
public enum EdgeDetectionOperators
{
/// <summary>
/// The Kayyali operator filter.
/// </summary>
Kayyali,
/// <summary>
/// The Kirsch operator filter.
/// </summary>
Kirsch,
/// <summary>
/// The Laplacian3X3 operator filter.
/// </summary>
Laplacian3x3,
/// <summary>
/// The Laplacian5X5 operator filter.
/// </summary>
Laplacian5x5,
/// <summary>
/// The LaplacianOfGaussian operator filter.
/// </summary>
LaplacianOfGaussian,
/// <summary>
/// The Prewitt operator filter.
/// </summary>
Prewitt,
/// <summary>
/// The RobertsCross operator filter.
/// </summary>
RobertsCross,
/// <summary>
/// The Robinson operator filter.
/// </summary>
Robinson,
/// <summary>
/// The Scharr operator filter.
/// </summary>
Scharr,
/// <summary>
/// The Sobel operator filter.
/// </summary>
Sobel
}
}

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

@ -1,7 +1,6 @@
// Copyright (c) Six Labors.
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Processing.Processors;
using SixLabors.ImageSharp.Processing.Processors.Convolution;
namespace SixLabors.ImageSharp.Processing
@ -12,144 +11,230 @@ namespace SixLabors.ImageSharp.Processing
public static class DetectEdgesExtensions
{
/// <summary>
/// Detects any edges within the image. Uses the <see cref="SobelProcessor"/> filter
/// operating in grayscale mode.
/// Detects any edges within the image.
/// Uses the <see cref="KnownEdgeDetectorKernels.Sobel"/> kernel operating in grayscale mode.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
public static IImageProcessingContext DetectEdges(this IImageProcessingContext source) =>
DetectEdges(source, new SobelProcessor(true));
DetectEdges(source, KnownEdgeDetectorKernels.Sobel);
/// <summary>
/// Detects any edges within the image. Uses the <see cref="SobelProcessor"/> filter
/// operating in grayscale mode.
/// Detects any edges within the image.
/// Uses the <see cref="KnownEdgeDetectorKernels.Sobel"/> kernel operating in grayscale mode.
/// </summary>
/// <param name="source">The image this method extends.</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"/> to allow chaining of operations.</returns>
public static IImageProcessingContext DetectEdges(this IImageProcessingContext source, Rectangle rectangle) =>
DetectEdges(source, rectangle, new SobelProcessor(true));
public static IImageProcessingContext DetectEdges(
this IImageProcessingContext source,
Rectangle rectangle) =>
DetectEdges(source, KnownEdgeDetectorKernels.Sobel, rectangle);
/// <summary>
/// Detects any edges within the image.
/// Detects any edges within the image operating in grayscale mode.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="filter">The filter for detecting edges.</param>
/// <param name="kernel">The 2D edge detector kernel.</param>
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
public static IImageProcessingContext DetectEdges(
this IImageProcessingContext source,
EdgeDetectionOperators filter) =>
DetectEdges(source, GetProcessor(filter, true));
EdgeDetector2DKernel kernel) =>
DetectEdges(source, kernel, true);
/// <summary>
/// Detects any edges within the image.
/// Detects any edges within the image using a <see cref="EdgeDetector2DKernel"/>.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="filter">The filter for detecting edges.</param>
/// <param name="grayscale">Whether to convert the image to grayscale first. Defaults to true.</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>
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
public static IImageProcessingContext DetectEdges(
this IImageProcessingContext source,
EdgeDetectionOperators filter,
bool grayscale) =>
DetectEdges(source, GetProcessor(filter, grayscale));
EdgeDetector2DKernel kernel,
bool grayscale)
{
var processor = new EdgeDetector2DProcessor(kernel, grayscale);
source.ApplyProcessor(processor);
return source;
}
/// <summary>
/// Detects any edges within the image.
/// Detects any edges within the image operating in grayscale mode.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="filter">The filter for detecting edges.</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="grayscale">Whether to convert the image to grayscale first. Defaults to true.</param>
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
public static IImageProcessingContext DetectEdges(
this IImageProcessingContext source,
EdgeDetectionOperators filter,
Rectangle rectangle,
bool grayscale = true) =>
DetectEdges(source, rectangle, GetProcessor(filter, grayscale));
EdgeDetector2DKernel kernel,
Rectangle rectangle) =>
DetectEdges(source, kernel, true, rectangle);
/// <summary>
/// Detects any edges within the image.
/// Detects any edges within the image using a <see cref="EdgeDetector2DKernel"/>.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="filter">The filter for detecting edges.</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"/> to allow chaining of operations.</returns>
private static IImageProcessingContext DetectEdges(this IImageProcessingContext source, IImageProcessor filter)
public static IImageProcessingContext DetectEdges(
this IImageProcessingContext source,
EdgeDetector2DKernel kernel,
bool grayscale,
Rectangle rectangle)
{
return source.ApplyProcessor(filter);
var processor = new EdgeDetector2DProcessor(kernel, grayscale);
source.ApplyProcessor(processor, rectangle);
return source;
}
/// <summary>
/// Detects any edges within the image.
/// Detects any edges within the image operating in grayscale mode.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="rectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter.
/// <param name="kernel">The edge detector kernel.</param>
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
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"/>.
/// </summary>
/// <param name="source">The image this method extends.</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="filter">The filter for detecting edges.</param>
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
private static IImageProcessingContext DetectEdges(
public static IImageProcessingContext DetectEdges(
this IImageProcessingContext source,
Rectangle rectangle,
IImageProcessor filter)
EdgeDetectorKernel kernel,
bool grayscale)
{
source.ApplyProcessor(filter, rectangle);
var processor = new EdgeDetectorProcessor(kernel, grayscale);
source.ApplyProcessor(processor);
return source;
}
private static IImageProcessor GetProcessor(EdgeDetectionOperators filter, bool grayscale)
{
IImageProcessor processor;
switch (filter)
{
case EdgeDetectionOperators.Kayyali:
processor = new KayyaliProcessor(grayscale);
break;
case EdgeDetectionOperators.Kirsch:
processor = new KirschProcessor(grayscale);
break;
case EdgeDetectionOperators.Laplacian3x3:
processor = new Laplacian3x3Processor(grayscale);
break;
case EdgeDetectionOperators.Laplacian5x5:
processor = new Laplacian5x5Processor(grayscale);
break;
case EdgeDetectionOperators.LaplacianOfGaussian:
processor = new LaplacianOfGaussianProcessor(grayscale);
break;
case EdgeDetectionOperators.Prewitt:
processor = new PrewittProcessor(grayscale);
break;
/// <summary>
/// Detects any edges within the image operating in grayscale mode.
/// </summary>
/// <param name="source">The image this method extends.</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>
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
public static IImageProcessingContext DetectEdges(
this IImageProcessingContext source,
EdgeDetectorKernel kernel,
Rectangle rectangle) =>
DetectEdges(source, kernel, true, rectangle);
case EdgeDetectionOperators.RobertsCross:
processor = new RobertsCrossProcessor(grayscale);
break;
/// <summary>
/// Detects any edges within the image using a <see cref="EdgeDetectorKernel"/>.
/// </summary>
/// <param name="source">The image this method extends.</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"/> to allow chaining of operations.</returns>
public static IImageProcessingContext DetectEdges(
this IImageProcessingContext source,
EdgeDetectorKernel kernel,
bool grayscale,
Rectangle rectangle)
{
var processor = new EdgeDetectorProcessor(kernel, grayscale);
source.ApplyProcessor(processor, rectangle);
return source;
}
case EdgeDetectionOperators.Robinson:
processor = new RobinsonProcessor(grayscale);
break;
/// <summary>
/// Detects any edges within the image operating in grayscale mode.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="kernel">Thecompass edge detector kernel.</param>
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
public static IImageProcessingContext DetectEdges(
this IImageProcessingContext source,
EdgeDetectorCompassKernel kernel) =>
DetectEdges(source, kernel, true);
case EdgeDetectionOperators.Scharr:
processor = new ScharrProcessor(grayscale);
break;
/// <summary>
/// Detects any edges within the image using a <see cref="EdgeDetectorCompassKernel"/>.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="kernel">Thecompass edge detector kernel.</param>
/// <param name="grayscale">
/// Whether to convert the image to grayscale before performing edge detection.
/// </param>
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
public static IImageProcessingContext DetectEdges(
this IImageProcessingContext source,
EdgeDetectorCompassKernel kernel,
bool grayscale)
{
var processor = new EdgeDetectorCompassProcessor(kernel, grayscale);
source.ApplyProcessor(processor);
return source;
}
default:
processor = new SobelProcessor(grayscale);
break;
}
/// <summary>
/// Detects any edges within the image operating in grayscale mode.
/// </summary>
/// <param name="source">The image this method extends.</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>
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
public static IImageProcessingContext DetectEdges(
this IImageProcessingContext source,
EdgeDetectorCompassKernel kernel,
Rectangle rectangle) =>
DetectEdges(source, kernel, true, rectangle);
return processor;
/// <summary>
/// Detects any edges within the image using a <see cref="EdgeDetectorCompassKernel"/>.
/// </summary>
/// <param name="source">The image this method extends.</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>
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
public static IImageProcessingContext DetectEdges(
this IImageProcessingContext source,
EdgeDetectorCompassKernel kernel,
bool grayscale,
Rectangle rectangle)
{
var processor = new EdgeDetectorCompassProcessor(kernel, grayscale);
source.ApplyProcessor(processor, rectangle);
return source;
}
}
}
}

63
src/ImageSharp/Processing/KnownEdgeDetectorKernels.cs

@ -0,0 +1,63 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Processing.Processors.Convolution;
namespace SixLabors.ImageSharp.Processing
{
/// <summary>
/// Contains reusable static instances of known edge detection kernels.
/// </summary>
public static class KnownEdgeDetectorKernels
{
/// <summary>
/// Gets the Kayyali edge detector kernel.
/// </summary>
public static EdgeDetector2DKernel Kayyali { get; } = EdgeDetector2DKernel.KayyaliKernel;
/// <summary>
/// Gets the Kirsch edge detector kernel.
/// </summary>
public static EdgeDetectorCompassKernel Kirsch { get; } = EdgeDetectorCompassKernel.Kirsch;
/// <summary>
/// Gets the Laplacian 3x3 edge detector kernel.
/// </summary>
public static EdgeDetectorKernel Laplacian3x3 { get; } = EdgeDetectorKernel.Laplacian3x3;
/// <summary>
/// Gets the Laplacian 5x5 edge detector kernel.
/// </summary>
public static EdgeDetectorKernel Laplacian5x5 { get; } = EdgeDetectorKernel.Laplacian5x5;
/// <summary>
/// Gets the Laplacian of Gaussian edge detector kernel.
/// </summary>
public static EdgeDetectorKernel LaplacianOfGaussian { get; } = EdgeDetectorKernel.LaplacianOfGaussian;
/// <summary>
/// Gets the Prewitt edge detector kernel.
/// </summary>
public static EdgeDetector2DKernel Prewitt { get; } = EdgeDetector2DKernel.PrewittKernel;
/// <summary>
/// Gets the Roberts-Cross edge detector kernel.
/// </summary>
public static EdgeDetector2DKernel RobertsCross { get; } = EdgeDetector2DKernel.RobertsCrossKernel;
/// <summary>
/// Gets the Robinson edge detector kernel.
/// </summary>
public static EdgeDetectorCompassKernel Robinson { get; } = EdgeDetectorCompassKernel.Robinson;
/// <summary>
/// Gets the Scharr edge detector kernel.
/// </summary>
public static EdgeDetector2DKernel Scharr { get; } = EdgeDetector2DKernel.ScharrKernel;
/// <summary>
/// Gets the Sobel edge detector kernel.
/// </summary>
public static EdgeDetector2DKernel Sobel { get; } = EdgeDetector2DKernel.SobelKernel;
}
}

42
src/ImageSharp/Processing/Processors/Convolution/EdgeDetector2DProcessor.cs

@ -0,0 +1,42 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{
/// <summary>
/// Defines edge detection using the two 1D gradient operators.
/// </summary>
public sealed class EdgeDetector2DProcessor : IImageProcessor
{
/// <summary>
/// Initializes a new instance of the <see cref="EdgeDetector2DProcessor"/> class.
/// </summary>
/// <param name="kernel">The 2D edge detector kernel.</param>
/// <param name="grayscale">
/// Whether to convert the image to grayscale before performing edge detection.
/// </param>
public EdgeDetector2DProcessor(EdgeDetector2DKernel kernel, bool grayscale)
{
this.Kernel = kernel;
this.Grayscale = grayscale;
}
/// <summary>
/// Gets the 2D edge detector kernel.
/// </summary>
public EdgeDetector2DKernel Kernel { get; }
/// <summary>
/// Gets a value indicating whether to convert the image to grayscale before performing
/// edge detection.
/// </summary>
public bool Grayscale { get; }
/// <inheritdoc />
public IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Configuration configuration, Image<TPixel> source, Rectangle sourceRectangle)
where TPixel : unmanaged, IPixel<TPixel>
=> new EdgeDetector2DProcessor<TPixel>(configuration, this, source, sourceRectangle);
}
}

43
src/ImageSharp/Processing/Processors/Convolution/EdgeDetector2DProcessor{TPixel}.cs

@ -13,42 +13,29 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
internal class EdgeDetector2DProcessor<TPixel> : ImageProcessor<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
private readonly DenseMatrix<float> kernelX;
private readonly DenseMatrix<float> kernelY;
private readonly bool grayscale;
/// <summary>
/// Initializes a new instance of the <see cref="EdgeDetector2DProcessor{TPixel}"/> class.
/// </summary>
/// <param name="configuration">The configuration which allows altering default behaviour or extending the library.</param>
/// <param name="kernelX">The horizontal gradient operator.</param>
/// <param name="kernelY">The vertical gradient operator.</param>
/// <param name="grayscale">Whether to convert the image to grayscale before performing edge detection.</param>
/// <param name="definition">The <see cref="EdgeDetector2DProcessor"/> 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>
internal EdgeDetector2DProcessor(
public EdgeDetector2DProcessor(
Configuration configuration,
in DenseMatrix<float> kernelX,
in DenseMatrix<float> kernelY,
bool grayscale,
EdgeDetector2DProcessor definition,
Image<TPixel> source,
Rectangle sourceRectangle)
: base(configuration, source, sourceRectangle)
{
Guard.IsTrue(kernelX.Size.Equals(kernelY.Size), $"{nameof(kernelX)} {nameof(kernelY)}", "Kernel sizes must be the same.");
this.KernelX = kernelX;
this.KernelY = kernelY;
this.Grayscale = grayscale;
this.kernelX = definition.Kernel.KernelX;
this.kernelY = definition.Kernel.KernelY;
this.grayscale = definition.Grayscale;
}
/// <summary>
/// Gets the horizontal gradient operator.
/// </summary>
public DenseMatrix<float> KernelX { get; }
/// <summary>
/// Gets the vertical gradient operator.
/// </summary>
public DenseMatrix<float> KernelY { get; }
public bool Grayscale { get; }
/// <inheritdoc/>
protected override void BeforeImageApply()
{
@ -57,7 +44,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
opaque.Execute();
}
if (this.Grayscale)
if (this.grayscale)
{
new GrayscaleBt709Processor(1F).Execute(this.Configuration, this.Source, this.SourceRectangle);
}
@ -68,7 +55,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// <inheritdoc />
protected override void OnFrameApply(ImageFrame<TPixel> source)
{
using var processor = new Convolution2DProcessor<TPixel>(this.Configuration, this.KernelX, this.KernelY, true, this.Source, this.SourceRectangle);
using var processor = new Convolution2DProcessor<TPixel>(
this.Configuration,
in this.kernelX,
in this.kernelY,
true,
this.Source,
this.SourceRectangle);
processor.Apply(source);
}

42
src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor.cs

@ -0,0 +1,42 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{
/// <summary>
/// Defines edge detection using eight gradient operators.
/// </summary>
public sealed class EdgeDetectorCompassProcessor : IImageProcessor
{
/// <summary>
/// Initializes a new instance of the <see cref="EdgeDetectorCompassProcessor"/> class.
/// </summary>
/// <param name="kernel">The edge detector kernel.</param>
/// <param name="grayscale">
/// Whether to convert the image to grayscale before performing edge detection.
/// </param>
public EdgeDetectorCompassProcessor(EdgeDetectorCompassKernel kernel, bool grayscale)
{
this.Kernel = kernel;
this.Grayscale = grayscale;
}
/// <summary>
/// Gets the edge detector kernel.
/// </summary>
public EdgeDetectorCompassKernel Kernel { get; }
/// <summary>
/// Gets a value indicating whether to convert the image to grayscale before performing
/// edge detection.
/// </summary>
public bool Grayscale { get; }
/// <inheritdoc />
public IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Configuration configuration, Image<TPixel> source, Rectangle sourceRectangle)
where TPixel : unmanaged, IPixel<TPixel>
=> new EdgeDetectorCompassProcessor<TPixel>(configuration, this, source, sourceRectangle);
}
}

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

@ -18,25 +18,27 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
internal class EdgeDetectorCompassProcessor<TPixel> : ImageProcessor<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
private readonly DenseMatrix<float>[] kernels;
private readonly bool grayscale;
/// <summary>
/// Initializes a new instance of the <see cref="EdgeDetectorCompassProcessor{TPixel}"/> class.
/// </summary>
/// <param name="configuration">The configuration which allows altering default behaviour or extending the library.</param>
/// <param name="kernels">Gets the kernels to use.</param>
/// <param name="grayscale">Whether to convert the image to grayscale before performing edge detection.</param>
/// <param name="definition">The <see cref="EdgeDetectorCompassProcessor"/> 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>
internal EdgeDetectorCompassProcessor(Configuration configuration, CompassKernels kernels, bool grayscale, Image<TPixel> source, Rectangle sourceRectangle)
internal EdgeDetectorCompassProcessor(
Configuration configuration,
EdgeDetectorCompassProcessor definition,
Image<TPixel> source,
Rectangle sourceRectangle)
: base(configuration, source, sourceRectangle)
{
this.Grayscale = grayscale;
this.Kernels = kernels;
this.grayscale = definition.Grayscale;
this.kernels = definition.Kernel.Flatten();
}
private CompassKernels Kernels { get; }
private bool Grayscale { get; }
/// <inheritdoc/>
protected override void BeforeImageApply()
{
@ -45,7 +47,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
opaque.Execute();
}
if (this.Grayscale)
if (this.grayscale)
{
new GrayscaleBt709Processor(1F).Execute(this.Configuration, this.Source, this.SourceRectangle);
}
@ -56,29 +58,27 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// <inheritdoc />
protected override void OnFrameApply(ImageFrame<TPixel> source)
{
DenseMatrix<float>[] kernels = this.Kernels.Flatten();
var 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, kernels[0], true, this.Source, interest))
using (var processor = new ConvolutionProcessor<TPixel>(this.Configuration, in this.kernels[0], true, this.Source, interest))
{
processor.Apply(source);
}
if (kernels.Length == 1)
if (this.kernels.Length == 1)
{
return;
}
// Additional runs
for (int i = 1; i < kernels.Length; i++)
for (int i = 1; i < this.kernels.Length; i++)
{
using ImageFrame<TPixel> pass = cleanCopy.Clone();
using (var processor = new ConvolutionProcessor<TPixel>(this.Configuration, kernels[i], true, this.Source, interest))
using (var processor = new ConvolutionProcessor<TPixel>(this.Configuration, in this.kernels[i], true, this.Source, interest))
{
processor.Apply(pass);
}

25
src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor.cs

@ -6,26 +6,37 @@ using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{
/// <summary>
/// Defines a processor that detects edges within an image using a single two dimensional matrix.
/// Defines edge detection using a single 2D gradient operator.
/// </summary>
public abstract class EdgeDetectorProcessor : IImageProcessor
public sealed class EdgeDetectorProcessor : IImageProcessor
{
/// <summary>
/// Initializes a new instance of the <see cref="EdgeDetectorProcessor"/> class.
/// </summary>
/// <param name="grayscale">A value indicating whether to convert the image to grayscale before performing edge detection.</param>
protected EdgeDetectorProcessor(bool grayscale)
/// <param name="kernel">The edge detector kernel.</param>
/// <param name="grayscale">
/// Whether to convert the image to grayscale before performing edge detection.
/// </param>
public EdgeDetectorProcessor(EdgeDetectorKernel kernel, bool grayscale)
{
this.Kernel = kernel;
this.Grayscale = grayscale;
}
/// <summary>
/// Gets a value indicating whether to convert the image to grayscale before performing edge detection.
/// Gets the edge detector kernel.
/// </summary>
public EdgeDetectorKernel Kernel { get; }
/// <summary>
/// Gets a value indicating whether to convert the image to grayscale before performing
/// edge detection.
/// </summary>
public bool Grayscale { get; }
/// <inheritdoc />
public abstract IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Configuration configuration, Image<TPixel> source, Rectangle sourceRectangle)
where TPixel : unmanaged, IPixel<TPixel>;
public IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Configuration configuration, Image<TPixel> source, Rectangle sourceRectangle)
where TPixel : unmanaged, IPixel<TPixel>
=> new EdgeDetectorProcessor<TPixel>(configuration, this, source, sourceRectangle);
}
}

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

@ -13,33 +13,27 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
internal class EdgeDetectorProcessor<TPixel> : ImageProcessor<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
private readonly bool grayscale;
private readonly DenseMatrix<float> kernelXY;
/// <summary>
/// Initializes a new instance of the <see cref="EdgeDetectorProcessor{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="grayscale">Whether to convert the image to grayscale before performing edge detection.</param>
/// <param name="definition">The <see cref="EdgeDetectorProcessor"/> defining the processor parameters.</param>
/// <param name="source">The source <see cref="Image{TPixel}"/> for the current processor instance.</param>
/// <param name="sourceRectangle">The target area to process for the current processor instance.</param>
public EdgeDetectorProcessor(
Configuration configuration,
in DenseMatrix<float> kernelXY,
bool grayscale,
EdgeDetectorProcessor definition,
Image<TPixel> source,
Rectangle sourceRectangle)
: base(configuration, source, sourceRectangle)
{
this.KernelXY = kernelXY;
this.Grayscale = grayscale;
this.kernelXY = definition.Kernel.KernelXY;
this.grayscale = definition.Grayscale;
}
public bool Grayscale { get; }
/// <summary>
/// Gets the 2d gradient operator.
/// </summary>
public DenseMatrix<float> KernelXY { get; }
/// <inheritdoc/>
protected override void BeforeImageApply()
{
@ -48,7 +42,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
opaque.Execute();
}
if (this.Grayscale)
if (this.grayscale)
{
new GrayscaleBt709Processor(1F).Execute(this.Configuration, this.Source, this.SourceRectangle);
}
@ -59,10 +53,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> source)
{
using (var processor = new ConvolutionProcessor<TPixel>(this.Configuration, this.KernelXY, true, this.Source, this.SourceRectangle))
{
processor.Apply(source);
}
using var processor = new ConvolutionProcessor<TPixel>(this.Configuration, in this.kernelXY, true, this.Source, this.SourceRectangle);
processor.Apply(source);
}
}
}

31
src/ImageSharp/Processing/Processors/Convolution/KayyaliProcessor.cs

@ -1,31 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{
/// <summary>
/// Defines edge detection processing using the Kayyali operator filter.
/// See <see href="http://edgedetection.webs.com/"/>.
/// </summary>
public sealed class KayyaliProcessor : EdgeDetectorProcessor
{
/// <summary>
/// Initializes a new instance of the <see cref="KayyaliProcessor"/> class.
/// </summary>
/// <param name="grayscale">Whether to convert the image to grayscale before performing edge detection.</param>
public KayyaliProcessor(bool grayscale)
: base(grayscale)
{
}
/// <inheritdoc />
public override IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Configuration configuration, Image<TPixel> source, Rectangle sourceRectangle)
=> new EdgeDetector2DProcessor<TPixel>(
configuration,
KayyaliKernels.KayyaliX,
KayyaliKernels.KayyaliY,
this.Grayscale,
source,
sourceRectangle);
}
}

55
src/ImageSharp/Processing/Processors/Convolution/Kernels/CompassKernels.cs

@ -1,55 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{
internal abstract class CompassKernels
{
/// <summary>
/// Gets the North gradient operator.
/// </summary>
public abstract DenseMatrix<float> North { get; }
/// <summary>
/// Gets the NorthWest gradient operator.
/// </summary>
public abstract DenseMatrix<float> NorthWest { get; }
/// <summary>
/// Gets the West gradient operator.
/// </summary>
public abstract DenseMatrix<float> West { get; }
/// <summary>
/// Gets the SouthWest gradient operator.
/// </summary>
public abstract DenseMatrix<float> SouthWest { get; }
/// <summary>
/// Gets the South gradient operator.
/// </summary>
public abstract DenseMatrix<float> South { get; }
/// <summary>
/// Gets the SouthEast gradient operator.
/// </summary>
public abstract DenseMatrix<float> SouthEast { get; }
/// <summary>
/// Gets the East gradient operator.
/// </summary>
public abstract DenseMatrix<float> East { get; }
/// <summary>
/// Gets the NorthEast gradient operator.
/// </summary>
public abstract DenseMatrix<float> NorthEast { get; }
public DenseMatrix<float>[] Flatten() =>
new[]
{
this.North, this.NorthWest, this.West, this.SouthWest,
this.South, this.SouthEast, this.East, this.NorthEast
};
}
}

103
src/ImageSharp/Processing/Processors/Convolution/Kernels/EdgeDetector2DKernel.cs

@ -0,0 +1,103 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{
/// <summary>
/// Represents an edge detection convolution kernel consisting of two 1D gradient operators.
/// </summary>
public readonly struct EdgeDetector2DKernel : IEquatable<EdgeDetector2DKernel>
{
/// <summary>
/// An edge detection kernel containing two Kayyali operators.
/// </summary>
public static EdgeDetector2DKernel KayyaliKernel = new EdgeDetector2DKernel(KayyaliKernels.KayyaliX, KayyaliKernels.KayyaliY);
/// <summary>
/// An edge detection kernel containing two Prewitt operators.
/// <see href="https://en.wikipedia.org/wiki/Prewitt_operator"/>.
/// </summary>
public static EdgeDetector2DKernel PrewittKernel = new EdgeDetector2DKernel(PrewittKernels.PrewittX, PrewittKernels.PrewittY);
/// <summary>
/// An edge detection kernel containing two Roberts-Cross operators.
/// <see href="https://en.wikipedia.org/wiki/Roberts_cross"/>.
/// </summary>
public static EdgeDetector2DKernel RobertsCrossKernel = new EdgeDetector2DKernel(RobertsCrossKernels.RobertsCrossX, RobertsCrossKernels.RobertsCrossY);
/// <summary>
/// An edge detection kernel containing two Scharr operators.
/// </summary>
public static EdgeDetector2DKernel ScharrKernel = new EdgeDetector2DKernel(ScharrKernels.ScharrX, ScharrKernels.ScharrY);
/// <summary>
/// An edge detection kernel containing two Sobel operators.
/// <see href="https://en.wikipedia.org/wiki/Sobel_operator"/>.
/// </summary>
public static EdgeDetector2DKernel SobelKernel = new EdgeDetector2DKernel(SobelKernels.SobelX, SobelKernels.SobelY);
/// <summary>
/// Initializes a new instance of the <see cref="EdgeDetector2DKernel"/> struct.
/// </summary>
/// <param name="kernelX">The horizontal gradient operator.</param>
/// <param name="kernelY">The vertical gradient operator.</param>
public EdgeDetector2DKernel(DenseMatrix<float> kernelX, DenseMatrix<float> kernelY)
{
Guard.IsTrue(
kernelX.Size.Equals(kernelY.Size),
$"{nameof(kernelX)} {nameof(kernelY)}",
"Kernel sizes must be the same.");
this.KernelX = kernelX;
this.KernelY = kernelY;
}
/// <summary>
/// Gets the horizontal gradient operator.
/// </summary>
public DenseMatrix<float> KernelX { get; }
/// <summary>
/// Gets the vertical gradient operator.
/// </summary>
public DenseMatrix<float> KernelY { get; }
/// <summary>
/// Checks whether two <see cref="EdgeDetector2DKernel"/> structures are equal.
/// </summary>
/// <param name="left">The left hand <see cref="EdgeDetector2DKernel"/> operand.</param>
/// <param name="right">The right hand <see cref="EdgeDetector2DKernel"/> operand.</param>
/// <returns>
/// True if the <paramref name="left"/> parameter is equal to the <paramref name="right"/> parameter;
/// otherwise, false.
/// </returns>
public static bool operator ==(EdgeDetector2DKernel left, EdgeDetector2DKernel right)
=> left.Equals(right);
/// <summary>
/// Checks whether two <see cref="EdgeDetector2DKernel"/> structures are equal.
/// </summary>
/// <param name="left">The left hand <see cref="EdgeDetector2DKernel"/> operand.</param>
/// <param name="right">The right hand <see cref="EdgeDetector2DKernel"/> operand.</param>
/// <returns>
/// True if the <paramref name="left"/> parameter is not equal to the <paramref name="right"/> parameter;
/// otherwise, false.
/// </returns>
public static bool operator !=(EdgeDetector2DKernel left, EdgeDetector2DKernel right)
=> !(left == right);
/// <inheritdoc/>
public override bool Equals(object obj)
=> obj is EdgeDetector2DKernel kernel && this.Equals(kernel);
/// <inheritdoc/>
public bool Equals(EdgeDetector2DKernel other)
=> this.KernelX.Equals(other.KernelX)
&& this.KernelY.Equals(other.KernelY);
/// <inheritdoc/>
public override int GetHashCode() => HashCode.Combine(this.KernelX, this.KernelY);
}
}

163
src/ImageSharp/Processing/Processors/Convolution/Kernels/EdgeDetectorCompassKernel.cs

@ -0,0 +1,163 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{
/// <summary>
/// Represents an edge detection convolution kernel consisting of eight gradient operators.
/// </summary>
public readonly struct EdgeDetectorCompassKernel : IEquatable<EdgeDetectorCompassKernel>
{
/// <summary>
/// An edge detection kenel comprised of Kirsch gradient operators.
/// <see href="http://en.wikipedia.org/wiki/Kirsch_operator"/>.
/// </summary>
public static EdgeDetectorCompassKernel Kirsch =
new EdgeDetectorCompassKernel(
KirschKernels.North,
KirschKernels.NorthWest,
KirschKernels.West,
KirschKernels.SouthWest,
KirschKernels.South,
KirschKernels.SouthEast,
KirschKernels.East,
KirschKernels.NorthEast);
/// <summary>
/// An edge detection kenel comprised of Robinson gradient operators.
/// <see href="http://www.tutorialspoint.com/dip/Robinson_Compass_Mask.htm"/>
/// </summary>
public static EdgeDetectorCompassKernel Robinson =
new EdgeDetectorCompassKernel(
RobinsonKernels.North,
RobinsonKernels.NorthWest,
RobinsonKernels.West,
RobinsonKernels.SouthWest,
RobinsonKernels.South,
RobinsonKernels.SouthEast,
RobinsonKernels.East,
RobinsonKernels.NorthEast);
/// <summary>
/// Initializes a new instance of the <see cref="EdgeDetectorCompassKernel"/> struct.
/// </summary>
/// <param name="north">The north gradient operator.</param>
/// <param name="northWest">The north-west gradient operator.</param>
/// <param name="west">The west gradient operator.</param>
/// <param name="southWest">The south-west gradient operator.</param>
/// <param name="south">The south gradient operator.</param>
/// <param name="southEast">The south-east gradient operator.</param>
/// <param name="east">The east gradient operator.</param>
/// <param name="northEast">The north-east gradient operator.</param>
public EdgeDetectorCompassKernel(
DenseMatrix<float> north,
DenseMatrix<float> northWest,
DenseMatrix<float> west,
DenseMatrix<float> southWest,
DenseMatrix<float> south,
DenseMatrix<float> southEast,
DenseMatrix<float> east,
DenseMatrix<float> northEast)
{
this.North = north;
this.NorthWest = northWest;
this.West = west;
this.SouthWest = southWest;
this.South = south;
this.SouthEast = southEast;
this.East = east;
this.NorthEast = northEast;
}
/// <summary>
/// Gets the North gradient operator.
/// </summary>
public DenseMatrix<float> North { get; }
/// <summary>
/// Gets the NorthWest gradient operator.
/// </summary>
public DenseMatrix<float> NorthWest { get; }
/// <summary>
/// Gets the West gradient operator.
/// </summary>
public DenseMatrix<float> West { get; }
/// <summary>
/// Gets the SouthWest gradient operator.
/// </summary>
public DenseMatrix<float> SouthWest { get; }
/// <summary>
/// Gets the South gradient operator.
/// </summary>
public DenseMatrix<float> South { get; }
/// <summary>
/// Gets the SouthEast gradient operator.
/// </summary>
public DenseMatrix<float> SouthEast { get; }
/// <summary>
/// Gets the East gradient operator.
/// </summary>
public DenseMatrix<float> East { get; }
/// <summary>
/// Gets the NorthEast gradient operator.
/// </summary>
public DenseMatrix<float> NorthEast { get; }
/// <summary>
/// Checks whether two <see cref="EdgeDetectorCompassKernel"/> structures are equal.
/// </summary>
/// <param name="left">The left hand <see cref="EdgeDetectorCompassKernel"/> operand.</param>
/// <param name="right">The right hand <see cref="EdgeDetectorCompassKernel"/> operand.</param>
/// <returns>
/// True if the <paramref name="left"/> parameter is equal to the <paramref name="right"/> parameter;
/// otherwise, false.
/// </returns>
public static bool operator ==(EdgeDetectorCompassKernel left, EdgeDetectorCompassKernel right)
=> left.Equals(right);
/// <summary>
/// Checks whether two <see cref="EdgeDetectorCompassKernel"/> structures are equal.
/// </summary>
/// <param name="left">The left hand <see cref="EdgeDetectorCompassKernel"/> operand.</param>
/// <param name="right">The right hand <see cref="EdgeDetectorCompassKernel"/> operand.</param>
/// <returns>
/// True if the <paramref name="left"/> parameter is not equal to the <paramref name="right"/> parameter;
/// otherwise, false.
/// </returns>
public static bool operator !=(EdgeDetectorCompassKernel left, EdgeDetectorCompassKernel right)
=> !(left == right);
/// <inheritdoc/>
public override bool Equals(object obj) => obj is EdgeDetectorCompassKernel kernel && this.Equals(kernel);
/// <inheritdoc/>
public bool Equals(EdgeDetectorCompassKernel other) => this.North.Equals(other.North) && this.NorthWest.Equals(other.NorthWest) && this.West.Equals(other.West) && this.SouthWest.Equals(other.SouthWest) && this.South.Equals(other.South) && this.SouthEast.Equals(other.SouthEast) && this.East.Equals(other.East) && this.NorthEast.Equals(other.NorthEast);
/// <inheritdoc/>
public override int GetHashCode()
=> HashCode.Combine(
this.North,
this.NorthWest,
this.West,
this.SouthWest,
this.South,
this.SouthEast,
this.East,
this.NorthEast);
internal DenseMatrix<float>[] Flatten() =>
new[]
{
this.North, this.NorthWest, this.West, this.SouthWest,
this.South, this.SouthEast, this.East, this.NorthEast
};
}
}

78
src/ImageSharp/Processing/Processors/Convolution/Kernels/EdgeDetectorKernel.cs

@ -0,0 +1,78 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{
/// <summary>
/// Represents an edge detection convolution kernel consisting of a single 2D gradient operator.
/// </summary>
public readonly struct EdgeDetectorKernel : IEquatable<EdgeDetectorKernel>
{
/// <summary>
/// An edge detection kernel containing a 3x3 Laplacian operator.
/// <see href="http://en.wikipedia.org/wiki/Discrete_Laplace_operator"/>
/// </summary>
public static EdgeDetectorKernel Laplacian3x3 = new EdgeDetectorKernel(LaplacianKernels.Laplacian3x3);
/// <summary>
/// An edge detection kernel containing a 5x5 Laplacian operator.
/// <see href="http://en.wikipedia.org/wiki/Discrete_Laplace_operator"/>
/// </summary>
public static EdgeDetectorKernel Laplacian5x5 = new EdgeDetectorKernel(LaplacianKernels.Laplacian5x5);
/// <summary>
/// An edge detection kernel containing a Laplacian of Gaussian operator.
/// <see href="http://fourier.eng.hmc.edu/e161/lectures/gradient/node8.html"/>.
/// </summary>
public static EdgeDetectorKernel LaplacianOfGaussian = new EdgeDetectorKernel(LaplacianKernels.LaplacianOfGaussianXY);
/// <summary>
/// Initializes a new instance of the <see cref="EdgeDetectorKernel"/> struct.
/// </summary>
/// <param name="kernelXY">The 2D gradient operator.</param>
public EdgeDetectorKernel(DenseMatrix<float> kernelXY)
=> this.KernelXY = kernelXY;
/// <summary>
/// Gets the 2D gradient operator.
/// </summary>
public DenseMatrix<float> KernelXY { get; }
/// <summary>
/// Checks whether two <see cref="EdgeDetectorKernel"/> structures are equal.
/// </summary>
/// <param name="left">The left hand <see cref="EdgeDetectorKernel"/> operand.</param>
/// <param name="right">The right hand <see cref="EdgeDetectorKernel"/> operand.</param>
/// <returns>
/// True if the <paramref name="left"/> parameter is equal to the <paramref name="right"/> parameter;
/// otherwise, false.
/// </returns>
public static bool operator ==(EdgeDetectorKernel left, EdgeDetectorKernel right)
=> left.Equals(right);
/// <summary>
/// Checks whether two <see cref="EdgeDetectorKernel"/> structures are equal.
/// </summary>
/// <param name="left">The left hand <see cref="EdgeDetectorKernel"/> operand.</param>
/// <param name="right">The right hand <see cref="EdgeDetectorKernel"/> operand.</param>
/// <returns>
/// True if the <paramref name="left"/> parameter is not equal to the <paramref name="right"/> parameter;
/// otherwise, false.
/// </returns>
public static bool operator !=(EdgeDetectorKernel left, EdgeDetectorKernel right)
=> !(left == right);
/// <inheritdoc/>
public override bool Equals(object obj)
=> obj is EdgeDetectorKernel kernel && this.Equals(kernel);
/// <inheritdoc/>
public bool Equals(EdgeDetectorKernel other)
=> this.KernelXY.Equals(other.KernelXY);
/// <inheritdoc/>
public override int GetHashCode() => this.KernelXY.GetHashCode();
}
}

0
src/ImageSharp/Processing/Processors/Convolution/Kernels/KayyaliKernels.cs → src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/KayyaliKernels.cs

25
src/ImageSharp/Processing/Processors/Convolution/Kernels/KirschKernels.cs → src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/KirschKernels.cs

@ -1,17 +1,18 @@
// Copyright (c) Six Labors.
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{
/// <summary>
/// Contains the eight matrices used for Kirsch edge detection
/// Contains the eight matrices used for Kirsch edge detection.
/// <see href="http://en.wikipedia.org/wiki/Kirsch_operator"/>.
/// </summary>
internal class KirschKernels : CompassKernels
internal static class KirschKernels
{
/// <summary>
/// Gets the North gradient operator
/// </summary>
public override DenseMatrix<float> North =>
public static DenseMatrix<float> North =>
new float[,]
{
{ 5, 5, 5 },
@ -22,7 +23,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// <summary>
/// Gets the NorthWest gradient operator
/// </summary>
public override DenseMatrix<float> NorthWest =>
public static DenseMatrix<float> NorthWest =>
new float[,]
{
{ 5, 5, -3 },
@ -33,7 +34,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// <summary>
/// Gets the West gradient operator
/// </summary>
public override DenseMatrix<float> West =>
public static DenseMatrix<float> West =>
new float[,]
{
{ 5, -3, -3 },
@ -44,7 +45,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// <summary>
/// Gets the SouthWest gradient operator
/// </summary>
public override DenseMatrix<float> SouthWest =>
public static DenseMatrix<float> SouthWest =>
new float[,]
{
{ -3, -3, -3 },
@ -55,7 +56,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// <summary>
/// Gets the South gradient operator
/// </summary>
public override DenseMatrix<float> South =>
public static DenseMatrix<float> South =>
new float[,]
{
{ -3, -3, -3 },
@ -66,7 +67,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// <summary>
/// Gets the SouthEast gradient operator
/// </summary>
public override DenseMatrix<float> SouthEast =>
public static DenseMatrix<float> SouthEast =>
new float[,]
{
{ -3, -3, -3 },
@ -77,7 +78,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// <summary>
/// Gets the East gradient operator
/// </summary>
public override DenseMatrix<float> East =>
public static DenseMatrix<float> East =>
new float[,]
{
{ -3, -3, 5 },
@ -88,7 +89,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// <summary>
/// Gets the NorthEast gradient operator
/// </summary>
public override DenseMatrix<float> NorthEast =>
public static DenseMatrix<float> NorthEast =>
new float[,]
{
{ -3, 5, 5 },
@ -96,4 +97,4 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{ -3, -3, -3 }
};
}
}
}

0
src/ImageSharp/Processing/Processors/Convolution/Kernels/LaplacianKernelFactory.cs → src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/LaplacianKernelFactory.cs

8
src/ImageSharp/Processing/Processors/Convolution/Kernels/LaplacianKernels.cs → src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/LaplacianKernels.cs

@ -1,10 +1,12 @@
// Copyright (c) Six Labors.
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{
/// <summary>
/// Contains Laplacian kernels of different sizes
/// Contains Laplacian kernels of different sizes.
/// <see href="http://en.wikipedia.org/wiki/Discrete_Laplace_operator"/>
/// <see href="http://fourier.eng.hmc.edu/e161/lectures/gradient/node8.html"/>.
/// </summary>
internal static class LaplacianKernels
{
@ -31,4 +33,4 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{ 0, 0, -1, 0, 0 }
};
}
}
}

0
src/ImageSharp/Processing/Processors/Convolution/Kernels/PrewittKernels.cs → src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/PrewittKernels.cs

0
src/ImageSharp/Processing/Processors/Convolution/Kernels/RobertsCrossKernels.cs → src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/RobertsCrossKernels.cs

23
src/ImageSharp/Processing/Processors/Convolution/Kernels/RobinsonKernels.cs → src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/RobinsonKernels.cs

@ -1,17 +1,18 @@
// Copyright (c) Six Labors.
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{
/// <summary>
/// Contains the kernels used for Robinson edge detection.
/// <see href="http://www.tutorialspoint.com/dip/Robinson_Compass_Mask.htm"/>
/// </summary>
internal class RobinsonKernels : CompassKernels
internal static class RobinsonKernels
{
/// <summary>
/// Gets the North gradient operator
/// </summary>
public override DenseMatrix<float> North =>
public static DenseMatrix<float> North =>
new float[,]
{
{ 1, 2, 1 },
@ -22,7 +23,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// <summary>
/// Gets the NorthWest gradient operator
/// </summary>
public override DenseMatrix<float> NorthWest =>
public static DenseMatrix<float> NorthWest =>
new float[,]
{
{ 2, 1, 0 },
@ -33,7 +34,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// <summary>
/// Gets the West gradient operator
/// </summary>
public override DenseMatrix<float> West =>
public static DenseMatrix<float> West =>
new float[,]
{
{ 1, 0, -1 },
@ -44,7 +45,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// <summary>
/// Gets the SouthWest gradient operator
/// </summary>
public override DenseMatrix<float> SouthWest =>
public static DenseMatrix<float> SouthWest =>
new float[,]
{
{ 0, -1, -2 },
@ -55,7 +56,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// <summary>
/// Gets the South gradient operator
/// </summary>
public override DenseMatrix<float> South =>
public static DenseMatrix<float> South =>
new float[,]
{
{ -1, -2, -1 },
@ -66,7 +67,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// <summary>
/// Gets the SouthEast gradient operator
/// </summary>
public override DenseMatrix<float> SouthEast =>
public static DenseMatrix<float> SouthEast =>
new float[,]
{
{ -2, -1, 0 },
@ -77,7 +78,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// <summary>
/// Gets the East gradient operator
/// </summary>
public override DenseMatrix<float> East =>
public static DenseMatrix<float> East =>
new float[,]
{
{ -1, 0, 1 },
@ -88,7 +89,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// <summary>
/// Gets the NorthEast gradient operator
/// </summary>
public override DenseMatrix<float> NorthEast =>
public static DenseMatrix<float> NorthEast =>
new float[,]
{
{ 0, 1, 2 },
@ -96,4 +97,4 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{ -2, -1, 0 }
};
}
}
}

0
src/ImageSharp/Processing/Processors/Convolution/Kernels/ScharrKernels.cs → src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/ScharrKernels.cs

0
src/ImageSharp/Processing/Processors/Convolution/Kernels/SobelKernels.cs → src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/SobelKernels.cs

25
src/ImageSharp/Processing/Processors/Convolution/KirschProcessor.cs

@ -1,25 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{
/// <summary>
/// Defines edge detection using the Kirsch operator filter.
/// See <see href="http://en.wikipedia.org/wiki/Kirsch_operator"/>.
/// </summary>
public sealed class KirschProcessor : EdgeDetectorProcessor
{
/// <summary>
/// Initializes a new instance of the <see cref="KirschProcessor"/> class.
/// </summary>
/// <param name="grayscale">Whether to convert the image to grayscale before performing edge detection.</param>
public KirschProcessor(bool grayscale)
: base(grayscale)
{
}
/// <inheritdoc />
public override IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Configuration configuration, Image<TPixel> source, Rectangle sourceRectangle)
=> new EdgeDetectorCompassProcessor<TPixel>(configuration, new KirschKernels(), this.Grayscale, source, sourceRectangle);
}
}

25
src/ImageSharp/Processing/Processors/Convolution/Laplacian3x3Processor.cs

@ -1,25 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{
/// <summary>
/// Applies edge detection processing to the image using the Laplacian 3x3 operator filter.
/// <see href="http://en.wikipedia.org/wiki/Discrete_Laplace_operator"/>
/// </summary>
public sealed class Laplacian3x3Processor : EdgeDetectorProcessor
{
/// <summary>
/// Initializes a new instance of the <see cref="Laplacian3x3Processor"/> class.
/// </summary>
/// <param name="grayscale">Whether to convert the image to grayscale before performing edge detection.</param>
public Laplacian3x3Processor(bool grayscale)
: base(grayscale)
{
}
/// <inheritdoc />
public override IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Configuration configuration, Image<TPixel> source, Rectangle sourceRectangle)
=> new EdgeDetectorProcessor<TPixel>(configuration, LaplacianKernels.Laplacian3x3, this.Grayscale, source, sourceRectangle);
}
}

25
src/ImageSharp/Processing/Processors/Convolution/Laplacian5x5Processor.cs

@ -1,25 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{
/// <summary>
/// Defines edge detection processing using the Laplacian 5x5 operator filter.
/// <see href="http://en.wikipedia.org/wiki/Discrete_Laplace_operator"/>.
/// </summary>
public sealed class Laplacian5x5Processor : EdgeDetectorProcessor
{
/// <summary>
/// Initializes a new instance of the <see cref="Laplacian5x5Processor"/> class.
/// </summary>
/// <param name="grayscale">Whether to convert the image to grayscale before performing edge detection.</param>
public Laplacian5x5Processor(bool grayscale)
: base(grayscale)
{
}
/// <inheritdoc />
public override IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Configuration configuration, Image<TPixel> source, Rectangle sourceRectangle)
=> new EdgeDetectorProcessor<TPixel>(configuration, LaplacianKernels.Laplacian5x5, this.Grayscale, source, sourceRectangle);
}
}

25
src/ImageSharp/Processing/Processors/Convolution/LaplacianOfGaussianProcessor.cs

@ -1,25 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{
/// <summary>
/// Applies edge detection processing to the image using the Laplacian of Gaussian operator filter.
/// See <see href="http://fourier.eng.hmc.edu/e161/lectures/gradient/node8.html"/>.
/// </summary>
public sealed class LaplacianOfGaussianProcessor : EdgeDetectorProcessor
{
/// <summary>
/// Initializes a new instance of the <see cref="LaplacianOfGaussianProcessor"/> class.
/// </summary>
/// <param name="grayscale">Whether to convert the image to grayscale before performing edge detection.</param>
public LaplacianOfGaussianProcessor(bool grayscale)
: base(grayscale)
{
}
/// <inheritdoc />
public override IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Configuration configuration, Image<TPixel> source, Rectangle sourceRectangle)
=> new EdgeDetectorProcessor<TPixel>(configuration, LaplacianKernels.LaplacianOfGaussianXY, this.Grayscale, source, sourceRectangle);
}
}

31
src/ImageSharp/Processing/Processors/Convolution/PrewittProcessor.cs

@ -1,31 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{
/// <summary>
/// Defines edge detection using the Prewitt operator filter.
/// See <see href="http://en.wikipedia.org/wiki/Prewitt_operator"/>.
/// </summary>
public sealed class PrewittProcessor : EdgeDetectorProcessor
{
/// <summary>
/// Initializes a new instance of the <see cref="PrewittProcessor"/> class.
/// </summary>
/// <param name="grayscale">Whether to convert the image to grayscale before performing edge detection.</param>
public PrewittProcessor(bool grayscale)
: base(grayscale)
{
}
/// <inheritdoc />
public override IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Configuration configuration, Image<TPixel> source, Rectangle sourceRectangle)
=> new EdgeDetector2DProcessor<TPixel>(
configuration,
PrewittKernels.PrewittX,
PrewittKernels.PrewittY,
this.Grayscale,
source,
sourceRectangle);
}
}

31
src/ImageSharp/Processing/Processors/Convolution/RobertsCrossProcessor.cs

@ -1,31 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{
/// <summary>
/// Defines edge detection processing using the Roberts Cross operator filter.
/// See <see href="http://en.wikipedia.org/wiki/Roberts_cross"/>.
/// </summary>
public sealed class RobertsCrossProcessor : EdgeDetectorProcessor
{
/// <summary>
/// Initializes a new instance of the <see cref="RobertsCrossProcessor"/> class.
/// </summary>
/// <param name="grayscale">Whether to convert the image to grayscale before performing edge detection.</param>
public RobertsCrossProcessor(bool grayscale)
: base(grayscale)
{
}
/// <inheritdoc />
public override IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Configuration configuration, Image<TPixel> source, Rectangle sourceRectangle)
=> new EdgeDetector2DProcessor<TPixel>(
configuration,
RobertsCrossKernels.RobertsCrossX,
RobertsCrossKernels.RobertsCrossY,
this.Grayscale,
source,
sourceRectangle);
}
}

25
src/ImageSharp/Processing/Processors/Convolution/RobinsonProcessor.cs

@ -1,25 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{
/// <summary>
/// Defines edge detection using the Robinson operator filter.
/// See <see href="http://www.tutorialspoint.com/dip/Robinson_Compass_Mask.htm"/>.
/// </summary>
public sealed class RobinsonProcessor : EdgeDetectorProcessor
{
/// <summary>
/// Initializes a new instance of the <see cref="RobinsonProcessor"/> class.
/// </summary>
/// <param name="grayscale">Whether to convert the image to grayscale before performing edge detection.</param>
public RobinsonProcessor(bool grayscale)
: base(grayscale)
{
}
/// <inheritdoc />
public override IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Configuration configuration, Image<TPixel> source, Rectangle sourceRectangle)
=> new EdgeDetectorCompassProcessor<TPixel>(configuration, new RobinsonKernels(), this.Grayscale, source, sourceRectangle);
}
}

31
src/ImageSharp/Processing/Processors/Convolution/ScharrProcessor.cs

@ -1,31 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{
/// <summary>
/// Defines edge detection processing using the Scharr operator filter.
/// <see href="http://en.wikipedia.org/wiki/Sobel_operator#Alternative_operators"/>
/// </summary>
public sealed class ScharrProcessor : EdgeDetectorProcessor
{
/// <summary>
/// Initializes a new instance of the <see cref="ScharrProcessor"/> class.
/// </summary>
/// <param name="grayscale">Whether to convert the image to grayscale before performing edge detection.</param>
public ScharrProcessor(bool grayscale)
: base(grayscale)
{
}
/// <inheritdoc />
public override IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Configuration configuration, Image<TPixel> source, Rectangle sourceRectangle)
=> new EdgeDetector2DProcessor<TPixel>(
configuration,
ScharrKernels.ScharrX,
ScharrKernels.ScharrY,
this.Grayscale,
source,
sourceRectangle);
}
}

31
src/ImageSharp/Processing/Processors/Convolution/SobelProcessor.cs

@ -1,31 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{
/// <summary>
/// Defines edge detection using the Sobel operator filter.
/// See <see href="http://en.wikipedia.org/wiki/Sobel_operator"/>.
/// </summary>
public sealed class SobelProcessor : EdgeDetectorProcessor
{
/// <summary>
/// Initializes a new instance of the <see cref="SobelProcessor"/> class.
/// </summary>
/// <param name="grayscale">Whether to convert the image to grayscale before performing edge detection.</param>
public SobelProcessor(bool grayscale)
: base(grayscale)
{
}
/// <inheritdoc />
public override IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Configuration configuration, Image<TPixel> source, Rectangle sourceRectangle)
=> new EdgeDetector2DProcessor<TPixel>(
configuration,
SobelKernels.SobelX,
SobelKernels.SobelY,
this.Grayscale,
source,
sourceRectangle);
}
}

2
src/ImageSharp/Processing/Processors/Transforms/EntropyCropProcessor{TPixel}.cs

@ -42,7 +42,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
Configuration configuration = this.Source.GetConfiguration();
// Detect the edges.
new SobelProcessor(false).Execute(this.Configuration, temp, this.SourceRectangle);
new EdgeDetector2DProcessor(KnownEdgeDetectorKernels.Sobel, false).Execute(this.Configuration, temp, this.SourceRectangle);
// Apply threshold binarization filter.
new BinaryThresholdProcessor(this.definition.Threshold).Execute(this.Configuration, temp, this.SourceRectangle);

22
tests/ImageSharp.Benchmarks/Samplers/DetectEdges.cs

@ -37,17 +37,17 @@ namespace SixLabors.ImageSharp.Benchmarks
[Benchmark(Description = "ImageSharp DetectEdges")]
public void ImageProcessorCoreDetectEdges()
{
this.image.Mutate(x => x.DetectEdges(EdgeDetectionOperators.Kayyali));
this.image.Mutate(x => x.DetectEdges(EdgeDetectionOperators.Kayyali));
this.image.Mutate(x => x.DetectEdges(EdgeDetectionOperators.Kirsch));
this.image.Mutate(x => x.DetectEdges(EdgeDetectionOperators.Laplacian3x3));
this.image.Mutate(x => x.DetectEdges(EdgeDetectionOperators.Laplacian5x5));
this.image.Mutate(x => x.DetectEdges(EdgeDetectionOperators.LaplacianOfGaussian));
this.image.Mutate(x => x.DetectEdges(EdgeDetectionOperators.Prewitt));
this.image.Mutate(x => x.DetectEdges(EdgeDetectionOperators.RobertsCross));
this.image.Mutate(x => x.DetectEdges(EdgeDetectionOperators.Robinson));
this.image.Mutate(x => x.DetectEdges(EdgeDetectionOperators.Scharr));
this.image.Mutate(x => x.DetectEdges(EdgeDetectionOperators.Sobel));
this.image.Mutate(x => x.DetectEdges(KnownEdgeDetectorKernels.Kayyali));
this.image.Mutate(x => x.DetectEdges(KnownEdgeDetectorKernels.Kayyali));
this.image.Mutate(x => x.DetectEdges(KnownEdgeDetectorKernels.Kirsch));
this.image.Mutate(x => x.DetectEdges(KnownEdgeDetectorKernels.Laplacian3x3));
this.image.Mutate(x => x.DetectEdges(KnownEdgeDetectorKernels.Laplacian5x5));
this.image.Mutate(x => x.DetectEdges(KnownEdgeDetectorKernels.LaplacianOfGaussian));
this.image.Mutate(x => x.DetectEdges(KnownEdgeDetectorKernels.Prewitt));
this.image.Mutate(x => x.DetectEdges(KnownEdgeDetectorKernels.RobertsCross));
this.image.Mutate(x => x.DetectEdges(KnownEdgeDetectorKernels.Robinson));
this.image.Mutate(x => x.DetectEdges(KnownEdgeDetectorKernels.Scharr));
this.image.Mutate(x => x.DetectEdges(KnownEdgeDetectorKernels.Sobel));
}
}
}

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

@ -1,75 +1,201 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors.Convolution;
using SixLabors.ImageSharp.Tests.TestUtilities;
using Xunit;
namespace SixLabors.ImageSharp.Tests.Processing.Convolution
{
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1313:Parameter names should begin with lower-case letter", Justification = "OK. Used for TheoryData compatibility.")]
public class DetectEdgesTest : BaseImageOperationsExtensionTest
{
[Fact]
public void DetectEdges_SobelProcessorDefaultsSet()
public void DetectEdges_EdgeDetector2DProcessorDefaultsSet()
{
this.operations.DetectEdges();
EdgeDetector2DProcessor processor = this.Verify<EdgeDetector2DProcessor>();
// TODO: Enable once we have updated the images
SobelProcessor processor = this.Verify<SobelProcessor>();
Assert.True(processor.Grayscale);
Assert.Equal(KnownEdgeDetectorKernels.Sobel, processor.Kernel);
}
[Fact]
public void DetectEdges_Rect_SobelProcessorDefaultsSet()
public void DetectEdges_Rect_EdgeDetector2DProcessorDefaultsSet()
{
this.operations.DetectEdges(this.rect);
EdgeDetector2DProcessor processor = this.Verify<EdgeDetector2DProcessor>(this.rect);
Assert.True(processor.Grayscale);
Assert.Equal(KnownEdgeDetectorKernels.Sobel, processor.Kernel);
}
public static TheoryData<EdgeDetector2DKernel, bool> EdgeDetector2DKernelData =
new TheoryData<EdgeDetector2DKernel, bool>
{
{ KnownEdgeDetectorKernels.Kayyali, true },
{ KnownEdgeDetectorKernels.Kayyali, false },
{ KnownEdgeDetectorKernels.Prewitt, true },
{ KnownEdgeDetectorKernels.Prewitt, false },
{ KnownEdgeDetectorKernels.RobertsCross, true },
{ KnownEdgeDetectorKernels.RobertsCross, false },
{ KnownEdgeDetectorKernels.Scharr, true },
{ KnownEdgeDetectorKernels.Scharr, false },
{ KnownEdgeDetectorKernels.Sobel, true },
{ KnownEdgeDetectorKernels.Sobel, false },
};
[Theory]
[MemberData(nameof(EdgeDetector2DKernelData))]
public void DetectEdges_EdgeDetector2DProcessor_DefaultGrayScale_Set(EdgeDetector2DKernel kernel, bool _)
{
this.operations.DetectEdges(kernel);
EdgeDetector2DProcessor processor = this.Verify<EdgeDetector2DProcessor>();
Assert.True(processor.Grayscale);
Assert.Equal(kernel, processor.Kernel);
}
[Theory]
[MemberData(nameof(EdgeDetector2DKernelData))]
public void DetectEdges_Rect_EdgeDetector2DProcessor_DefaultGrayScale_Set(EdgeDetector2DKernel kernel, bool _)
{
this.operations.DetectEdges(kernel, this.rect);
EdgeDetector2DProcessor processor = this.Verify<EdgeDetector2DProcessor>(this.rect);
Assert.True(processor.Grayscale);
Assert.Equal(kernel, processor.Kernel);
}
[Theory]
[MemberData(nameof(EdgeDetector2DKernelData))]
public void DetectEdges_EdgeDetector2DProcessorSet(EdgeDetector2DKernel kernel, bool grayscale)
{
this.operations.DetectEdges(kernel, grayscale);
EdgeDetector2DProcessor processor = this.Verify<EdgeDetector2DProcessor>();
Assert.Equal(grayscale, processor.Grayscale);
Assert.Equal(kernel, processor.Kernel);
}
[Theory]
[MemberData(nameof(EdgeDetector2DKernelData))]
public void DetectEdges_Rect_EdgeDetector2DProcessorSet(EdgeDetector2DKernel kernel, bool grayscale)
{
this.operations.DetectEdges(kernel, grayscale, this.rect);
EdgeDetector2DProcessor processor = this.Verify<EdgeDetector2DProcessor>(this.rect);
Assert.Equal(grayscale, processor.Grayscale);
Assert.Equal(kernel, processor.Kernel);
}
public static TheoryData<EdgeDetectorKernel, bool> EdgeDetectorKernelData =
new TheoryData<EdgeDetectorKernel, bool>
{
{ KnownEdgeDetectorKernels.Laplacian3x3, true },
{ KnownEdgeDetectorKernels.Laplacian3x3, false },
{ KnownEdgeDetectorKernels.Laplacian5x5, true },
{ KnownEdgeDetectorKernels.Laplacian5x5, false },
{ KnownEdgeDetectorKernels.LaplacianOfGaussian, true },
{ KnownEdgeDetectorKernels.LaplacianOfGaussian, false },
};
[Theory]
[MemberData(nameof(EdgeDetectorKernelData))]
public void DetectEdges_EdgeDetectorProcessor_DefaultGrayScale_Set(EdgeDetectorKernel kernel, bool _)
{
this.operations.DetectEdges(kernel);
EdgeDetectorProcessor processor = this.Verify<EdgeDetectorProcessor>();
// TODO: Enable once we have updated the images
SobelProcessor processor = this.Verify<SobelProcessor>(this.rect);
Assert.True(processor.Grayscale);
Assert.Equal(kernel, processor.Kernel);
}
public static IEnumerable<object[]> EdgeDetectionTheoryData => new[]
[Theory]
[MemberData(nameof(EdgeDetectorKernelData))]
public void DetectEdges_Rect_EdgeDetectorProcessor_DefaultGrayScale_Set(EdgeDetectorKernel kernel, bool _)
{
new object[] { new TestType<KayyaliProcessor>(), EdgeDetectionOperators.Kayyali },
new object[] { new TestType<KirschProcessor>(), EdgeDetectionOperators.Kirsch },
new object[] { new TestType<Laplacian3x3Processor>(), EdgeDetectionOperators.Laplacian3x3 },
new object[] { new TestType<Laplacian5x5Processor>(), EdgeDetectionOperators.Laplacian5x5 },
new object[] { new TestType<LaplacianOfGaussianProcessor>(), EdgeDetectionOperators.LaplacianOfGaussian },
new object[] { new TestType<PrewittProcessor>(), EdgeDetectionOperators.Prewitt },
new object[] { new TestType<RobertsCrossProcessor>(), EdgeDetectionOperators.RobertsCross },
new object[] { new TestType<RobinsonProcessor>(), EdgeDetectionOperators.Robinson },
new object[] { new TestType<ScharrProcessor>(), EdgeDetectionOperators.Scharr },
new object[] { new TestType<SobelProcessor>(), EdgeDetectionOperators.Sobel },
};
this.operations.DetectEdges(kernel, this.rect);
EdgeDetectorProcessor processor = this.Verify<EdgeDetectorProcessor>(this.rect);
Assert.True(processor.Grayscale);
Assert.Equal(kernel, processor.Kernel);
}
[Theory]
[MemberData(nameof(EdgeDetectionTheoryData))]
public void DetectEdges_filter_SobelProcessorDefaultsSet<TProcessor>(TestType<TProcessor> type, EdgeDetectionOperators filter)
where TProcessor : EdgeDetectorProcessor
[MemberData(nameof(EdgeDetectorKernelData))]
public void DetectEdges_EdgeDetectorProcessorSet(EdgeDetectorKernel kernel, bool grayscale)
{
this.operations.DetectEdges(filter);
this.operations.DetectEdges(kernel, grayscale);
EdgeDetectorProcessor processor = this.Verify<EdgeDetectorProcessor>();
Assert.Equal(grayscale, processor.Grayscale);
Assert.Equal(kernel, processor.Kernel);
}
[Theory]
[MemberData(nameof(EdgeDetectorKernelData))]
public void DetectEdges_Rect_EdgeDetectorProcessorSet(EdgeDetectorKernel kernel, bool grayscale)
{
this.operations.DetectEdges(kernel, grayscale, this.rect);
EdgeDetectorProcessor processor = this.Verify<EdgeDetectorProcessor>(this.rect);
Assert.Equal(grayscale, processor.Grayscale);
Assert.Equal(kernel, processor.Kernel);
}
public static TheoryData<EdgeDetectorCompassKernel, bool> EdgeDetectorCompassKernelData =
new TheoryData<EdgeDetectorCompassKernel, bool>
{
{ KnownEdgeDetectorKernels.Kirsch, true },
{ KnownEdgeDetectorKernels.Kirsch, false },
{ KnownEdgeDetectorKernels.Robinson, true },
{ KnownEdgeDetectorKernels.Robinson, false },
};
[Theory]
[MemberData(nameof(EdgeDetectorCompassKernelData))]
public void DetectEdges_EdgeDetectorCompassProcessor_DefaultGrayScale_Set(EdgeDetectorCompassKernel kernel, bool _)
{
this.operations.DetectEdges(kernel);
EdgeDetectorCompassProcessor processor = this.Verify<EdgeDetectorCompassProcessor>();
Assert.True(processor.Grayscale);
Assert.Equal(kernel, processor.Kernel);
}
[Theory]
[MemberData(nameof(EdgeDetectorCompassKernelData))]
public void DetectEdges_Rect_EdgeDetectorCompassProcessor_DefaultGrayScale_Set(EdgeDetectorCompassKernel kernel, bool _)
{
this.operations.DetectEdges(kernel, this.rect);
EdgeDetectorCompassProcessor processor = this.Verify<EdgeDetectorCompassProcessor>(this.rect);
// TODO: Enable once we have updated the images
var processor = this.Verify<TProcessor>();
Assert.True(processor.Grayscale);
Assert.Equal(kernel, processor.Kernel);
}
[Theory]
[MemberData(nameof(EdgeDetectorCompassKernelData))]
public void DetectEdges_EdgeDetectorCompassProcessorSet(EdgeDetectorCompassKernel kernel, bool grayscale)
{
this.operations.DetectEdges(kernel, grayscale);
EdgeDetectorCompassProcessor processor = this.Verify<EdgeDetectorCompassProcessor>();
Assert.Equal(grayscale, processor.Grayscale);
Assert.Equal(kernel, processor.Kernel);
}
[Theory]
[MemberData(nameof(EdgeDetectionTheoryData))]
public void DetectEdges_filter_grayscale_SobelProcessorDefaultsSet<TProcessor>(TestType<TProcessor> type, EdgeDetectionOperators filter)
where TProcessor : EdgeDetectorProcessor
[MemberData(nameof(EdgeDetectorCompassKernelData))]
public void DetectEdges_Rect_EdgeDetectorCompassProcessorSet(EdgeDetectorCompassKernel kernel, bool grayscale)
{
bool grey = (int)filter % 2 == 0;
this.operations.DetectEdges(filter, grey);
this.operations.DetectEdges(kernel, grayscale, this.rect);
EdgeDetectorCompassProcessor processor = this.Verify<EdgeDetectorCompassProcessor>(this.rect);
// TODO: Enable once we have updated the images
var processor = this.Verify<TProcessor>();
Assert.Equal(grey, processor.Grayscale);
Assert.Equal(grayscale, processor.Grayscale);
Assert.Equal(kernel, processor.Kernel);
}
}
}

81
tests/ImageSharp.Tests/Processing/Convolution/Processors/EdgeDetectorKernelTests.cs

@ -0,0 +1,81 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors.Convolution;
using Xunit;
namespace SixLabors.ImageSharp.Tests.Processing.Convolution.Processors
{
public class EdgeDetectorKernelTests
{
[Fact]
public void EdgeDetectorKernelEqualityOperatorTest()
{
EdgeDetectorKernel kernel0 = KnownEdgeDetectorKernels.Laplacian3x3;
EdgeDetectorKernel kernel1 = KnownEdgeDetectorKernels.Laplacian3x3;
EdgeDetectorKernel kernel2 = KnownEdgeDetectorKernels.Laplacian5x5;
Assert.True(kernel0 == kernel1);
Assert.False(kernel0 != kernel1);
Assert.True(kernel0 != kernel2);
Assert.False(kernel0 == kernel2);
Assert.True(kernel0.Equals((object)kernel1));
Assert.True(kernel0.Equals(kernel1));
Assert.False(kernel0.Equals((object)kernel2));
Assert.False(kernel0.Equals(kernel2));
Assert.Equal(kernel0.GetHashCode(), kernel1.GetHashCode());
Assert.NotEqual(kernel0.GetHashCode(), kernel2.GetHashCode());
}
[Fact]
public void EdgeDetector2DKernelEqualityOperatorTest()
{
EdgeDetector2DKernel kernel0 = KnownEdgeDetectorKernels.Prewitt;
EdgeDetector2DKernel kernel1 = KnownEdgeDetectorKernels.Prewitt;
EdgeDetector2DKernel kernel2 = KnownEdgeDetectorKernels.RobertsCross;
Assert.True(kernel0 == kernel1);
Assert.False(kernel0 != kernel1);
Assert.True(kernel0 != kernel2);
Assert.False(kernel0 == kernel2);
Assert.True(kernel0.Equals((object)kernel1));
Assert.True(kernel0.Equals(kernel1));
Assert.False(kernel0.Equals((object)kernel2));
Assert.False(kernel0.Equals(kernel2));
Assert.Equal(kernel0.GetHashCode(), kernel1.GetHashCode());
Assert.NotEqual(kernel0.GetHashCode(), kernel2.GetHashCode());
}
[Fact]
public void EdgeDetectorCompassKernelEqualityOperatorTest()
{
EdgeDetectorCompassKernel kernel0 = KnownEdgeDetectorKernels.Kirsch;
EdgeDetectorCompassKernel kernel1 = KnownEdgeDetectorKernels.Kirsch;
EdgeDetectorCompassKernel kernel2 = KnownEdgeDetectorKernels.Robinson;
Assert.True(kernel0 == kernel1);
Assert.False(kernel0 != kernel1);
Assert.True(kernel0 != kernel2);
Assert.False(kernel0 == kernel2);
Assert.True(kernel0.Equals((object)kernel1));
Assert.True(kernel0.Equals(kernel1));
Assert.False(kernel0.Equals((object)kernel2));
Assert.False(kernel0.Equals(kernel2));
Assert.Equal(kernel0.GetHashCode(), kernel1.GetHashCode());
Assert.NotEqual(kernel0.GetHashCode(), kernel2.GetHashCode());
}
}
}

116
tests/ImageSharp.Tests/Processing/Processors/Convolution/DetectEdgesTest.cs

@ -1,8 +1,10 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.Diagnostics.CodeAnalysis;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors.Convolution;
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
using Xunit;
@ -10,6 +12,7 @@ using Xunit;
namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution
{
[GroupOutput("Convolution")]
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1313:Parameter names should begin with lower-case letter", Justification = "OK. Used for TheoryData compatibility.")]
public class DetectEdgesTest
{
private static readonly ImageComparer OpaqueComparer = ImageComparer.TolerantPercentage(0.01F);
@ -20,18 +23,29 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution
public const PixelTypes CommonNonDefaultPixelTypes = PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.RgbaVector;
public static readonly TheoryData<EdgeDetectionOperators> DetectEdgesFilters = new TheoryData<EdgeDetectionOperators>
public static readonly TheoryData<EdgeDetectorKernel, string> DetectEdgesFilters
= new TheoryData<EdgeDetectorKernel, string>
{
EdgeDetectionOperators.Kayyali,
EdgeDetectionOperators.Kirsch,
EdgeDetectionOperators.Laplacian3x3,
EdgeDetectionOperators.Laplacian5x5,
EdgeDetectionOperators.LaplacianOfGaussian,
EdgeDetectionOperators.Prewitt,
EdgeDetectionOperators.RobertsCross,
EdgeDetectionOperators.Robinson,
EdgeDetectionOperators.Scharr,
EdgeDetectionOperators.Sobel
{ KnownEdgeDetectorKernels.Laplacian3x3, nameof(KnownEdgeDetectorKernels.Laplacian3x3) },
{ KnownEdgeDetectorKernels.Laplacian5x5, nameof(KnownEdgeDetectorKernels.Laplacian5x5) },
{ KnownEdgeDetectorKernels.LaplacianOfGaussian, nameof(KnownEdgeDetectorKernels.LaplacianOfGaussian) },
};
public static readonly TheoryData<EdgeDetector2DKernel, string> DetectEdges2DFilters
= new TheoryData<EdgeDetector2DKernel, string>
{
{ KnownEdgeDetectorKernels.Kayyali, nameof(KnownEdgeDetectorKernels.Kayyali) },
{ KnownEdgeDetectorKernels.Prewitt, nameof(KnownEdgeDetectorKernels.Prewitt) },
{ KnownEdgeDetectorKernels.RobertsCross, nameof(KnownEdgeDetectorKernels.RobertsCross) },
{ KnownEdgeDetectorKernels.Scharr, nameof(KnownEdgeDetectorKernels.Scharr) },
{ KnownEdgeDetectorKernels.Sobel, nameof(KnownEdgeDetectorKernels.Sobel) },
};
public static readonly TheoryData<EdgeDetectorCompassKernel, string> DetectEdgesCompassFilters
= new TheoryData<EdgeDetectorCompassKernel, string>
{
{ KnownEdgeDetectorKernels.Kirsch, nameof(KnownEdgeDetectorKernels.Kirsch) },
{ KnownEdgeDetectorKernels.Robinson, nameof(KnownEdgeDetectorKernels.Robinson) },
};
[Theory]
@ -53,7 +67,48 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution
[Theory]
[WithTestPatternImages(nameof(DetectEdgesFilters), 100, 100, PixelTypes.Rgba32)]
[WithFileCollection(nameof(TestImages), nameof(DetectEdgesFilters), PixelTypes.Rgba32)]
public void DetectEdges_WorksWithAllFilters<TPixel>(TestImageProvider<TPixel> provider, EdgeDetectionOperators detector)
public void DetectEdges_WorksWithAllFilters<TPixel>(
TestImageProvider<TPixel> provider,
EdgeDetectorKernel detector,
string name)
where TPixel : unmanaged, IPixel<TPixel>
{
bool hasAlpha = provider.SourceFileOrDescription.Contains("TestPattern");
ImageComparer comparer = hasAlpha ? TransparentComparer : OpaqueComparer;
using (Image<TPixel> image = provider.GetImage())
{
image.Mutate(x => x.DetectEdges(detector));
image.DebugSave(provider, name);
image.CompareToReferenceOutput(comparer, provider, name);
}
}
[Theory]
[WithTestPatternImages(nameof(DetectEdges2DFilters), 100, 100, PixelTypes.Rgba32)]
[WithFileCollection(nameof(TestImages), nameof(DetectEdges2DFilters), PixelTypes.Rgba32)]
public void DetectEdges2D_WorksWithAllFilters<TPixel>(
TestImageProvider<TPixel> provider,
EdgeDetector2DKernel detector,
string name)
where TPixel : unmanaged, IPixel<TPixel>
{
bool hasAlpha = provider.SourceFileOrDescription.Contains("TestPattern");
ImageComparer comparer = hasAlpha ? TransparentComparer : OpaqueComparer;
using (Image<TPixel> image = provider.GetImage())
{
image.Mutate(x => x.DetectEdges(detector));
image.DebugSave(provider, name);
image.CompareToReferenceOutput(comparer, provider, name);
}
}
[Theory]
[WithTestPatternImages(nameof(DetectEdgesCompassFilters), 100, 100, PixelTypes.Rgba32)]
[WithFileCollection(nameof(TestImages), nameof(DetectEdgesCompassFilters), PixelTypes.Rgba32)]
public void DetectEdgesCompass_WorksWithAllFilters<TPixel>(
TestImageProvider<TPixel> provider,
EdgeDetectorCompassKernel detector,
string name)
where TPixel : unmanaged, IPixel<TPixel>
{
bool hasAlpha = provider.SourceFileOrDescription.Contains("TestPattern");
@ -61,8 +116,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution
using (Image<TPixel> image = provider.GetImage())
{
image.Mutate(x => x.DetectEdges(detector));
image.DebugSave(provider, detector.ToString());
image.CompareToReferenceOutput(comparer, provider, detector.ToString());
image.DebugSave(provider, name);
image.CompareToReferenceOutput(comparer, provider, name);
}
}
@ -115,7 +170,38 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution
[Theory]
[WithFile(Tests.TestImages.Png.Bike, nameof(DetectEdgesFilters), PixelTypes.Rgba32)]
public void WorksWithDiscoBuffers<TPixel>(TestImageProvider<TPixel> provider, EdgeDetectionOperators detector)
public void WorksWithDiscoBuffers<TPixel>(
TestImageProvider<TPixel> provider,
EdgeDetectorKernel detector,
string _)
where TPixel : unmanaged, IPixel<TPixel>
{
provider.RunBufferCapacityLimitProcessorTest(
41,
c => c.DetectEdges(detector),
detector);
}
[Theory]
[WithFile(Tests.TestImages.Png.Bike, nameof(DetectEdges2DFilters), PixelTypes.Rgba32)]
public void WorksWithDiscoBuffers2D<TPixel>(
TestImageProvider<TPixel> provider,
EdgeDetector2DKernel detector,
string _)
where TPixel : unmanaged, IPixel<TPixel>
{
provider.RunBufferCapacityLimitProcessorTest(
41,
c => c.DetectEdges(detector),
detector);
}
[Theory]
[WithFile(Tests.TestImages.Png.Bike, nameof(DetectEdgesCompassFilters), PixelTypes.Rgba32)]
public void WorksWithDiscoBuffersCompass<TPixel>(
TestImageProvider<TPixel> provider,
EdgeDetectorCompassKernel detector,
string _)
where TPixel : unmanaged, IPixel<TPixel>
{
provider.RunBufferCapacityLimitProcessorTest(

2
tests/Images/External

@ -1 +1 @@
Subproject commit 0d1f91e2fe1491f6dc2c137a8ea20460fde4404c
Subproject commit 6a003080674d1fedc66292c13ce5a357b2a33083
Loading…
Cancel
Save