diff --git a/src/ImageProcessor/Filters/Convolution/Convolution2DFilter.cs b/src/ImageProcessor/Filters/Convolution/Convolution2DFilter.cs new file mode 100644 index 0000000000..47cf25a7c3 --- /dev/null +++ b/src/ImageProcessor/Filters/Convolution/Convolution2DFilter.cs @@ -0,0 +1,97 @@ +// +// Copyright (c) James South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessor.Filters +{ + using System; + using System.Threading.Tasks; + + /// + /// Defines a filter that uses a matrix to perform convolution across two dimensions against an image. + /// + public abstract class Convolution2DFilter : ParallelImageProcessor + { + /// + /// Gets the horizontal gradient operator. + /// + public abstract float[,] KernelX { get; } + + /// + /// Gets the vertical gradient operator. + /// + public abstract float[,] KernelY { get; } + + /// + protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) + { + float[,] kernelX = this.KernelX; + float[,] kernelY = this.KernelY; + int kernelLength = kernelX.GetLength(0); + int radius = kernelLength >> 1; + + int sourceY = sourceRectangle.Y; + int sourceBottom = sourceRectangle.Bottom; + int startX = sourceRectangle.X; + int endX = sourceRectangle.Right; + int maxY = sourceBottom - 1; + int maxX = endX - 1; + + Parallel.For( + startY, + endY, + y => + { + if (y >= sourceY && y < sourceBottom) + { + for (int x = startX; x < endX; x++) + { + float rX = 0; + float gX = 0; + float bX = 0; + float rY = 0; + float gY = 0; + float bY = 0; + + // Apply each matrix multiplier to the color components for each pixel. + for (int fy = 0; fy < kernelLength; fy++) + { + int fyr = fy - radius; + int offsetY = y + fyr; + + offsetY = offsetY.Clamp(0, maxY); + + for (int fx = 0; fx < kernelLength; fx++) + { + int fxr = fx - radius; + int offsetX = x + fxr; + + offsetX = offsetX.Clamp(0, maxX); + + Color currentColor = source[offsetX, offsetY]; + float r = currentColor.R; + float g = currentColor.G; + float b = currentColor.B; + + rX += kernelX[fy, fx] * r; + gX += kernelX[fy, fx] * g; + bX += kernelX[fy, fx] * b; + + rY += kernelY[fy, fx] * r; + gY += kernelY[fy, fx] * g; + bY += kernelY[fy, fx] * b; + } + } + + float red = (float)Math.Sqrt((rX * rX) + (rY * rY)); + float green = (float)Math.Sqrt((gX * gX) + (gY * gY)); + float blue = (float)Math.Sqrt((bX * bX) + (bY * bY)); + + target[x, y] = new Color(red, green, blue); + } + } + }); + } + } +} diff --git a/src/ImageProcessor/Filters/Convolution/ConvolutionFilter.cs b/src/ImageProcessor/Filters/Convolution/ConvolutionFilter.cs new file mode 100644 index 0000000000..9ee763e6a4 --- /dev/null +++ b/src/ImageProcessor/Filters/Convolution/ConvolutionFilter.cs @@ -0,0 +1,83 @@ +// +// Copyright (c) James South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessor.Filters +{ + using System.Threading.Tasks; + + /// + /// Defines a filter that uses a matrix to perform convolution across a single dimension against an image. + /// + public abstract class ConvolutionFilter : ParallelImageProcessor + { + /// + /// Gets the horizontal gradient operator. + /// + public abstract float[,] KernelX { get; } + + /// + protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) + { + float[,] kernelX = this.KernelX; + int kernelLength = kernelX.GetLength(0); + int radius = kernelLength >> 1; + + int sourceY = sourceRectangle.Y; + int sourceBottom = sourceRectangle.Bottom; + int startX = sourceRectangle.X; + int endX = sourceRectangle.Right; + int maxY = sourceBottom - 1; + int maxX = endX - 1; + + Parallel.For( + startY, + endY, + y => + { + if (y >= sourceY && y < sourceBottom) + { + for (int x = startX; x < endX; x++) + { + float rX = 0; + float gX = 0; + float bX = 0; + + // Apply each matrix multiplier to the color components for each pixel. + for (int fy = 0; fy < kernelLength; fy++) + { + int fyr = fy - radius; + int offsetY = y + fyr; + + offsetY = offsetY.Clamp(0, maxY); + + for (int fx = 0; fx < kernelLength; fx++) + { + int fxr = fx - radius; + int offsetX = x + fxr; + + offsetX = offsetX.Clamp(0, maxX); + + Color currentColor = source[offsetX, offsetY]; + float r = currentColor.R; + float g = currentColor.G; + float b = currentColor.B; + + rX += kernelX[fy, fx] * r; + gX += kernelX[fy, fx] * g; + bX += kernelX[fy, fx] * b; + } + } + + float red = rX; + float green = gX; + float blue = bX; + + target[x, y] = new Color(red, green, blue); + } + } + }); + } + } +} diff --git a/src/ImageProcessor/Filters/Convolution/Kayyali.cs b/src/ImageProcessor/Filters/Convolution/Kayyali.cs new file mode 100644 index 0000000000..f322608832 --- /dev/null +++ b/src/ImageProcessor/Filters/Convolution/Kayyali.cs @@ -0,0 +1,34 @@ +// +// Copyright (c) James South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessor.Filters +{ + /// + /// The Kayyali operator filter. + /// + /// + public class Kayyali : Convolution2DFilter + { + /// + /// Gets the horizontal gradient operator. + /// + public override float[,] KernelX => new float[,] + { + { 6, 0, -6 }, + { 0, 0, 0 }, + { -6, 0, 6 } + }; + + /// + /// Gets the vertical gradient operator. + /// + public override float[,] KernelY => new float[,] + { + { -6, 0, 6 }, + { 0, 0, 0 }, + { 6, 0, -6 } + }; + } +} diff --git a/src/ImageProcessor/Filters/Convolution/Kirsch.cs b/src/ImageProcessor/Filters/Convolution/Kirsch.cs new file mode 100644 index 0000000000..e38572bbb2 --- /dev/null +++ b/src/ImageProcessor/Filters/Convolution/Kirsch.cs @@ -0,0 +1,34 @@ +// +// Copyright (c) James South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessor.Filters +{ + /// + /// The Kirsch operator filter. + /// + /// + public class Kirsch : Convolution2DFilter + { + /// + /// Gets the horizontal gradient operator. + /// + public override float[,] KernelX => new float[,] + { + { 5, 5, 5 }, + { -3, 0, -3 }, + { -3, -3, -3 } + }; + + /// + /// Gets the vertical gradient operator. + /// + public override float[,] KernelY => new float[,] + { + { 5, -3, -3 }, + { 5, 0, -3 }, + { 5, -3, -3 } + }; + } +} diff --git a/src/ImageProcessor/Filters/Convolution/Laplacian3X3.cs b/src/ImageProcessor/Filters/Convolution/Laplacian3X3.cs new file mode 100644 index 0000000000..e3a170da55 --- /dev/null +++ b/src/ImageProcessor/Filters/Convolution/Laplacian3X3.cs @@ -0,0 +1,24 @@ +// +// Copyright (c) James South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessor.Filters +{ + /// + /// The Laplacian 3 x 3 operator filter. + /// + /// + public class Laplacian3X3 : ConvolutionFilter + { + /// + /// Gets the horizontal gradient operator. + /// + public override float[,] KernelX => new float[,] + { + { -1, -1, -1 }, + { -1, 8, -1 }, + { -1, -1, -1 } + }; + } +} diff --git a/src/ImageProcessor/Filters/Convolution/Laplacian5X5.cs b/src/ImageProcessor/Filters/Convolution/Laplacian5X5.cs new file mode 100644 index 0000000000..7ee2a93edd --- /dev/null +++ b/src/ImageProcessor/Filters/Convolution/Laplacian5X5.cs @@ -0,0 +1,26 @@ +// +// Copyright (c) James South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessor.Filters +{ + /// + /// The Laplacian 5 x 5 operator filter. + /// + /// + public class Laplacian5X5 : ConvolutionFilter + { + /// + /// Gets the horizontal gradient operator. + /// + public override float[,] KernelX => new float[,] + { + { -1, -1, -1, -1, -1 }, + { -1, -1, -1, -1, -1 }, + { -1, -1, 24, -1, -1 }, + { -1, -1, -1, -1, -1 }, + { -1, -1, -1, -1, -1 } + }; + } +} diff --git a/src/ImageProcessor/Filters/Convolution/LaplacianOfGaussian.cs b/src/ImageProcessor/Filters/Convolution/LaplacianOfGaussian.cs new file mode 100644 index 0000000000..9a7883ce8b --- /dev/null +++ b/src/ImageProcessor/Filters/Convolution/LaplacianOfGaussian.cs @@ -0,0 +1,26 @@ +// +// Copyright (c) James South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessor.Filters +{ + /// + /// The Laplacian of Gaussian operator filter. + /// + /// + public class LaplacianOfGaussian : ConvolutionFilter + { + /// + /// Gets the horizontal gradient operator. + /// + public override float[,] KernelX => new float[,] + { + { 0, 0, -1, 0, 0 }, + { 0, -1, -2, -1, 0 }, + { -1, -2, 16, -2, -1 }, + { 0, -1, -2, -1, 0 }, + { 0, 0, -1, 0, 0 } + }; + } +} diff --git a/src/ImageProcessor/Filters/Convolution/Prewitt.cs b/src/ImageProcessor/Filters/Convolution/Prewitt.cs new file mode 100644 index 0000000000..c246ece0d1 --- /dev/null +++ b/src/ImageProcessor/Filters/Convolution/Prewitt.cs @@ -0,0 +1,34 @@ +// +// Copyright (c) James South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessor.Filters +{ + /// + /// The Prewitt operator filter. + /// + /// + public class Prewitt : Convolution2DFilter + { + /// + /// Gets the horizontal gradient operator. + /// + public override float[,] KernelX => new float[,] + { + { -1, 0, 1 }, + { -1, 0, 1 }, + { -1, 0, 1 } + }; + + /// + /// Gets the vertical gradient operator. + /// + public override float[,] KernelY => new float[,] + { + { 1, 1, 1 }, + { 0, 0, 0 }, + { -1, -1, -1 } + }; + } +} diff --git a/src/ImageProcessor/Filters/Convolution/RobertsCross.cs b/src/ImageProcessor/Filters/Convolution/RobertsCross.cs new file mode 100644 index 0000000000..9af29b3ec7 --- /dev/null +++ b/src/ImageProcessor/Filters/Convolution/RobertsCross.cs @@ -0,0 +1,32 @@ +// +// Copyright (c) James South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessor.Filters +{ + /// + /// The Roberts Cross operator filter. + /// + /// + public class RobertsCross : Convolution2DFilter + { + /// + /// Gets the horizontal gradient operator. + /// + public override float[,] KernelX => new float[,] + { + { 1, 0 }, + { 0, -1 } + }; + + /// + /// Gets the vertical gradient operator. + /// + public override float[,] KernelY => new float[,] + { + { 0, 1 }, + { -1, 0 } + }; + } +} diff --git a/src/ImageProcessor/Filters/Convolution/Scharr.cs b/src/ImageProcessor/Filters/Convolution/Scharr.cs new file mode 100644 index 0000000000..3ec8a8630e --- /dev/null +++ b/src/ImageProcessor/Filters/Convolution/Scharr.cs @@ -0,0 +1,34 @@ +// +// Copyright (c) James South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessor.Filters +{ + /// + /// The Scharr operator filter. + /// + /// + public class Scharr : Convolution2DFilter + { + /// + /// Gets the horizontal gradient operator. + /// + public override float[,] KernelX => new float[,] + { + { -3, 0, 3 }, + { -10, 0, 10 }, + { -3, 0, 3 } + }; + + /// + /// Gets the vertical gradient operator. + /// + public override float[,] KernelY => new float[,] + { + { 3, 10, 3 }, + { 0, 0, 0 }, + { -3, -10, -3 } + }; + } +} diff --git a/src/ImageProcessor/Filters/Convolution/Sobel.cs b/src/ImageProcessor/Filters/Convolution/Sobel.cs new file mode 100644 index 0000000000..a1a450cf33 --- /dev/null +++ b/src/ImageProcessor/Filters/Convolution/Sobel.cs @@ -0,0 +1,34 @@ +// +// Copyright (c) James South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessor.Filters +{ + /// + /// The Sobel operator filter. + /// + /// + public class Sobel : Convolution2DFilter + { + /// + /// Gets the horizontal gradient operator. + /// + public override float[,] KernelX => new float[,] + { + { -1, 0, 1 }, + { -2, 0, 2 }, + { -1, 0, 1 } + }; + + /// + /// Gets the vertical gradient operator. + /// + public override float[,] KernelY => new float[,] + { + { 1, 2, 1 }, + { 0, 0, 0 }, + { -1, -2, -1 } + }; + } +} diff --git a/src/ImageProcessor/ImageProcessor.csproj b/src/ImageProcessor/ImageProcessor.csproj index 3560f88b2d..d5076b03e1 100644 --- a/src/ImageProcessor/ImageProcessor.csproj +++ b/src/ImageProcessor/ImageProcessor.csproj @@ -49,11 +49,21 @@ + + + + + + + + + + @@ -220,6 +230,7 @@ + @@ -251,6 +262,7 @@ +