diff --git a/src/ImageSharp/Processing/EdgeDetectionOperators.cs b/src/ImageSharp/Processing/EdgeDetectionOperators.cs deleted file mode 100644 index 3e986f802..000000000 --- a/src/ImageSharp/Processing/EdgeDetectionOperators.cs +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Processing -{ - /// - /// Enumerates the various types of defined edge detection filters. - /// - public enum EdgeDetectionOperators - { - /// - /// The Kayyali operator filter. - /// - Kayyali, - - /// - /// The Kirsch operator filter. - /// - Kirsch, - - /// - /// The Laplacian3X3 operator filter. - /// - Laplacian3x3, - - /// - /// The Laplacian5X5 operator filter. - /// - Laplacian5x5, - - /// - /// The LaplacianOfGaussian operator filter. - /// - LaplacianOfGaussian, - - /// - /// The Prewitt operator filter. - /// - Prewitt, - - /// - /// The RobertsCross operator filter. - /// - RobertsCross, - - /// - /// The Robinson operator filter. - /// - Robinson, - - /// - /// The Scharr operator filter. - /// - Scharr, - - /// - /// The Sobel operator filter. - /// - Sobel - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Extensions/Convolution/DetectEdgesExtensions.cs b/src/ImageSharp/Processing/Extensions/Convolution/DetectEdgesExtensions.cs index 61b900848..2377151bb 100644 --- a/src/ImageSharp/Processing/Extensions/Convolution/DetectEdgesExtensions.cs +++ b/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 { /// - /// Detects any edges within the image. Uses the filter - /// operating in grayscale mode. + /// Detects any edges within the image. + /// Uses the kernel operating in grayscale mode. /// /// The image this method extends. /// The to allow chaining of operations. public static IImageProcessingContext DetectEdges(this IImageProcessingContext source) => - DetectEdges(source, new SobelProcessor(true)); + DetectEdges(source, KnownEdgeDetectorKernels.Sobel); /// - /// Detects any edges within the image. Uses the filter - /// operating in grayscale mode. + /// Detects any edges within the image. + /// Uses the kernel operating in grayscale mode. /// /// The image this method extends. /// /// The structure that specifies the portion of the image object to alter. /// /// The to allow chaining of operations. - 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); /// - /// Detects any edges within the image. + /// Detects any edges within the image operating in grayscale mode. /// /// The image this method extends. - /// The filter for detecting edges. + /// The 2D edge detector kernel. /// The to allow chaining of operations. public static IImageProcessingContext DetectEdges( this IImageProcessingContext source, - EdgeDetectionOperators filter) => - DetectEdges(source, GetProcessor(filter, true)); + EdgeDetector2DKernel kernel) => + DetectEdges(source, kernel, true); /// - /// Detects any edges within the image. + /// Detects any edges within the image using a . /// /// The image this method extends. - /// The filter for detecting edges. - /// Whether to convert the image to grayscale first. Defaults to true. + /// The 2D edge detector kernel. + /// + /// Whether to convert the image to grayscale before performing edge detection. + /// /// The to allow chaining of operations. 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; + } /// - /// Detects any edges within the image. + /// Detects any edges within the image operating in grayscale mode. /// /// The image this method extends. - /// The filter for detecting edges. + /// The 2D edge detector kernel. /// /// The structure that specifies the portion of the image object to alter. /// - /// Whether to convert the image to grayscale first. Defaults to true. /// The to allow chaining of operations. 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); /// - /// Detects any edges within the image. + /// Detects any edges within the image using a . /// /// The image this method extends. - /// The filter for detecting edges. + /// The 2D edge detector kernel. + /// + /// Whether to convert the image to grayscale before performing edge detection. + /// + /// + /// The structure that specifies the portion of the image object to alter. + /// /// The to allow chaining of operations. - 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; } /// - /// Detects any edges within the image. + /// Detects any edges within the image operating in grayscale mode. /// /// The image this method extends. - /// - /// The structure that specifies the portion of the image object to alter. + /// The edge detector kernel. + /// The to allow chaining of operations. + public static IImageProcessingContext DetectEdges( + this IImageProcessingContext source, + EdgeDetectorKernel kernel) => + DetectEdges(source, kernel, true); + + /// + /// Detects any edges within the image using a . + /// + /// The image this method extends. + /// The edge detector kernel. + /// + /// Whether to convert the image to grayscale before performing edge detection. /// - /// The filter for detecting edges. /// The to allow chaining of operations. - 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; + /// + /// Detects any edges within the image operating in grayscale mode. + /// + /// The image this method extends. + /// The edge detector kernel. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext DetectEdges( + this IImageProcessingContext source, + EdgeDetectorKernel kernel, + Rectangle rectangle) => + DetectEdges(source, kernel, true, rectangle); - case EdgeDetectionOperators.RobertsCross: - processor = new RobertsCrossProcessor(grayscale); - break; + /// + /// Detects any edges within the image using a . + /// + /// The image this method extends. + /// The edge detector kernel. + /// + /// Whether to convert the image to grayscale before performing edge detection. + /// + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + 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; + /// + /// Detects any edges within the image operating in grayscale mode. + /// + /// The image this method extends. + /// Thecompass edge detector kernel. + /// The to allow chaining of operations. + public static IImageProcessingContext DetectEdges( + this IImageProcessingContext source, + EdgeDetectorCompassKernel kernel) => + DetectEdges(source, kernel, true); - case EdgeDetectionOperators.Scharr: - processor = new ScharrProcessor(grayscale); - break; + /// + /// Detects any edges within the image using a . + /// + /// The image this method extends. + /// Thecompass edge detector kernel. + /// + /// Whether to convert the image to grayscale before performing edge detection. + /// + /// The to allow chaining of operations. + 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; - } + /// + /// Detects any edges within the image operating in grayscale mode. + /// + /// The image this method extends. + /// Thecompass edge detector kernel. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext DetectEdges( + this IImageProcessingContext source, + EdgeDetectorCompassKernel kernel, + Rectangle rectangle) => + DetectEdges(source, kernel, true, rectangle); - return processor; + /// + /// Detects any edges within the image using a . + /// + /// The image this method extends. + /// Thecompass edge detector kernel. + /// + /// Whether to convert the image to grayscale before performing edge detection. + /// + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + 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; } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/KnownEdgeDetectorKernels.cs b/src/ImageSharp/Processing/KnownEdgeDetectorKernels.cs new file mode 100644 index 000000000..2e279d340 --- /dev/null +++ b/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 +{ + /// + /// Contains reusable static instances of known edge detection kernels. + /// + public static class KnownEdgeDetectorKernels + { + /// + /// Gets the Kayyali edge detector kernel. + /// + public static EdgeDetector2DKernel Kayyali { get; } = EdgeDetector2DKernel.KayyaliKernel; + + /// + /// Gets the Kirsch edge detector kernel. + /// + public static EdgeDetectorCompassKernel Kirsch { get; } = EdgeDetectorCompassKernel.Kirsch; + + /// + /// Gets the Laplacian 3x3 edge detector kernel. + /// + public static EdgeDetectorKernel Laplacian3x3 { get; } = EdgeDetectorKernel.Laplacian3x3; + + /// + /// Gets the Laplacian 5x5 edge detector kernel. + /// + public static EdgeDetectorKernel Laplacian5x5 { get; } = EdgeDetectorKernel.Laplacian5x5; + + /// + /// Gets the Laplacian of Gaussian edge detector kernel. + /// + public static EdgeDetectorKernel LaplacianOfGaussian { get; } = EdgeDetectorKernel.LaplacianOfGaussian; + + /// + /// Gets the Prewitt edge detector kernel. + /// + public static EdgeDetector2DKernel Prewitt { get; } = EdgeDetector2DKernel.PrewittKernel; + + /// + /// Gets the Roberts-Cross edge detector kernel. + /// + public static EdgeDetector2DKernel RobertsCross { get; } = EdgeDetector2DKernel.RobertsCrossKernel; + + /// + /// Gets the Robinson edge detector kernel. + /// + public static EdgeDetectorCompassKernel Robinson { get; } = EdgeDetectorCompassKernel.Robinson; + + /// + /// Gets the Scharr edge detector kernel. + /// + public static EdgeDetector2DKernel Scharr { get; } = EdgeDetector2DKernel.ScharrKernel; + + /// + /// Gets the Sobel edge detector kernel. + /// + public static EdgeDetector2DKernel Sobel { get; } = EdgeDetector2DKernel.SobelKernel; + } +} diff --git a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetector2DProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetector2DProcessor.cs new file mode 100644 index 000000000..1d0a29a35 --- /dev/null +++ b/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 +{ + /// + /// Defines edge detection using the two 1D gradient operators. + /// + public sealed class EdgeDetector2DProcessor : IImageProcessor + { + /// + /// Initializes a new instance of the class. + /// + /// The 2D edge detector kernel. + /// + /// Whether to convert the image to grayscale before performing edge detection. + /// + public EdgeDetector2DProcessor(EdgeDetector2DKernel kernel, bool grayscale) + { + this.Kernel = kernel; + this.Grayscale = grayscale; + } + + /// + /// Gets the 2D edge detector kernel. + /// + public EdgeDetector2DKernel Kernel { get; } + + /// + /// Gets a value indicating whether to convert the image to grayscale before performing + /// edge detection. + /// + public bool Grayscale { get; } + + /// + public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + where TPixel : unmanaged, IPixel + => new EdgeDetector2DProcessor(configuration, this, source, sourceRectangle); + } +} diff --git a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetector2DProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetector2DProcessor{TPixel}.cs index 6c831e727..80f8a7706 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetector2DProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetector2DProcessor{TPixel}.cs @@ -13,42 +13,29 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution internal class EdgeDetector2DProcessor : ImageProcessor where TPixel : unmanaged, IPixel { + private readonly DenseMatrix kernelX; + private readonly DenseMatrix kernelY; + private readonly bool grayscale; + /// /// Initializes a new instance of the class. /// /// The configuration which allows altering default behaviour or extending the library. - /// The horizontal gradient operator. - /// The vertical gradient operator. - /// Whether to convert the image to grayscale before performing edge detection. + /// The defining the processor parameters. /// The source for the current processor instance. /// The source area to process for the current processor instance. - internal EdgeDetector2DProcessor( + public EdgeDetector2DProcessor( Configuration configuration, - in DenseMatrix kernelX, - in DenseMatrix kernelY, - bool grayscale, + EdgeDetector2DProcessor definition, Image 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; } - /// - /// Gets the horizontal gradient operator. - /// - public DenseMatrix KernelX { get; } - - /// - /// Gets the vertical gradient operator. - /// - public DenseMatrix KernelY { get; } - - public bool Grayscale { get; } - /// 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 /// protected override void OnFrameApply(ImageFrame source) { - using var processor = new Convolution2DProcessor(this.Configuration, this.KernelX, this.KernelY, true, this.Source, this.SourceRectangle); + using var processor = new Convolution2DProcessor( + this.Configuration, + in this.kernelX, + in this.kernelY, + true, + this.Source, + this.SourceRectangle); processor.Apply(source); } diff --git a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor.cs new file mode 100644 index 000000000..083a69bd2 --- /dev/null +++ b/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 +{ + /// + /// Defines edge detection using eight gradient operators. + /// + public sealed class EdgeDetectorCompassProcessor : IImageProcessor + { + /// + /// Initializes a new instance of the class. + /// + /// The edge detector kernel. + /// + /// Whether to convert the image to grayscale before performing edge detection. + /// + public EdgeDetectorCompassProcessor(EdgeDetectorCompassKernel kernel, bool grayscale) + { + this.Kernel = kernel; + this.Grayscale = grayscale; + } + + /// + /// Gets the edge detector kernel. + /// + public EdgeDetectorCompassKernel Kernel { get; } + + /// + /// Gets a value indicating whether to convert the image to grayscale before performing + /// edge detection. + /// + public bool Grayscale { get; } + + /// + public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + where TPixel : unmanaged, IPixel + => new EdgeDetectorCompassProcessor(configuration, this, source, sourceRectangle); + } +} diff --git a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs index 164488155..27963613e 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs @@ -18,25 +18,27 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution internal class EdgeDetectorCompassProcessor : ImageProcessor where TPixel : unmanaged, IPixel { + private readonly DenseMatrix[] kernels; + private readonly bool grayscale; + /// /// Initializes a new instance of the class. /// /// The configuration which allows altering default behaviour or extending the library. - /// Gets the kernels to use. - /// Whether to convert the image to grayscale before performing edge detection. + /// The defining the processor parameters. /// The source for the current processor instance. /// The source area to process for the current processor instance. - internal EdgeDetectorCompassProcessor(Configuration configuration, CompassKernels kernels, bool grayscale, Image source, Rectangle sourceRectangle) + internal EdgeDetectorCompassProcessor( + Configuration configuration, + EdgeDetectorCompassProcessor definition, + Image 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; } - /// 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 /// protected override void OnFrameApply(ImageFrame source) { - DenseMatrix[] 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 cleanCopy = source.Clone(); - using (var processor = new ConvolutionProcessor(this.Configuration, kernels[0], true, this.Source, interest)) + using (var processor = new ConvolutionProcessor(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 pass = cleanCopy.Clone(); - using (var processor = new ConvolutionProcessor(this.Configuration, kernels[i], true, this.Source, interest)) + using (var processor = new ConvolutionProcessor(this.Configuration, in this.kernels[i], true, this.Source, interest)) { processor.Apply(pass); } diff --git a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor.cs index a9d20b547..6fcfca662 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor.cs @@ -6,26 +6,37 @@ using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing.Processors.Convolution { /// - /// Defines a processor that detects edges within an image using a single two dimensional matrix. + /// Defines edge detection using a single 2D gradient operator. /// - public abstract class EdgeDetectorProcessor : IImageProcessor + public sealed class EdgeDetectorProcessor : IImageProcessor { /// /// Initializes a new instance of the class. /// - /// A value indicating whether to convert the image to grayscale before performing edge detection. - protected EdgeDetectorProcessor(bool grayscale) + /// The edge detector kernel. + /// + /// Whether to convert the image to grayscale before performing edge detection. + /// + public EdgeDetectorProcessor(EdgeDetectorKernel kernel, bool grayscale) { + this.Kernel = kernel; this.Grayscale = grayscale; } /// - /// Gets a value indicating whether to convert the image to grayscale before performing edge detection. + /// Gets the edge detector kernel. + /// + public EdgeDetectorKernel Kernel { get; } + + /// + /// Gets a value indicating whether to convert the image to grayscale before performing + /// edge detection. /// public bool Grayscale { get; } /// - public abstract IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - where TPixel : unmanaged, IPixel; + public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + where TPixel : unmanaged, IPixel + => new EdgeDetectorProcessor(configuration, this, source, sourceRectangle); } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor{TPixel}.cs index 45639d93a..62dd54919 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor{TPixel}.cs @@ -13,33 +13,27 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution internal class EdgeDetectorProcessor : ImageProcessor where TPixel : unmanaged, IPixel { + private readonly bool grayscale; + private readonly DenseMatrix kernelXY; + /// /// Initializes a new instance of the class. /// /// The configuration which allows altering default behaviour or extending the library. - /// The 2d gradient operator. - /// Whether to convert the image to grayscale before performing edge detection. + /// The defining the processor parameters. /// The source for the current processor instance. /// The target area to process for the current processor instance. public EdgeDetectorProcessor( Configuration configuration, - in DenseMatrix kernelXY, - bool grayscale, + EdgeDetectorProcessor definition, Image 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; } - - /// - /// Gets the 2d gradient operator. - /// - public DenseMatrix KernelXY { get; } - /// 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 /// protected override void OnFrameApply(ImageFrame source) { - using (var processor = new ConvolutionProcessor(this.Configuration, this.KernelXY, true, this.Source, this.SourceRectangle)) - { - processor.Apply(source); - } + using var processor = new ConvolutionProcessor(this.Configuration, in this.kernelXY, true, this.Source, this.SourceRectangle); + processor.Apply(source); } } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/KayyaliProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/KayyaliProcessor.cs deleted file mode 100644 index c13e8b543..000000000 --- a/src/ImageSharp/Processing/Processors/Convolution/KayyaliProcessor.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Processing.Processors.Convolution -{ - /// - /// Defines edge detection processing using the Kayyali operator filter. - /// See . - /// - public sealed class KayyaliProcessor : EdgeDetectorProcessor - { - /// - /// Initializes a new instance of the class. - /// - /// Whether to convert the image to grayscale before performing edge detection. - public KayyaliProcessor(bool grayscale) - : base(grayscale) - { - } - - /// - public override IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - => new EdgeDetector2DProcessor( - configuration, - KayyaliKernels.KayyaliX, - KayyaliKernels.KayyaliY, - this.Grayscale, - source, - sourceRectangle); - } -} diff --git a/src/ImageSharp/Processing/Processors/Convolution/Kernels/CompassKernels.cs b/src/ImageSharp/Processing/Processors/Convolution/Kernels/CompassKernels.cs deleted file mode 100644 index 24caa40f8..000000000 --- a/src/ImageSharp/Processing/Processors/Convolution/Kernels/CompassKernels.cs +++ /dev/null @@ -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 - { - /// - /// Gets the North gradient operator. - /// - public abstract DenseMatrix North { get; } - - /// - /// Gets the NorthWest gradient operator. - /// - public abstract DenseMatrix NorthWest { get; } - - /// - /// Gets the West gradient operator. - /// - public abstract DenseMatrix West { get; } - - /// - /// Gets the SouthWest gradient operator. - /// - public abstract DenseMatrix SouthWest { get; } - - /// - /// Gets the South gradient operator. - /// - public abstract DenseMatrix South { get; } - - /// - /// Gets the SouthEast gradient operator. - /// - public abstract DenseMatrix SouthEast { get; } - - /// - /// Gets the East gradient operator. - /// - public abstract DenseMatrix East { get; } - - /// - /// Gets the NorthEast gradient operator. - /// - public abstract DenseMatrix NorthEast { get; } - - public DenseMatrix[] Flatten() => - new[] - { - this.North, this.NorthWest, this.West, this.SouthWest, - this.South, this.SouthEast, this.East, this.NorthEast - }; - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Convolution/Kernels/EdgeDetector2DKernel.cs b/src/ImageSharp/Processing/Processors/Convolution/Kernels/EdgeDetector2DKernel.cs new file mode 100644 index 000000000..ed363595d --- /dev/null +++ b/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 +{ + /// + /// Represents an edge detection convolution kernel consisting of two 1D gradient operators. + /// + public readonly struct EdgeDetector2DKernel : IEquatable + { + /// + /// An edge detection kernel containing two Kayyali operators. + /// + public static EdgeDetector2DKernel KayyaliKernel = new EdgeDetector2DKernel(KayyaliKernels.KayyaliX, KayyaliKernels.KayyaliY); + + /// + /// An edge detection kernel containing two Prewitt operators. + /// . + /// + public static EdgeDetector2DKernel PrewittKernel = new EdgeDetector2DKernel(PrewittKernels.PrewittX, PrewittKernels.PrewittY); + + /// + /// An edge detection kernel containing two Roberts-Cross operators. + /// . + /// + public static EdgeDetector2DKernel RobertsCrossKernel = new EdgeDetector2DKernel(RobertsCrossKernels.RobertsCrossX, RobertsCrossKernels.RobertsCrossY); + + /// + /// An edge detection kernel containing two Scharr operators. + /// + public static EdgeDetector2DKernel ScharrKernel = new EdgeDetector2DKernel(ScharrKernels.ScharrX, ScharrKernels.ScharrY); + + /// + /// An edge detection kernel containing two Sobel operators. + /// . + /// + public static EdgeDetector2DKernel SobelKernel = new EdgeDetector2DKernel(SobelKernels.SobelX, SobelKernels.SobelY); + + /// + /// Initializes a new instance of the struct. + /// + /// The horizontal gradient operator. + /// The vertical gradient operator. + public EdgeDetector2DKernel(DenseMatrix kernelX, DenseMatrix kernelY) + { + Guard.IsTrue( + kernelX.Size.Equals(kernelY.Size), + $"{nameof(kernelX)} {nameof(kernelY)}", + "Kernel sizes must be the same."); + + this.KernelX = kernelX; + this.KernelY = kernelY; + } + + /// + /// Gets the horizontal gradient operator. + /// + public DenseMatrix KernelX { get; } + + /// + /// Gets the vertical gradient operator. + /// + public DenseMatrix KernelY { get; } + + /// + /// Checks whether two structures are equal. + /// + /// The left hand operand. + /// The right hand operand. + /// + /// True if the parameter is equal to the parameter; + /// otherwise, false. + /// + public static bool operator ==(EdgeDetector2DKernel left, EdgeDetector2DKernel right) + => left.Equals(right); + + /// + /// Checks whether two structures are equal. + /// + /// The left hand operand. + /// The right hand operand. + /// + /// True if the parameter is not equal to the parameter; + /// otherwise, false. + /// + public static bool operator !=(EdgeDetector2DKernel left, EdgeDetector2DKernel right) + => !(left == right); + + /// + public override bool Equals(object obj) + => obj is EdgeDetector2DKernel kernel && this.Equals(kernel); + + /// + public bool Equals(EdgeDetector2DKernel other) + => this.KernelX.Equals(other.KernelX) + && this.KernelY.Equals(other.KernelY); + + /// + public override int GetHashCode() => HashCode.Combine(this.KernelX, this.KernelY); + } +} diff --git a/src/ImageSharp/Processing/Processors/Convolution/Kernels/EdgeDetectorCompassKernel.cs b/src/ImageSharp/Processing/Processors/Convolution/Kernels/EdgeDetectorCompassKernel.cs new file mode 100644 index 000000000..bda861799 --- /dev/null +++ b/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 +{ + /// + /// Represents an edge detection convolution kernel consisting of eight gradient operators. + /// + public readonly struct EdgeDetectorCompassKernel : IEquatable + { + /// + /// An edge detection kenel comprised of Kirsch gradient operators. + /// . + /// + public static EdgeDetectorCompassKernel Kirsch = + new EdgeDetectorCompassKernel( + KirschKernels.North, + KirschKernels.NorthWest, + KirschKernels.West, + KirschKernels.SouthWest, + KirschKernels.South, + KirschKernels.SouthEast, + KirschKernels.East, + KirschKernels.NorthEast); + + /// + /// An edge detection kenel comprised of Robinson gradient operators. + /// + /// + public static EdgeDetectorCompassKernel Robinson = + new EdgeDetectorCompassKernel( + RobinsonKernels.North, + RobinsonKernels.NorthWest, + RobinsonKernels.West, + RobinsonKernels.SouthWest, + RobinsonKernels.South, + RobinsonKernels.SouthEast, + RobinsonKernels.East, + RobinsonKernels.NorthEast); + + /// + /// Initializes a new instance of the struct. + /// + /// The north gradient operator. + /// The north-west gradient operator. + /// The west gradient operator. + /// The south-west gradient operator. + /// The south gradient operator. + /// The south-east gradient operator. + /// The east gradient operator. + /// The north-east gradient operator. + public EdgeDetectorCompassKernel( + DenseMatrix north, + DenseMatrix northWest, + DenseMatrix west, + DenseMatrix southWest, + DenseMatrix south, + DenseMatrix southEast, + DenseMatrix east, + DenseMatrix 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; + } + + /// + /// Gets the North gradient operator. + /// + public DenseMatrix North { get; } + + /// + /// Gets the NorthWest gradient operator. + /// + public DenseMatrix NorthWest { get; } + + /// + /// Gets the West gradient operator. + /// + public DenseMatrix West { get; } + + /// + /// Gets the SouthWest gradient operator. + /// + public DenseMatrix SouthWest { get; } + + /// + /// Gets the South gradient operator. + /// + public DenseMatrix South { get; } + + /// + /// Gets the SouthEast gradient operator. + /// + public DenseMatrix SouthEast { get; } + + /// + /// Gets the East gradient operator. + /// + public DenseMatrix East { get; } + + /// + /// Gets the NorthEast gradient operator. + /// + public DenseMatrix NorthEast { get; } + + /// + /// Checks whether two structures are equal. + /// + /// The left hand operand. + /// The right hand operand. + /// + /// True if the parameter is equal to the parameter; + /// otherwise, false. + /// + public static bool operator ==(EdgeDetectorCompassKernel left, EdgeDetectorCompassKernel right) + => left.Equals(right); + + /// + /// Checks whether two structures are equal. + /// + /// The left hand operand. + /// The right hand operand. + /// + /// True if the parameter is not equal to the parameter; + /// otherwise, false. + /// + public static bool operator !=(EdgeDetectorCompassKernel left, EdgeDetectorCompassKernel right) + => !(left == right); + + /// + public override bool Equals(object obj) => obj is EdgeDetectorCompassKernel kernel && this.Equals(kernel); + + /// + 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); + + /// + public override int GetHashCode() + => HashCode.Combine( + this.North, + this.NorthWest, + this.West, + this.SouthWest, + this.South, + this.SouthEast, + this.East, + this.NorthEast); + + internal DenseMatrix[] Flatten() => + new[] + { + this.North, this.NorthWest, this.West, this.SouthWest, + this.South, this.SouthEast, this.East, this.NorthEast + }; + } +} diff --git a/src/ImageSharp/Processing/Processors/Convolution/Kernels/EdgeDetectorKernel.cs b/src/ImageSharp/Processing/Processors/Convolution/Kernels/EdgeDetectorKernel.cs new file mode 100644 index 000000000..86b8a24d9 --- /dev/null +++ b/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 +{ + /// + /// Represents an edge detection convolution kernel consisting of a single 2D gradient operator. + /// + public readonly struct EdgeDetectorKernel : IEquatable + { + /// + /// An edge detection kernel containing a 3x3 Laplacian operator. + /// + /// + public static EdgeDetectorKernel Laplacian3x3 = new EdgeDetectorKernel(LaplacianKernels.Laplacian3x3); + + /// + /// An edge detection kernel containing a 5x5 Laplacian operator. + /// + /// + public static EdgeDetectorKernel Laplacian5x5 = new EdgeDetectorKernel(LaplacianKernels.Laplacian5x5); + + /// + /// An edge detection kernel containing a Laplacian of Gaussian operator. + /// . + /// + public static EdgeDetectorKernel LaplacianOfGaussian = new EdgeDetectorKernel(LaplacianKernels.LaplacianOfGaussianXY); + + /// + /// Initializes a new instance of the struct. + /// + /// The 2D gradient operator. + public EdgeDetectorKernel(DenseMatrix kernelXY) + => this.KernelXY = kernelXY; + + /// + /// Gets the 2D gradient operator. + /// + public DenseMatrix KernelXY { get; } + + /// + /// Checks whether two structures are equal. + /// + /// The left hand operand. + /// The right hand operand. + /// + /// True if the parameter is equal to the parameter; + /// otherwise, false. + /// + public static bool operator ==(EdgeDetectorKernel left, EdgeDetectorKernel right) + => left.Equals(right); + + /// + /// Checks whether two structures are equal. + /// + /// The left hand operand. + /// The right hand operand. + /// + /// True if the parameter is not equal to the parameter; + /// otherwise, false. + /// + public static bool operator !=(EdgeDetectorKernel left, EdgeDetectorKernel right) + => !(left == right); + + /// + public override bool Equals(object obj) + => obj is EdgeDetectorKernel kernel && this.Equals(kernel); + + /// + public bool Equals(EdgeDetectorKernel other) + => this.KernelXY.Equals(other.KernelXY); + + /// + public override int GetHashCode() => this.KernelXY.GetHashCode(); + } +} diff --git a/src/ImageSharp/Processing/Processors/Convolution/Kernels/KayyaliKernels.cs b/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/KayyaliKernels.cs similarity index 100% rename from src/ImageSharp/Processing/Processors/Convolution/Kernels/KayyaliKernels.cs rename to src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/KayyaliKernels.cs diff --git a/src/ImageSharp/Processing/Processors/Convolution/Kernels/KirschKernels.cs b/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/KirschKernels.cs similarity index 79% rename from src/ImageSharp/Processing/Processors/Convolution/Kernels/KirschKernels.cs rename to src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/KirschKernels.cs index 28c5590ef..87ccb174d 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Kernels/KirschKernels.cs +++ b/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 { /// - /// Contains the eight matrices used for Kirsch edge detection + /// Contains the eight matrices used for Kirsch edge detection. + /// . /// - internal class KirschKernels : CompassKernels + internal static class KirschKernels { /// /// Gets the North gradient operator /// - public override DenseMatrix North => + public static DenseMatrix North => new float[,] { { 5, 5, 5 }, @@ -22,7 +23,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// /// Gets the NorthWest gradient operator /// - public override DenseMatrix NorthWest => + public static DenseMatrix NorthWest => new float[,] { { 5, 5, -3 }, @@ -33,7 +34,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// /// Gets the West gradient operator /// - public override DenseMatrix West => + public static DenseMatrix West => new float[,] { { 5, -3, -3 }, @@ -44,7 +45,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// /// Gets the SouthWest gradient operator /// - public override DenseMatrix SouthWest => + public static DenseMatrix SouthWest => new float[,] { { -3, -3, -3 }, @@ -55,7 +56,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// /// Gets the South gradient operator /// - public override DenseMatrix South => + public static DenseMatrix South => new float[,] { { -3, -3, -3 }, @@ -66,7 +67,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// /// Gets the SouthEast gradient operator /// - public override DenseMatrix SouthEast => + public static DenseMatrix SouthEast => new float[,] { { -3, -3, -3 }, @@ -77,7 +78,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// /// Gets the East gradient operator /// - public override DenseMatrix East => + public static DenseMatrix East => new float[,] { { -3, -3, 5 }, @@ -88,7 +89,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// /// Gets the NorthEast gradient operator /// - public override DenseMatrix NorthEast => + public static DenseMatrix NorthEast => new float[,] { { -3, 5, 5 }, @@ -96,4 +97,4 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution { -3, -3, -3 } }; } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/Processors/Convolution/Kernels/LaplacianKernelFactory.cs b/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/LaplacianKernelFactory.cs similarity index 100% rename from src/ImageSharp/Processing/Processors/Convolution/Kernels/LaplacianKernelFactory.cs rename to src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/LaplacianKernelFactory.cs diff --git a/src/ImageSharp/Processing/Processors/Convolution/Kernels/LaplacianKernels.cs b/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/LaplacianKernels.cs similarity index 80% rename from src/ImageSharp/Processing/Processors/Convolution/Kernels/LaplacianKernels.cs rename to src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/LaplacianKernels.cs index ce574341f..7036300c7 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Kernels/LaplacianKernels.cs +++ b/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 { /// - /// Contains Laplacian kernels of different sizes + /// Contains Laplacian kernels of different sizes. + /// + /// . /// internal static class LaplacianKernels { @@ -31,4 +33,4 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution { 0, 0, -1, 0, 0 } }; } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/Processors/Convolution/Kernels/PrewittKernels.cs b/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/PrewittKernels.cs similarity index 100% rename from src/ImageSharp/Processing/Processors/Convolution/Kernels/PrewittKernels.cs rename to src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/PrewittKernels.cs diff --git a/src/ImageSharp/Processing/Processors/Convolution/Kernels/RobertsCrossKernels.cs b/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/RobertsCrossKernels.cs similarity index 100% rename from src/ImageSharp/Processing/Processors/Convolution/Kernels/RobertsCrossKernels.cs rename to src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/RobertsCrossKernels.cs diff --git a/src/ImageSharp/Processing/Processors/Convolution/Kernels/RobinsonKernels.cs b/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/RobinsonKernels.cs similarity index 78% rename from src/ImageSharp/Processing/Processors/Convolution/Kernels/RobinsonKernels.cs rename to src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/RobinsonKernels.cs index 857c772b0..7d0478aa5 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Kernels/RobinsonKernels.cs +++ b/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 { /// /// Contains the kernels used for Robinson edge detection. + /// /// - internal class RobinsonKernels : CompassKernels + internal static class RobinsonKernels { /// /// Gets the North gradient operator /// - public override DenseMatrix North => + public static DenseMatrix North => new float[,] { { 1, 2, 1 }, @@ -22,7 +23,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// /// Gets the NorthWest gradient operator /// - public override DenseMatrix NorthWest => + public static DenseMatrix NorthWest => new float[,] { { 2, 1, 0 }, @@ -33,7 +34,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// /// Gets the West gradient operator /// - public override DenseMatrix West => + public static DenseMatrix West => new float[,] { { 1, 0, -1 }, @@ -44,7 +45,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// /// Gets the SouthWest gradient operator /// - public override DenseMatrix SouthWest => + public static DenseMatrix SouthWest => new float[,] { { 0, -1, -2 }, @@ -55,7 +56,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// /// Gets the South gradient operator /// - public override DenseMatrix South => + public static DenseMatrix South => new float[,] { { -1, -2, -1 }, @@ -66,7 +67,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// /// Gets the SouthEast gradient operator /// - public override DenseMatrix SouthEast => + public static DenseMatrix SouthEast => new float[,] { { -2, -1, 0 }, @@ -77,7 +78,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// /// Gets the East gradient operator /// - public override DenseMatrix East => + public static DenseMatrix East => new float[,] { { -1, 0, 1 }, @@ -88,7 +89,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// /// Gets the NorthEast gradient operator /// - public override DenseMatrix NorthEast => + public static DenseMatrix NorthEast => new float[,] { { 0, 1, 2 }, @@ -96,4 +97,4 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution { -2, -1, 0 } }; } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/Processors/Convolution/Kernels/ScharrKernels.cs b/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/ScharrKernels.cs similarity index 100% rename from src/ImageSharp/Processing/Processors/Convolution/Kernels/ScharrKernels.cs rename to src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/ScharrKernels.cs diff --git a/src/ImageSharp/Processing/Processors/Convolution/Kernels/SobelKernels.cs b/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/SobelKernels.cs similarity index 100% rename from src/ImageSharp/Processing/Processors/Convolution/Kernels/SobelKernels.cs rename to src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/SobelKernels.cs diff --git a/src/ImageSharp/Processing/Processors/Convolution/KirschProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/KirschProcessor.cs deleted file mode 100644 index 62bd17016..000000000 --- a/src/ImageSharp/Processing/Processors/Convolution/KirschProcessor.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Processing.Processors.Convolution -{ - /// - /// Defines edge detection using the Kirsch operator filter. - /// See . - /// - public sealed class KirschProcessor : EdgeDetectorProcessor - { - /// - /// Initializes a new instance of the class. - /// - /// Whether to convert the image to grayscale before performing edge detection. - public KirschProcessor(bool grayscale) - : base(grayscale) - { - } - - /// - public override IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - => new EdgeDetectorCompassProcessor(configuration, new KirschKernels(), this.Grayscale, source, sourceRectangle); - } -} diff --git a/src/ImageSharp/Processing/Processors/Convolution/Laplacian3x3Processor.cs b/src/ImageSharp/Processing/Processors/Convolution/Laplacian3x3Processor.cs deleted file mode 100644 index 957c61b07..000000000 --- a/src/ImageSharp/Processing/Processors/Convolution/Laplacian3x3Processor.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Processing.Processors.Convolution -{ - /// - /// Applies edge detection processing to the image using the Laplacian 3x3 operator filter. - /// - /// - public sealed class Laplacian3x3Processor : EdgeDetectorProcessor - { - /// - /// Initializes a new instance of the class. - /// - /// Whether to convert the image to grayscale before performing edge detection. - public Laplacian3x3Processor(bool grayscale) - : base(grayscale) - { - } - - /// - public override IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - => new EdgeDetectorProcessor(configuration, LaplacianKernels.Laplacian3x3, this.Grayscale, source, sourceRectangle); - } -} diff --git a/src/ImageSharp/Processing/Processors/Convolution/Laplacian5x5Processor.cs b/src/ImageSharp/Processing/Processors/Convolution/Laplacian5x5Processor.cs deleted file mode 100644 index 8b28662ae..000000000 --- a/src/ImageSharp/Processing/Processors/Convolution/Laplacian5x5Processor.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Processing.Processors.Convolution -{ - /// - /// Defines edge detection processing using the Laplacian 5x5 operator filter. - /// . - /// - public sealed class Laplacian5x5Processor : EdgeDetectorProcessor - { - /// - /// Initializes a new instance of the class. - /// - /// Whether to convert the image to grayscale before performing edge detection. - public Laplacian5x5Processor(bool grayscale) - : base(grayscale) - { - } - - /// - public override IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - => new EdgeDetectorProcessor(configuration, LaplacianKernels.Laplacian5x5, this.Grayscale, source, sourceRectangle); - } -} diff --git a/src/ImageSharp/Processing/Processors/Convolution/LaplacianOfGaussianProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/LaplacianOfGaussianProcessor.cs deleted file mode 100644 index 5c8f7c40d..000000000 --- a/src/ImageSharp/Processing/Processors/Convolution/LaplacianOfGaussianProcessor.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Processing.Processors.Convolution -{ - /// - /// Applies edge detection processing to the image using the Laplacian of Gaussian operator filter. - /// See . - /// - public sealed class LaplacianOfGaussianProcessor : EdgeDetectorProcessor - { - /// - /// Initializes a new instance of the class. - /// - /// Whether to convert the image to grayscale before performing edge detection. - public LaplacianOfGaussianProcessor(bool grayscale) - : base(grayscale) - { - } - - /// - public override IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - => new EdgeDetectorProcessor(configuration, LaplacianKernels.LaplacianOfGaussianXY, this.Grayscale, source, sourceRectangle); - } -} diff --git a/src/ImageSharp/Processing/Processors/Convolution/PrewittProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/PrewittProcessor.cs deleted file mode 100644 index c5ae14935..000000000 --- a/src/ImageSharp/Processing/Processors/Convolution/PrewittProcessor.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Processing.Processors.Convolution -{ - /// - /// Defines edge detection using the Prewitt operator filter. - /// See . - /// - public sealed class PrewittProcessor : EdgeDetectorProcessor - { - /// - /// Initializes a new instance of the class. - /// - /// Whether to convert the image to grayscale before performing edge detection. - public PrewittProcessor(bool grayscale) - : base(grayscale) - { - } - - /// - public override IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - => new EdgeDetector2DProcessor( - configuration, - PrewittKernels.PrewittX, - PrewittKernels.PrewittY, - this.Grayscale, - source, - sourceRectangle); - } -} diff --git a/src/ImageSharp/Processing/Processors/Convolution/RobertsCrossProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/RobertsCrossProcessor.cs deleted file mode 100644 index 57df83a14..000000000 --- a/src/ImageSharp/Processing/Processors/Convolution/RobertsCrossProcessor.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Processing.Processors.Convolution -{ - /// - /// Defines edge detection processing using the Roberts Cross operator filter. - /// See . - /// - public sealed class RobertsCrossProcessor : EdgeDetectorProcessor - { - /// - /// Initializes a new instance of the class. - /// - /// Whether to convert the image to grayscale before performing edge detection. - public RobertsCrossProcessor(bool grayscale) - : base(grayscale) - { - } - - /// - public override IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - => new EdgeDetector2DProcessor( - configuration, - RobertsCrossKernels.RobertsCrossX, - RobertsCrossKernels.RobertsCrossY, - this.Grayscale, - source, - sourceRectangle); - } -} diff --git a/src/ImageSharp/Processing/Processors/Convolution/RobinsonProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/RobinsonProcessor.cs deleted file mode 100644 index 22c8562e2..000000000 --- a/src/ImageSharp/Processing/Processors/Convolution/RobinsonProcessor.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Processing.Processors.Convolution -{ - /// - /// Defines edge detection using the Robinson operator filter. - /// See . - /// - public sealed class RobinsonProcessor : EdgeDetectorProcessor - { - /// - /// Initializes a new instance of the class. - /// - /// Whether to convert the image to grayscale before performing edge detection. - public RobinsonProcessor(bool grayscale) - : base(grayscale) - { - } - - /// - public override IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - => new EdgeDetectorCompassProcessor(configuration, new RobinsonKernels(), this.Grayscale, source, sourceRectangle); - } -} diff --git a/src/ImageSharp/Processing/Processors/Convolution/ScharrProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/ScharrProcessor.cs deleted file mode 100644 index d9a0745e2..000000000 --- a/src/ImageSharp/Processing/Processors/Convolution/ScharrProcessor.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Processing.Processors.Convolution -{ - /// - /// Defines edge detection processing using the Scharr operator filter. - /// - /// - public sealed class ScharrProcessor : EdgeDetectorProcessor - { - /// - /// Initializes a new instance of the class. - /// - /// Whether to convert the image to grayscale before performing edge detection. - public ScharrProcessor(bool grayscale) - : base(grayscale) - { - } - - /// - public override IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - => new EdgeDetector2DProcessor( - configuration, - ScharrKernels.ScharrX, - ScharrKernels.ScharrY, - this.Grayscale, - source, - sourceRectangle); - } -} diff --git a/src/ImageSharp/Processing/Processors/Convolution/SobelProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/SobelProcessor.cs deleted file mode 100644 index 73632392e..000000000 --- a/src/ImageSharp/Processing/Processors/Convolution/SobelProcessor.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Processing.Processors.Convolution -{ - /// - /// Defines edge detection using the Sobel operator filter. - /// See . - /// - public sealed class SobelProcessor : EdgeDetectorProcessor - { - /// - /// Initializes a new instance of the class. - /// - /// Whether to convert the image to grayscale before performing edge detection. - public SobelProcessor(bool grayscale) - : base(grayscale) - { - } - - /// - public override IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - => new EdgeDetector2DProcessor( - configuration, - SobelKernels.SobelX, - SobelKernels.SobelY, - this.Grayscale, - source, - sourceRectangle); - } -} diff --git a/src/ImageSharp/Processing/Processors/Transforms/EntropyCropProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/EntropyCropProcessor{TPixel}.cs index ebc8f0e4f..dd9c06938 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/EntropyCropProcessor{TPixel}.cs +++ b/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); diff --git a/tests/ImageSharp.Benchmarks/Samplers/DetectEdges.cs b/tests/ImageSharp.Benchmarks/Samplers/DetectEdges.cs index d40201bd8..ce2fa988c 100644 --- a/tests/ImageSharp.Benchmarks/Samplers/DetectEdges.cs +++ b/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)); } } } diff --git a/tests/ImageSharp.Tests/Processing/Convolution/DetectEdgesTest.cs b/tests/ImageSharp.Tests/Processing/Convolution/DetectEdgesTest.cs index a0e9be110..3ffb8f4e3 100644 --- a/tests/ImageSharp.Tests/Processing/Convolution/DetectEdgesTest.cs +++ b/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(); - // TODO: Enable once we have updated the images - SobelProcessor processor = this.Verify(); 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(this.rect); + + Assert.True(processor.Grayscale); + Assert.Equal(KnownEdgeDetectorKernels.Sobel, processor.Kernel); + } + + public static TheoryData EdgeDetector2DKernelData = + new TheoryData + { + { 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(); + + 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(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(); + + 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(this.rect); + + Assert.Equal(grayscale, processor.Grayscale); + Assert.Equal(kernel, processor.Kernel); + } + + public static TheoryData EdgeDetectorKernelData = + new TheoryData + { + { 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(); - // TODO: Enable once we have updated the images - SobelProcessor processor = this.Verify(this.rect); Assert.True(processor.Grayscale); + Assert.Equal(kernel, processor.Kernel); } - public static IEnumerable EdgeDetectionTheoryData => new[] + [Theory] + [MemberData(nameof(EdgeDetectorKernelData))] + public void DetectEdges_Rect_EdgeDetectorProcessor_DefaultGrayScale_Set(EdgeDetectorKernel kernel, bool _) { - new object[] { new TestType(), EdgeDetectionOperators.Kayyali }, - new object[] { new TestType(), EdgeDetectionOperators.Kirsch }, - new object[] { new TestType(), EdgeDetectionOperators.Laplacian3x3 }, - new object[] { new TestType(), EdgeDetectionOperators.Laplacian5x5 }, - new object[] { new TestType(), EdgeDetectionOperators.LaplacianOfGaussian }, - new object[] { new TestType(), EdgeDetectionOperators.Prewitt }, - new object[] { new TestType(), EdgeDetectionOperators.RobertsCross }, - new object[] { new TestType(), EdgeDetectionOperators.Robinson }, - new object[] { new TestType(), EdgeDetectionOperators.Scharr }, - new object[] { new TestType(), EdgeDetectionOperators.Sobel }, - }; + this.operations.DetectEdges(kernel, this.rect); + EdgeDetectorProcessor processor = this.Verify(this.rect); + + Assert.True(processor.Grayscale); + Assert.Equal(kernel, processor.Kernel); + } [Theory] - [MemberData(nameof(EdgeDetectionTheoryData))] - public void DetectEdges_filter_SobelProcessorDefaultsSet(TestType 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(); + + 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(this.rect); + + Assert.Equal(grayscale, processor.Grayscale); + Assert.Equal(kernel, processor.Kernel); + } + + public static TheoryData EdgeDetectorCompassKernelData = + new TheoryData + { + { 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(); + + 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(this.rect); - // TODO: Enable once we have updated the images - var processor = this.Verify(); 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(); + + Assert.Equal(grayscale, processor.Grayscale); + Assert.Equal(kernel, processor.Kernel); } [Theory] - [MemberData(nameof(EdgeDetectionTheoryData))] - public void DetectEdges_filter_grayscale_SobelProcessorDefaultsSet(TestType 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(this.rect); - // TODO: Enable once we have updated the images - var processor = this.Verify(); - Assert.Equal(grey, processor.Grayscale); + Assert.Equal(grayscale, processor.Grayscale); + Assert.Equal(kernel, processor.Kernel); } } } diff --git a/tests/ImageSharp.Tests/Processing/Convolution/Processors/EdgeDetectorKernelTests.cs b/tests/ImageSharp.Tests/Processing/Convolution/Processors/EdgeDetectorKernelTests.cs new file mode 100644 index 000000000..b50e5d7cc --- /dev/null +++ b/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()); + } + } +} diff --git a/tests/ImageSharp.Tests/Processing/Processors/Convolution/DetectEdgesTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Convolution/DetectEdgesTest.cs index dbbf624db..e468778de 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Convolution/DetectEdgesTest.cs +++ b/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 DetectEdgesFilters = new TheoryData + public static readonly TheoryData DetectEdgesFilters + = new TheoryData { - 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 DetectEdges2DFilters + = new TheoryData + { + { 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 DetectEdgesCompassFilters + = new TheoryData + { + { 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(TestImageProvider provider, EdgeDetectionOperators detector) + public void DetectEdges_WorksWithAllFilters( + TestImageProvider provider, + EdgeDetectorKernel detector, + string name) + where TPixel : unmanaged, IPixel + { + bool hasAlpha = provider.SourceFileOrDescription.Contains("TestPattern"); + ImageComparer comparer = hasAlpha ? TransparentComparer : OpaqueComparer; + using (Image 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( + TestImageProvider provider, + EdgeDetector2DKernel detector, + string name) + where TPixel : unmanaged, IPixel + { + bool hasAlpha = provider.SourceFileOrDescription.Contains("TestPattern"); + ImageComparer comparer = hasAlpha ? TransparentComparer : OpaqueComparer; + using (Image 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( + TestImageProvider provider, + EdgeDetectorCompassKernel detector, + string name) where TPixel : unmanaged, IPixel { bool hasAlpha = provider.SourceFileOrDescription.Contains("TestPattern"); @@ -61,8 +116,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution using (Image 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(TestImageProvider provider, EdgeDetectionOperators detector) + public void WorksWithDiscoBuffers( + TestImageProvider provider, + EdgeDetectorKernel detector, + string _) + where TPixel : unmanaged, IPixel + { + provider.RunBufferCapacityLimitProcessorTest( + 41, + c => c.DetectEdges(detector), + detector); + } + + [Theory] + [WithFile(Tests.TestImages.Png.Bike, nameof(DetectEdges2DFilters), PixelTypes.Rgba32)] + public void WorksWithDiscoBuffers2D( + TestImageProvider provider, + EdgeDetector2DKernel detector, + string _) + where TPixel : unmanaged, IPixel + { + provider.RunBufferCapacityLimitProcessorTest( + 41, + c => c.DetectEdges(detector), + detector); + } + + [Theory] + [WithFile(Tests.TestImages.Png.Bike, nameof(DetectEdgesCompassFilters), PixelTypes.Rgba32)] + public void WorksWithDiscoBuffersCompass( + TestImageProvider provider, + EdgeDetectorCompassKernel detector, + string _) where TPixel : unmanaged, IPixel { provider.RunBufferCapacityLimitProcessorTest( diff --git a/tests/Images/External b/tests/Images/External index 0d1f91e2f..6a0030806 160000 --- a/tests/Images/External +++ b/tests/Images/External @@ -1 +1 @@ -Subproject commit 0d1f91e2fe1491f6dc2c137a8ea20460fde4404c +Subproject commit 6a003080674d1fedc66292c13ce5a357b2a33083