Browse Source

Merge pull request #108 from JimBobSquarePants/fast2d-convolution

Use Fast2DArray for all convolution algorithms.
af/merge-core
James Jackson-South 9 years ago
committed by GitHub
parent
commit
7044e15e24
  1. 3
      src/ImageSharp.Processing/Convolution/BoxBlur.cs
  2. 30
      src/ImageSharp.Processing/Processors/Convolution/BoxBlurProcessor.cs
  3. 31
      src/ImageSharp.Processing/Processors/Convolution/Convolution2DProcessor.cs
  4. 74
      src/ImageSharp.Processing/Processors/Convolution/Convolution2PassProcessor.cs
  5. 29
      src/ImageSharp.Processing/Processors/Convolution/ConvolutionProcessor.cs
  6. 15
      src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/EdgeDetector2DProcessor.cs
  7. 36
      src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/EdgeDetectorCompassProcessor.cs
  8. 23
      src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/EdgeDetectorProcessor.cs
  9. 38
      src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/KayyaliProcessor.cs
  10. 121
      src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/KirschProcessor.cs
  11. 22
      src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/Laplacian3X3Processor.cs
  12. 26
      src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/Laplacian5X5Processor.cs
  13. 26
      src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/LaplacianOfGaussianProcessor.cs
  14. 38
      src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/PrewittProcessor.cs
  15. 34
      src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/RobertsCrossProcessor.cs
  16. 121
      src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/RobinsonProcessor.cs
  17. 38
      src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/ScharrProcessor.cs
  18. 38
      src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/SobelProcessor.cs
  19. 31
      src/ImageSharp.Processing/Processors/Convolution/GaussianBlurProcessor.cs
  20. 33
      src/ImageSharp.Processing/Processors/Convolution/GaussianSharpenProcessor.cs
  21. 29
      src/ImageSharp/Common/Helpers/Fast2DArray{T}.cs
  22. 6
      src/ImageSharp/Common/Helpers/ImageMaths.cs
  23. 4
      src/ImageSharp/Dithering/ErrorDiffusion/Atkinson.cs
  24. 4
      src/ImageSharp/Dithering/ErrorDiffusion/Burks.cs
  25. 4
      src/ImageSharp/Dithering/ErrorDiffusion/FloydSteinberg.cs
  26. 4
      src/ImageSharp/Dithering/ErrorDiffusion/JarvisJudiceNinke.cs
  27. 4
      src/ImageSharp/Dithering/ErrorDiffusion/Sierra2.cs
  28. 4
      src/ImageSharp/Dithering/ErrorDiffusion/Sierra3.cs
  29. 4
      src/ImageSharp/Dithering/ErrorDiffusion/SierraLite.cs
  30. 4
      src/ImageSharp/Dithering/ErrorDiffusion/Stucki.cs
  31. 4
      src/ImageSharp/Dithering/Ordered/Bayer.cs
  32. 4
      src/ImageSharp/Dithering/Ordered/Ordered.cs
  33. 73
      tests/ImageSharp.Benchmarks/General/Array2D.cs
  34. 28
      tests/ImageSharp.Tests/Common/Fast2DArrayTests.cs

3
src/ImageSharp.Processing/Convolution/BoxBlur.cs

@ -7,7 +7,6 @@ namespace ImageSharp
{ {
using System; using System;
using Processing;
using Processing.Processors; using Processing.Processors;
/// <summary> /// <summary>
@ -41,7 +40,7 @@ namespace ImageSharp
public static Image<TColor> BoxBlur<TColor>(this Image<TColor> source, int radius, Rectangle rectangle) public static Image<TColor> BoxBlur<TColor>(this Image<TColor> source, int radius, Rectangle rectangle)
where TColor : struct, IPackedPixel, IEquatable<TColor> where TColor : struct, IPackedPixel, IEquatable<TColor>
{ {
source.ApplyProcessor(new BoxBlurProcessor<TColor>(), rectangle); source.ApplyProcessor(new BoxBlurProcessor<TColor>(radius), rectangle);
return source; return source;
} }
} }

30
src/ImageSharp.Processing/Processors/Convolution/BoxBlurProcessor.cs

@ -35,12 +35,12 @@ namespace ImageSharp.Processing.Processors
/// <summary> /// <summary>
/// Gets the horizontal gradient operator. /// Gets the horizontal gradient operator.
/// </summary> /// </summary>
public float[][] KernelX { get; } public Fast2DArray<float> KernelX { get; }
/// <summary> /// <summary>
/// Gets the vertical gradient operator. /// Gets the vertical gradient operator.
/// </summary> /// </summary>
public float[][] KernelY { get; } public Fast2DArray<float> KernelY { get; }
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnApply(ImageBase<TColor> source, Rectangle sourceRectangle) protected override void OnApply(ImageBase<TColor> source, Rectangle sourceRectangle)
@ -52,46 +52,42 @@ namespace ImageSharp.Processing.Processors
/// Create a 1 dimensional Box kernel. /// Create a 1 dimensional Box kernel.
/// </summary> /// </summary>
/// <param name="horizontal">Whether to calculate a horizontal kernel.</param> /// <param name="horizontal">Whether to calculate a horizontal kernel.</param>
/// <returns>The <see cref="T:float[][]"/></returns> /// <returns>The <see cref="Fast2DArray{T}"/></returns>
private float[][] CreateBoxKernel(bool horizontal) private Fast2DArray<float> CreateBoxKernel(bool horizontal)
{ {
int size = this.kernelSize; int size = this.kernelSize;
float[][] kernel = horizontal ? new float[1][] : new float[size][]; Fast2DArray<float> kernel = horizontal
? new Fast2DArray<float>(size, 1)
if (horizontal) : new Fast2DArray<float>(1, size);
{
kernel[0] = new float[size];
}
float sum = 0.0f;
float sum = 0F;
for (int i = 0; i < size; i++) for (int i = 0; i < size; i++)
{ {
float x = 1; float x = 1;
sum += x; sum += x;
if (horizontal) if (horizontal)
{ {
kernel[0][i] = x; kernel[0, i] = x;
} }
else else
{ {
kernel[i] = new[] { x }; kernel[i, 0] = x;
} }
} }
// Normalise kernel so that the sum of all weights equals 1 // Normalize kernel so that the sum of all weights equals 1
if (horizontal) if (horizontal)
{ {
for (int i = 0; i < size; i++) for (int i = 0; i < size; i++)
{ {
kernel[0][i] = kernel[0][i] / sum; kernel[0, i] = kernel[0, i] / sum;
} }
} }
else else
{ {
for (int i = 0; i < size; i++) for (int i = 0; i < size; i++)
{ {
kernel[i][0] = kernel[i][0] / sum; kernel[i, 0] = kernel[i, 0] / sum;
} }
} }

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

@ -21,7 +21,7 @@ namespace ImageSharp.Processing.Processors
/// </summary> /// </summary>
/// <param name="kernelX">The horizontal gradient operator.</param> /// <param name="kernelX">The horizontal gradient operator.</param>
/// <param name="kernelY">The vertical gradient operator.</param> /// <param name="kernelY">The vertical gradient operator.</param>
public Convolution2DProcessor(float[][] kernelX, float[][] kernelY) public Convolution2DProcessor(Fast2DArray<float> kernelX, Fast2DArray<float> kernelY)
{ {
this.KernelX = kernelX; this.KernelX = kernelX;
this.KernelY = kernelY; this.KernelY = kernelY;
@ -30,20 +30,20 @@ namespace ImageSharp.Processing.Processors
/// <summary> /// <summary>
/// Gets the horizontal gradient operator. /// Gets the horizontal gradient operator.
/// </summary> /// </summary>
public float[][] KernelX { get; } public Fast2DArray<float> KernelX { get; }
/// <summary> /// <summary>
/// Gets the vertical gradient operator. /// Gets the vertical gradient operator.
/// </summary> /// </summary>
public float[][] KernelY { get; } public Fast2DArray<float> KernelY { get; }
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnApply(ImageBase<TColor> source, Rectangle sourceRectangle) protected override void OnApply(ImageBase<TColor> source, Rectangle sourceRectangle)
{ {
int kernelYHeight = this.KernelY.Length; int kernelYHeight = this.KernelY.Height;
int kernelYWidth = this.KernelY[0].Length; int kernelYWidth = this.KernelY.Width;
int kernelXHeight = this.KernelX.Length; int kernelXHeight = this.KernelX.Height;
int kernelXWidth = this.KernelX[0].Length; int kernelXWidth = this.KernelX.Width;
int radiusY = kernelYHeight >> 1; int radiusY = kernelYHeight >> 1;
int radiusX = kernelXWidth >> 1; int radiusX = kernelXWidth >> 1;
@ -89,22 +89,21 @@ namespace ImageSharp.Processing.Processors
offsetX = offsetX.Clamp(0, maxX); offsetX = offsetX.Clamp(0, maxX);
Vector4 currentColor = sourcePixels[offsetX, offsetY].ToVector4(); Vector4 currentColor = sourcePixels[offsetX, offsetY].ToVector4();
float r = currentColor.X;
float g = currentColor.Y;
float b = currentColor.Z;
if (fy < kernelXHeight) if (fy < kernelXHeight)
{ {
rX += this.KernelX[fy][fx] * r; Vector4 kx = this.KernelX[fy, fx] * currentColor;
gX += this.KernelX[fy][fx] * g; rX += kx.X;
bX += this.KernelX[fy][fx] * b; gX += kx.Y;
bX += kx.Z;
} }
if (fx < kernelYWidth) if (fx < kernelYWidth)
{ {
rY += this.KernelY[fy][fx] * r; Vector4 ky = this.KernelY[fy, fx] * currentColor;
gY += this.KernelY[fy][fx] * g; rY += ky.X;
bY += this.KernelY[fy][fx] * b; gY += ky.Y;
bY += ky.Z;
} }
} }
} }

74
src/ImageSharp.Processing/Processors/Convolution/Convolution2PassProcessor.cs

@ -14,14 +14,14 @@ namespace ImageSharp.Processing.Processors
/// </summary> /// </summary>
/// <typeparam name="TColor">The pixel format.</typeparam> /// <typeparam name="TColor">The pixel format.</typeparam>
public class Convolution2PassProcessor<TColor> : ImageProcessor<TColor> public class Convolution2PassProcessor<TColor> : ImageProcessor<TColor>
where TColor : struct, IPackedPixel, IEquatable<TColor> where TColor : struct, IPackedPixel, IEquatable<TColor>
{ {
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Convolution2PassProcessor{TColor}"/> class. /// Initializes a new instance of the <see cref="Convolution2PassProcessor{TColor}"/> class.
/// </summary> /// </summary>
/// <param name="kernelX">The horizontal gradient operator.</param> /// <param name="kernelX">The horizontal gradient operator.</param>
/// <param name="kernelY">The vertical gradient operator.</param> /// <param name="kernelY">The vertical gradient operator.</param>
public Convolution2PassProcessor(float[][] kernelX, float[][] kernelY) public Convolution2PassProcessor(Fast2DArray<float> kernelX, Fast2DArray<float> kernelY)
{ {
this.KernelX = kernelX; this.KernelX = kernelX;
this.KernelY = kernelY; this.KernelY = kernelY;
@ -30,18 +30,16 @@ namespace ImageSharp.Processing.Processors
/// <summary> /// <summary>
/// Gets the horizontal gradient operator. /// Gets the horizontal gradient operator.
/// </summary> /// </summary>
public float[][] KernelX { get; } public Fast2DArray<float> KernelX { get; }
/// <summary> /// <summary>
/// Gets the vertical gradient operator. /// Gets the vertical gradient operator.
/// </summary> /// </summary>
public float[][] KernelY { get; } public Fast2DArray<float> KernelY { get; }
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnApply(ImageBase<TColor> source, Rectangle sourceRectangle) protected override void OnApply(ImageBase<TColor> source, Rectangle sourceRectangle)
{ {
float[][] kernelX = this.KernelX;
float[][] kernelY = this.KernelY;
int width = source.Width; int width = source.Width;
int height = source.Height; int height = source.Height;
@ -50,8 +48,8 @@ namespace ImageSharp.Processing.Processors
using (PixelAccessor<TColor> firstPassPixels = new PixelAccessor<TColor>(width, height)) using (PixelAccessor<TColor> firstPassPixels = new PixelAccessor<TColor>(width, height))
using (PixelAccessor<TColor> sourcePixels = source.Lock()) using (PixelAccessor<TColor> sourcePixels = source.Lock())
{ {
this.ApplyConvolution(width, height, firstPassPixels, sourcePixels, sourceRectangle, kernelX); this.ApplyConvolution(firstPassPixels, sourcePixels, sourceRectangle, this.KernelX);
this.ApplyConvolution(width, height, targetPixels, firstPassPixels, sourceRectangle, kernelY); this.ApplyConvolution(targetPixels, firstPassPixels, sourceRectangle, this.KernelY);
} }
source.SwapPixelsBuffers(targetPixels); source.SwapPixelsBuffers(targetPixels);
@ -62,18 +60,16 @@ namespace ImageSharp.Processing.Processors
/// Applies the process to the specified portion of the specified <see cref="ImageBase{TColor}"/> at the specified location /// Applies the process to the specified portion of the specified <see cref="ImageBase{TColor}"/> at the specified location
/// and with the specified size. /// and with the specified size.
/// </summary> /// </summary>
/// <param name="width">The image width.</param>
/// <param name="height">The image height.</param>
/// <param name="targetPixels">The target pixels to apply the process to.</param> /// <param name="targetPixels">The target pixels to apply the process to.</param>
/// <param name="sourcePixels">The source pixels. Cannot be null.</param> /// <param name="sourcePixels">The source pixels. Cannot be null.</param>
/// <param name="sourceRectangle"> /// <param name="sourceRectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to draw. /// The <see cref="Rectangle"/> structure that specifies the portion of the image object to draw.
/// </param> /// </param>
/// <param name="kernel">The kernel operator.</param> /// <param name="kernel">The kernel operator.</param>
private void ApplyConvolution(int width, int height, PixelAccessor<TColor> targetPixels, PixelAccessor<TColor> sourcePixels, Rectangle sourceRectangle, float[][] kernel) private void ApplyConvolution(PixelAccessor<TColor> targetPixels, PixelAccessor<TColor> sourcePixels, Rectangle sourceRectangle, Fast2DArray<float> kernel)
{ {
int kernelHeight = kernel.Length; int kernelHeight = kernel.Height;
int kernelWidth = kernel[0].Length; int kernelWidth = kernel.Width;
int radiusY = kernelHeight >> 1; int radiusY = kernelHeight >> 1;
int radiusX = kernelWidth >> 1; int radiusX = kernelWidth >> 1;
@ -85,40 +81,40 @@ namespace ImageSharp.Processing.Processors
int maxX = endX - 1; int maxX = endX - 1;
Parallel.For( Parallel.For(
startY, startY,
endY, endY,
this.ParallelOptions, this.ParallelOptions,
y => y =>
{
for (int x = startX; x < endX; x++)
{ {
Vector4 destination = default(Vector4); for (int x = startX; x < endX; x++)
// Apply each matrix multiplier to the color components for each pixel.
for (int fy = 0; fy < kernelHeight; fy++)
{ {
int fyr = fy - radiusY; Vector4 destination = default(Vector4);
int offsetY = y + fyr;
offsetY = offsetY.Clamp(0, maxY); // Apply each matrix multiplier to the color components for each pixel.
for (int fy = 0; fy < kernelHeight; fy++)
for (int fx = 0; fx < kernelWidth; fx++)
{ {
int fxr = fx - radiusX; int fyr = fy - radiusY;
int offsetX = x + fxr; int offsetY = y + fyr;
offsetY = offsetY.Clamp(0, maxY);
offsetX = offsetX.Clamp(0, maxX); for (int fx = 0; fx < kernelWidth; fx++)
{
int fxr = fx - radiusX;
int offsetX = x + fxr;
Vector4 currentColor = sourcePixels[offsetX, offsetY].ToVector4(); offsetX = offsetX.Clamp(0, maxX);
destination += kernel[fy][fx] * currentColor;
Vector4 currentColor = sourcePixels[offsetX, offsetY].ToVector4();
destination += kernel[fy, fx] * currentColor;
}
} }
}
TColor packed = default(TColor); TColor packed = default(TColor);
packed.PackFromVector4(destination); packed.PackFromVector4(destination);
targetPixels[x, y] = packed; targetPixels[x, y] = packed;
} }
}); });
} }
} }
} }

29
src/ImageSharp.Processing/Processors/Convolution/ConvolutionProcessor.cs

@ -14,13 +14,13 @@ namespace ImageSharp.Processing.Processors
/// </summary> /// </summary>
/// <typeparam name="TColor">The pixel format.</typeparam> /// <typeparam name="TColor">The pixel format.</typeparam>
public class ConvolutionProcessor<TColor> : ImageProcessor<TColor> public class ConvolutionProcessor<TColor> : ImageProcessor<TColor>
where TColor : struct, IPackedPixel, IEquatable<TColor> where TColor : struct, IPackedPixel, IEquatable<TColor>
{ {
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ConvolutionProcessor{TColor}"/> class. /// Initializes a new instance of the <see cref="ConvolutionProcessor{TColor}"/> class.
/// </summary> /// </summary>
/// <param name="kernelXY">The 2d gradient operator.</param> /// <param name="kernelXY">The 2d gradient operator.</param>
public ConvolutionProcessor(float[][] kernelXY) public ConvolutionProcessor(Fast2DArray<float> kernelXY)
{ {
this.KernelXY = kernelXY; this.KernelXY = kernelXY;
} }
@ -28,13 +28,12 @@ namespace ImageSharp.Processing.Processors
/// <summary> /// <summary>
/// Gets the 2d gradient operator. /// Gets the 2d gradient operator.
/// </summary> /// </summary>
public virtual float[][] KernelXY { get; } public Fast2DArray<float> KernelXY { get; }
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnApply(ImageBase<TColor> source, Rectangle sourceRectangle) protected override void OnApply(ImageBase<TColor> source, Rectangle sourceRectangle)
{ {
float[][] kernelX = this.KernelXY; int kernelLength = this.KernelXY.Height;
int kernelLength = kernelX.GetLength(0);
int radius = kernelLength >> 1; int radius = kernelLength >> 1;
int startY = sourceRectangle.Y; int startY = sourceRectangle.Y;
@ -56,9 +55,9 @@ namespace ImageSharp.Processing.Processors
{ {
for (int x = startX; x < endX; x++) for (int x = startX; x < endX; x++)
{ {
float rX = 0; float red = 0;
float gX = 0; float green = 0;
float bX = 0; float blue = 0;
// Apply each matrix multiplier to the color components for each pixel. // Apply each matrix multiplier to the color components for each pixel.
for (int fy = 0; fy < kernelLength; fy++) for (int fy = 0; fy < kernelLength; fy++)
@ -76,20 +75,14 @@ namespace ImageSharp.Processing.Processors
offsetX = offsetX.Clamp(0, maxX); offsetX = offsetX.Clamp(0, maxX);
Vector4 currentColor = sourcePixels[offsetX, offsetY].ToVector4(); Vector4 currentColor = sourcePixels[offsetX, offsetY].ToVector4();
float r = currentColor.X; currentColor *= this.KernelXY[fy, fx];
float g = currentColor.Y;
float b = currentColor.Z;
rX += kernelX[fy][fx] * r; red += currentColor.X;
gX += kernelX[fy][fx] * g; green += currentColor.Y;
bX += kernelX[fy][fx] * b; blue += currentColor.Z;
} }
} }
float red = rX;
float green = gX;
float blue = bX;
TColor packed = default(TColor); TColor packed = default(TColor);
packed.PackFromVector4(new Vector4(red, green, blue, sourcePixels[x, y].ToVector4().W)); packed.PackFromVector4(new Vector4(red, green, blue, sourcePixels[x, y].ToVector4().W));
targetPixels[x, y] = packed; targetPixels[x, y] = packed;

15
src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/EdgeDetector2DProcessor.cs

@ -14,15 +14,26 @@ namespace ImageSharp.Processing.Processors
public abstract class EdgeDetector2DProcessor<TColor> : ImageProcessor<TColor>, IEdgeDetectorProcessor<TColor> public abstract class EdgeDetector2DProcessor<TColor> : ImageProcessor<TColor>, IEdgeDetectorProcessor<TColor>
where TColor : struct, IPackedPixel, IEquatable<TColor> where TColor : struct, IPackedPixel, IEquatable<TColor>
{ {
/// <summary>
/// Initializes a new instance of the <see cref="EdgeDetector2DProcessor{TColor}"/> class.
/// </summary>
/// <param name="kernelX">The horizontal gradient operator.</param>
/// <param name="kernelY">The vertical gradient operator.</param>
protected EdgeDetector2DProcessor(Fast2DArray<float> kernelX, Fast2DArray<float> kernelY)
{
this.KernelX = kernelX;
this.KernelY = kernelY;
}
/// <summary> /// <summary>
/// Gets the horizontal gradient operator. /// Gets the horizontal gradient operator.
/// </summary> /// </summary>
public abstract float[][] KernelX { get; } public Fast2DArray<float> KernelX { get; }
/// <summary> /// <summary>
/// Gets the vertical gradient operator. /// Gets the vertical gradient operator.
/// </summary> /// </summary>
public abstract float[][] KernelY { get; } public Fast2DArray<float> KernelY { get; }
/// <inheritdoc/> /// <inheritdoc/>
public bool Grayscale { get; set; } public bool Grayscale { get; set; }

36
src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/EdgeDetectorCompassProcessor.cs

@ -19,50 +19,59 @@ namespace ImageSharp.Processing.Processors
/// <summary> /// <summary>
/// Gets the North gradient operator /// Gets the North gradient operator
/// </summary> /// </summary>
public abstract float[][] North { get; } public abstract Fast2DArray<float> North { get; }
/// <summary> /// <summary>
/// Gets the NorthWest gradient operator /// Gets the NorthWest gradient operator
/// </summary> /// </summary>
public abstract float[][] NorthWest { get; } public abstract Fast2DArray<float> NorthWest { get; }
/// <summary> /// <summary>
/// Gets the West gradient operator /// Gets the West gradient operator
/// </summary> /// </summary>
public abstract float[][] West { get; } public abstract Fast2DArray<float> West { get; }
/// <summary> /// <summary>
/// Gets the SouthWest gradient operator /// Gets the SouthWest gradient operator
/// </summary> /// </summary>
public abstract float[][] SouthWest { get; } public abstract Fast2DArray<float> SouthWest { get; }
/// <summary> /// <summary>
/// Gets the South gradient operator /// Gets the South gradient operator
/// </summary> /// </summary>
public abstract float[][] South { get; } public abstract Fast2DArray<float> South { get; }
/// <summary> /// <summary>
/// Gets the SouthEast gradient operator /// Gets the SouthEast gradient operator
/// </summary> /// </summary>
public abstract float[][] SouthEast { get; } public abstract Fast2DArray<float> SouthEast { get; }
/// <summary> /// <summary>
/// Gets the East gradient operator /// Gets the East gradient operator
/// </summary> /// </summary>
public abstract float[][] East { get; } public abstract Fast2DArray<float> East { get; }
/// <summary> /// <summary>
/// Gets the NorthEast gradient operator /// Gets the NorthEast gradient operator
/// </summary> /// </summary>
public abstract float[][] NorthEast { get; } public abstract Fast2DArray<float> NorthEast { get; }
/// <inheritdoc/> /// <inheritdoc/>
public bool Grayscale { get; set; } public bool Grayscale { get; set; }
/// <inheritdoc/>
protected override void BeforeApply(ImageBase<TColor> source, Rectangle sourceRectangle)
{
if (this.Grayscale)
{
new GrayscaleBt709Processor<TColor>().Apply(source, sourceRectangle);
}
}
/// <inheritdoc /> /// <inheritdoc />
protected override void OnApply(ImageBase<TColor> source, Rectangle sourceRectangle) protected override void OnApply(ImageBase<TColor> source, Rectangle sourceRectangle)
{ {
float[][][] kernels = { this.North, this.NorthWest, this.West, this.SouthWest, this.South, this.SouthEast, this.East, this.NorthEast }; Fast2DArray<float>[] kernels = { this.North, this.NorthWest, this.West, this.SouthWest, this.South, this.SouthEast, this.East, this.NorthEast };
int startY = sourceRectangle.Y; int startY = sourceRectangle.Y;
int endY = sourceRectangle.Bottom; int endY = sourceRectangle.Bottom;
@ -132,14 +141,5 @@ namespace ImageSharp.Processing.Processors
} }
} }
} }
/// <inheritdoc/>
protected override void BeforeApply(ImageBase<TColor> source, Rectangle sourceRectangle)
{
if (this.Grayscale)
{
new GrayscaleBt709Processor<TColor>().Apply(source, sourceRectangle);
}
}
} }
} }

23
src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/EdgeDetectorProcessor.cs

@ -14,19 +14,22 @@ namespace ImageSharp.Processing.Processors
public abstract class EdgeDetectorProcessor<TColor> : ImageProcessor<TColor>, IEdgeDetectorProcessor<TColor> public abstract class EdgeDetectorProcessor<TColor> : ImageProcessor<TColor>, IEdgeDetectorProcessor<TColor>
where TColor : struct, IPackedPixel, IEquatable<TColor> where TColor : struct, IPackedPixel, IEquatable<TColor>
{ {
/// <summary>
/// Initializes a new instance of the <see cref="EdgeDetectorProcessor{TColor}"/> class.
/// </summary>
/// <param name="kernelXY">The 2d gradient operator.</param>
protected EdgeDetectorProcessor(Fast2DArray<float> kernelXY)
{
this.KernelXY = kernelXY;
}
/// <inheritdoc/> /// <inheritdoc/>
public bool Grayscale { get; set; } public bool Grayscale { get; set; }
/// <summary> /// <summary>
/// Gets the 2d gradient operator. /// Gets the 2d gradient operator.
/// </summary> /// </summary>
public abstract float[][] KernelXY { get; } public Fast2DArray<float> KernelXY { get; }
/// <inheritdoc/>
protected override void OnApply(ImageBase<TColor> source, Rectangle sourceRectangle)
{
new ConvolutionProcessor<TColor>(this.KernelXY).Apply(source, sourceRectangle);
}
/// <inheritdoc/> /// <inheritdoc/>
protected override void BeforeApply(ImageBase<TColor> source, Rectangle sourceRectangle) protected override void BeforeApply(ImageBase<TColor> source, Rectangle sourceRectangle)
@ -36,5 +39,11 @@ namespace ImageSharp.Processing.Processors
new GrayscaleBt709Processor<TColor>().Apply(source, sourceRectangle); new GrayscaleBt709Processor<TColor>().Apply(source, sourceRectangle);
} }
} }
/// <inheritdoc/>
protected override void OnApply(ImageBase<TColor> source, Rectangle sourceRectangle)
{
new ConvolutionProcessor<TColor>(this.KernelXY).Apply(source, sourceRectangle);
}
} }
} }

38
src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/KayyaliProcessor.cs

@ -20,27 +20,31 @@ namespace ImageSharp.Processing.Processors
/// <summary> /// <summary>
/// The horizontal gradient operator. /// The horizontal gradient operator.
/// </summary> /// </summary>
private static readonly float[][] KayyaliX = private static readonly Fast2DArray<float> KayyaliX =
{ new float[,]
new float[] { 6, 0, -6 }, {
new float[] { 0, 0, 0 }, { 6, 0, -6 },
new float[] { -6, 0, 6 } { 0, 0, 0 },
}; { -6, 0, 6 }
};
/// <summary> /// <summary>
/// The vertical gradient operator. /// The vertical gradient operator.
/// </summary> /// </summary>
private static readonly float[][] KayyaliY = private static readonly Fast2DArray<float> KayyaliY =
{ new float[,]
new float[] { -6, 0, 6 }, {
new float[] { 0, 0, 0 }, { -6, 0, 6 },
new float[] { 6, 0, -6 } { 0, 0, 0 },
}; { 6, 0, -6 }
};
/// <inheritdoc/>
public override float[][] KernelX => KayyaliX;
/// <inheritdoc/> /// <summary>
public override float[][] KernelY => KayyaliY; /// Initializes a new instance of the <see cref="KayyaliProcessor{TColor}"/> class.
/// </summary>
public KayyaliProcessor()
: base(KayyaliX, KayyaliY)
{
}
} }
} }

121
src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/KirschProcessor.cs

@ -2,6 +2,7 @@
// Copyright (c) James Jackson-South and contributors. // Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
// </copyright> // </copyright>
namespace ImageSharp.Processing.Processors namespace ImageSharp.Processing.Processors
{ {
using System; using System;
@ -19,105 +20,113 @@ namespace ImageSharp.Processing.Processors
/// <summary> /// <summary>
/// The North gradient operator /// The North gradient operator
/// </summary> /// </summary>
private static readonly float[][] KirschNorth = private static readonly Fast2DArray<float> KirschNorth =
{ new float[,]
new float[] { 5, 5, 5 }, {
new float[] { -3, 0, -3 }, { 5, 5, 5 },
new float[] { -3, -3, -3 } { -3, 0, -3 },
}; { -3, -3, -3 }
};
/// <summary> /// <summary>
/// The NorthWest gradient operator /// The NorthWest gradient operator
/// </summary> /// </summary>
private static readonly float[][] KirschNorthWest = private static readonly Fast2DArray<float> KirschNorthWest =
{ new float[,]
new float[] { 5, 5, -3 }, {
new float[] { 5, 0, -3 }, { 5, 5, -3 },
new float[] { -3, -3, -3 } { 5, 0, -3 },
}; { -3, -3, -3 }
};
/// <summary> /// <summary>
/// The West gradient operator /// The West gradient operator
/// </summary> /// </summary>
private static readonly float[][] KirschWest = private static readonly Fast2DArray<float> KirschWest =
{ new float[,]
new float[] { 5, -3, -3 }, {
new float[] { 5, 0, -3 }, { 5, -3, -3 },
new float[] { 5, -3, -3 } { 5, 0, -3 },
}; { 5, -3, -3 }
};
/// <summary> /// <summary>
/// The SouthWest gradient operator /// The SouthWest gradient operator
/// </summary> /// </summary>
private static readonly float[][] KirschSouthWest = private static readonly Fast2DArray<float> KirschSouthWest =
{ new float[,]
new float[] { -3, -3, -3 }, {
new float[] { 5, 0, -3 }, { -3, -3, -3 },
new float[] { 5, 5, -3 } { 5, 0, -3 },
}; { 5, 5, -3 }
};
/// <summary> /// <summary>
/// The South gradient operator /// The South gradient operator
/// </summary> /// </summary>
private static readonly float[][] KirschSouth = private static readonly Fast2DArray<float> KirschSouth =
{ new float[,]
new float[] { -3, -3, -3 }, {
new float[] { -3, 0, -3 }, { -3, -3, -3 },
new float[] { 5, 5, 5 } { -3, 0, -3 },
}; { 5, 5, 5 }
};
/// <summary> /// <summary>
/// The SouthEast gradient operator /// The SouthEast gradient operator
/// </summary> /// </summary>
private static readonly float[][] KirschSouthEast = private static readonly Fast2DArray<float> KirschSouthEast =
{ new float[,]
new float[] { -3, -3, -3 }, {
new float[] { -3, 0, 5 }, { -3, -3, -3 },
new float[] { -3, 5, 5 } { -3, 0, 5 },
}; { -3, 5, 5 }
};
/// <summary> /// <summary>
/// The East gradient operator /// The East gradient operator
/// </summary> /// </summary>
private static readonly float[][] KirschEast = private static readonly Fast2DArray<float> KirschEast =
{ new float[,]
new float[] { -3, -3, 5 }, {
new float[] { -3, 0, 5 }, { -3, -3, 5 },
new float[] { -3, -3, 5 } { -3, 0, 5 },
}; { -3, -3, 5 }
};
/// <summary> /// <summary>
/// The NorthEast gradient operator /// The NorthEast gradient operator
/// </summary> /// </summary>
private static readonly float[][] KirschNorthEast = private static readonly Fast2DArray<float> KirschNorthEast =
{ new float[,]
new float[] { -3, 5, 5 }, {
new float[] { -3, 0, 5 }, { -3, 5, 5 },
new float[] { -3, -3, -3 } { -3, 0, 5 },
}; { -3, -3, -3 }
};
/// <inheritdoc/> /// <inheritdoc/>
public override float[][] North => KirschNorth; public override Fast2DArray<float> North => KirschNorth;
/// <inheritdoc/> /// <inheritdoc/>
public override float[][] NorthWest => KirschNorthWest; public override Fast2DArray<float> NorthWest => KirschNorthWest;
/// <inheritdoc/> /// <inheritdoc/>
public override float[][] West => KirschWest; public override Fast2DArray<float> West => KirschWest;
/// <inheritdoc/> /// <inheritdoc/>
public override float[][] SouthWest => KirschSouthWest; public override Fast2DArray<float> SouthWest => KirschSouthWest;
/// <inheritdoc/> /// <inheritdoc/>
public override float[][] South => KirschSouth; public override Fast2DArray<float> South => KirschSouth;
/// <inheritdoc/> /// <inheritdoc/>
public override float[][] SouthEast => KirschSouthEast; public override Fast2DArray<float> SouthEast => KirschSouthEast;
/// <inheritdoc/> /// <inheritdoc/>
public override float[][] East => KirschEast; public override Fast2DArray<float> East => KirschEast;
/// <inheritdoc/> /// <inheritdoc/>
public override float[][] NorthEast => KirschNorthEast; public override Fast2DArray<float> NorthEast => KirschNorthEast;
} }
} }

22
src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/Laplacian3X3Processor.cs

@ -20,14 +20,20 @@ namespace ImageSharp.Processing.Processors
/// <summary> /// <summary>
/// The 2d gradient operator. /// The 2d gradient operator.
/// </summary> /// </summary>
private static readonly float[][] Laplacian3X3XY = new float[][] private static readonly Fast2DArray<float> Laplacian3X3XY =
{ new float[,]
new float[] { -1, -1, -1 }, {
new float[] { -1, 8, -1 }, { -1, -1, -1 },
new float[] { -1, -1, -1 } { -1, 8, -1 },
}; { -1, -1, -1 }
};
/// <inheritdoc/> /// <summary>
public override float[][] KernelXY => Laplacian3X3XY; /// Initializes a new instance of the <see cref="Laplacian3X3Processor{TColor}"/> class.
/// </summary>
public Laplacian3X3Processor()
: base(Laplacian3X3XY)
{
}
} }
} }

26
src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/Laplacian5X5Processor.cs

@ -20,16 +20,22 @@ namespace ImageSharp.Processing.Processors
/// <summary> /// <summary>
/// The 2d gradient operator. /// The 2d gradient operator.
/// </summary> /// </summary>
private static readonly float[][] Laplacian5X5XY = private static readonly Fast2DArray<float> Laplacian5X5XY =
{ new float[,]
new float[] { -1, -1, -1, -1, -1 }, {
new float[] { -1, -1, -1, -1, -1 }, { -1, -1, -1, -1, -1 },
new float[] { -1, -1, 24, -1, -1 }, { -1, -1, -1, -1, -1 },
new float[] { -1, -1, -1, -1, -1 }, { -1, -1, 24, -1, -1 },
new float[] { -1, -1, -1, -1, -1 } { -1, -1, -1, -1, -1 },
}; { -1, -1, -1, -1, -1 }
};
/// <inheritdoc/> /// <summary>
public override float[][] KernelXY => Laplacian5X5XY; /// Initializes a new instance of the <see cref="Laplacian5X5Processor{TColor}"/> class.
/// </summary>
public Laplacian5X5Processor()
: base(Laplacian5X5XY)
{
}
} }
} }

26
src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/LaplacianOfGaussianProcessor.cs

@ -20,16 +20,22 @@ namespace ImageSharp.Processing.Processors
/// <summary> /// <summary>
/// The 2d gradient operator. /// The 2d gradient operator.
/// </summary> /// </summary>
private static readonly float[][] LaplacianOfGaussianXY = private static readonly Fast2DArray<float> LaplacianOfGaussianXY =
{ new float[,]
new float[] { 0, 0, -1, 0, 0 }, {
new float[] { 0, -1, -2, -1, 0 }, { 0, 0, -1, 0, 0 },
new float[] { -1, -2, 16, -2, -1 }, { 0, -1, -2, -1, 0 },
new float[] { 0, -1, -2, -1, 0 }, { -1, -2, 16, -2, -1 },
new float[] { 0, 0, -1, 0, 0 } { 0, -1, -2, -1, 0 },
}; { 0, 0, -1, 0, 0 }
};
/// <inheritdoc/> /// <summary>
public override float[][] KernelXY => LaplacianOfGaussianXY; /// Initializes a new instance of the <see cref="LaplacianOfGaussianProcessor{TColor}"/> class.
/// </summary>
public LaplacianOfGaussianProcessor()
: base(LaplacianOfGaussianXY)
{
}
} }
} }

38
src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/PrewittProcessor.cs

@ -20,27 +20,31 @@ namespace ImageSharp.Processing.Processors
/// <summary> /// <summary>
/// The horizontal gradient operator. /// The horizontal gradient operator.
/// </summary> /// </summary>
private static readonly float[][] PrewittX = private static readonly Fast2DArray<float> PrewittX =
{ new float[,]
new float[] { -1, 0, 1 }, {
new float[] { -1, 0, 1 }, { -1, 0, 1 },
new float[] { -1, 0, 1 } { -1, 0, 1 },
}; { -1, 0, 1 }
};
/// <summary> /// <summary>
/// The vertical gradient operator. /// The vertical gradient operator.
/// </summary> /// </summary>
private static readonly float[][] PrewittY = private static readonly Fast2DArray<float> PrewittY =
{ new float[,]
new float[] { 1, 1, 1 }, {
new float[] { 0, 0, 0 }, { 1, 1, 1 },
new float[] { -1, -1, -1 } { 0, 0, 0 },
}; { -1, -1, -1 }
};
/// <inheritdoc/>
public override float[][] KernelX => PrewittX;
/// <inheritdoc/> /// <summary>
public override float[][] KernelY => PrewittY; /// Initializes a new instance of the <see cref="PrewittProcessor{TColor}"/> class.
/// </summary>
public PrewittProcessor()
: base(PrewittX, PrewittY)
{
}
} }
} }

34
src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/RobertsCrossProcessor.cs

@ -20,25 +20,29 @@ namespace ImageSharp.Processing.Processors
/// <summary> /// <summary>
/// The horizontal gradient operator. /// The horizontal gradient operator.
/// </summary> /// </summary>
private static readonly float[][] RobertsCrossX = private static readonly Fast2DArray<float> RobertsCrossX =
{ new float[,]
new float[] { 1, 0 }, {
new float[] { 0, -1 } { 1, 0 },
}; { 0, -1 }
};
/// <summary> /// <summary>
/// The vertical gradient operator. /// The vertical gradient operator.
/// </summary> /// </summary>
private static readonly float[][] RobertsCrossY = private static readonly Fast2DArray<float> RobertsCrossY =
{ new float[,]
new float[] { 0, 1 }, {
new float[] { -1, 0 } { 0, 1 },
}; { -1, 0 }
};
/// <inheritdoc/>
public override float[][] KernelX => RobertsCrossX;
/// <inheritdoc/> /// <summary>
public override float[][] KernelY => RobertsCrossY; /// Initializes a new instance of the <see cref="RobertsCrossProcessor{TColor}"/> class.
/// </summary>
public RobertsCrossProcessor()
: base(RobertsCrossX, RobertsCrossY)
{
}
} }
} }

121
src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/RobinsonProcessor.cs

@ -2,6 +2,7 @@
// Copyright (c) James Jackson-South and contributors. // Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
// </copyright> // </copyright>
namespace ImageSharp.Processing.Processors namespace ImageSharp.Processing.Processors
{ {
using System; using System;
@ -19,105 +20,113 @@ namespace ImageSharp.Processing.Processors
/// <summary> /// <summary>
/// The North gradient operator /// The North gradient operator
/// </summary> /// </summary>
private static readonly float[][] RobinsonNorth = private static readonly Fast2DArray<float> RobinsonNorth =
{ new float[,]
new float[] { 1, 2, 1 }, {
new float[] { 0, 0, 0 }, { 1, 2, 1 },
new float[] { -1, -2, -1 } { 0, 0, 0 },
}; { -1, -2, -1 }
};
/// <summary> /// <summary>
/// The NorthWest gradient operator /// The NorthWest gradient operator
/// </summary> /// </summary>
private static readonly float[][] RobinsonNorthWest = private static readonly Fast2DArray<float> RobinsonNorthWest =
{ new float[,]
new float[] { 2, 1, 0 }, {
new float[] { 1, 0, -1 }, { 2, 1, 0 },
new float[] { 0, -1, -2 } { 1, 0, -1 },
}; { 0, -1, -2 }
};
/// <summary> /// <summary>
/// The West gradient operator /// The West gradient operator
/// </summary> /// </summary>
private static readonly float[][] RobinsonWest = private static readonly Fast2DArray<float> RobinsonWest =
{ new float[,]
new float[] { 1, 0, -1 }, {
new float[] { 2, 0, -2 }, { 1, 0, -1 },
new float[] { 1, 0, -1 } { 2, 0, -2 },
}; { 1, 0, -1 }
};
/// <summary> /// <summary>
/// The SouthWest gradient operator /// The SouthWest gradient operator
/// </summary> /// </summary>
private static readonly float[][] RobinsonSouthWest = private static readonly Fast2DArray<float> RobinsonSouthWest =
{ new float[,]
new float[] { 0, -1, -2 }, {
new float[] { 1, 0, -1 }, { 0, -1, -2 },
new float[] { 2, 1, 0 } { 1, 0, -1 },
}; { 2, 1, 0 }
};
/// <summary> /// <summary>
/// The South gradient operator /// The South gradient operator
/// </summary> /// </summary>
private static readonly float[][] RobinsonSouth = private static readonly Fast2DArray<float> RobinsonSouth =
{ new float[,]
new float[] { -1, -2, -1 }, {
new float[] { 0, 0, 0 }, { -1, -2, -1 },
new float[] { 1, 2, 1 } { 0, 0, 0 },
}; { 1, 2, 1 }
};
/// <summary> /// <summary>
/// The SouthEast gradient operator /// The SouthEast gradient operator
/// </summary> /// </summary>
private static readonly float[][] RobinsonSouthEast = private static readonly Fast2DArray<float> RobinsonSouthEast =
{ new float[,]
new float[] { -2, -1, 0 }, {
new float[] { -1, 0, 1 }, { -2, -1, 0 },
new float[] { 0, 1, 2 } { -1, 0, 1 },
}; { 0, 1, 2 }
};
/// <summary> /// <summary>
/// The East gradient operator /// The East gradient operator
/// </summary> /// </summary>
private static readonly float[][] RobinsonEast = private static readonly Fast2DArray<float> RobinsonEast =
{ new float[,]
new float[] { -1, 0, 1 }, {
new float[] { -2, 0, 2 }, { -1, 0, 1 },
new float[] { -1, 0, 1 } { -2, 0, 2 },
}; { -1, 0, 1 }
};
/// <summary> /// <summary>
/// The NorthEast gradient operator /// The NorthEast gradient operator
/// </summary> /// </summary>
private static readonly float[][] RobinsonNorthEast = private static readonly Fast2DArray<float> RobinsonNorthEast =
{ new float[,]
new float[] { 0, 1, 2 }, {
new float[] { -1, 0, 1 }, { 0, 1, 2 },
new float[] { -2, -1, 0 } { -1, 0, 1 },
}; { -2, -1, 0 }
};
/// <inheritdoc/> /// <inheritdoc/>
public override float[][] North => RobinsonNorth; public override Fast2DArray<float> North => RobinsonNorth;
/// <inheritdoc/> /// <inheritdoc/>
public override float[][] NorthWest => RobinsonNorthWest; public override Fast2DArray<float> NorthWest => RobinsonNorthWest;
/// <inheritdoc/> /// <inheritdoc/>
public override float[][] West => RobinsonWest; public override Fast2DArray<float> West => RobinsonWest;
/// <inheritdoc/> /// <inheritdoc/>
public override float[][] SouthWest => RobinsonSouthWest; public override Fast2DArray<float> SouthWest => RobinsonSouthWest;
/// <inheritdoc/> /// <inheritdoc/>
public override float[][] South => RobinsonSouth; public override Fast2DArray<float> South => RobinsonSouth;
/// <inheritdoc/> /// <inheritdoc/>
public override float[][] SouthEast => RobinsonSouthEast; public override Fast2DArray<float> SouthEast => RobinsonSouthEast;
/// <inheritdoc/> /// <inheritdoc/>
public override float[][] East => RobinsonEast; public override Fast2DArray<float> East => RobinsonEast;
/// <inheritdoc/> /// <inheritdoc/>
public override float[][] NorthEast => RobinsonNorthEast; public override Fast2DArray<float> NorthEast => RobinsonNorthEast;
} }
} }

38
src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/ScharrProcessor.cs

@ -20,27 +20,31 @@ namespace ImageSharp.Processing.Processors
/// <summary> /// <summary>
/// The horizontal gradient operator. /// The horizontal gradient operator.
/// </summary> /// </summary>
private static readonly float[][] ScharrX = new float[3][] private static readonly Fast2DArray<float> ScharrX =
{ new float[,]
new float[] { -3, 0, 3 }, {
new float[] { -10, 0, 10 }, { -3, 0, 3 },
new float[] { -3, 0, 3 } { -10, 0, 10 },
}; { -3, 0, 3 }
};
/// <summary> /// <summary>
/// The vertical gradient operator. /// The vertical gradient operator.
/// </summary> /// </summary>
private static readonly float[][] ScharrY = new float[3][] private static readonly Fast2DArray<float> ScharrY =
{ new float[,]
new float[] { 3, 10, 3 }, {
new float[] { 0, 0, 0 }, { 3, 10, 3 },
new float[] { -3, -10, -3 } { 0, 0, 0 },
}; { -3, -10, -3 }
};
/// <inheritdoc/>
public override float[][] KernelX => ScharrX;
/// <inheritdoc/> /// <summary>
public override float[][] KernelY => ScharrY; /// Initializes a new instance of the <see cref="ScharrProcessor{TColor}"/> class.
/// </summary>
public ScharrProcessor()
: base(ScharrX, ScharrY)
{
}
} }
} }

38
src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/SobelProcessor.cs

@ -20,27 +20,31 @@ namespace ImageSharp.Processing.Processors
/// <summary> /// <summary>
/// The horizontal gradient operator. /// The horizontal gradient operator.
/// </summary> /// </summary>
private static readonly float[][] SobelX = private static readonly Fast2DArray<float> SobelX =
{ new float[,]
new float[] { -1, 0, 1 }, {
new float[] { -2, 0, 2 }, { -1, 0, 1 },
new float[] { -1, 0, 1 } { -2, 0, 2 },
}; { -1, 0, 1 }
};
/// <summary> /// <summary>
/// The vertical gradient operator. /// The vertical gradient operator.
/// </summary> /// </summary>
private static readonly float[][] SobelY = private static readonly Fast2DArray<float> SobelY =
{ new float[,]
new float[] { -1, -2, -1 }, {
new float[] { 0, 0, 0 }, { -1, -2, -1 },
new float[] { 1, 2, 1 } { 0, 0, 0 },
}; { 1, 2, 1 }
};
/// <inheritdoc/>
public override float[][] KernelX => SobelX;
/// <inheritdoc/> /// <summary>
public override float[][] KernelY => SobelY; /// Initializes a new instance of the <see cref="SobelProcessor{TColor}"/> class.
/// </summary>
public SobelProcessor()
: base(SobelX, SobelY)
{
}
} }
} }

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

@ -71,12 +71,12 @@ namespace ImageSharp.Processing.Processors
/// <summary> /// <summary>
/// Gets the horizontal gradient operator. /// Gets the horizontal gradient operator.
/// </summary> /// </summary>
public float[][] KernelX { get; } public Fast2DArray<float> KernelX { get; }
/// <summary> /// <summary>
/// Gets the vertical gradient operator. /// Gets the vertical gradient operator.
/// </summary> /// </summary>
public float[][] KernelY { get; } public Fast2DArray<float> KernelY { get; }
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnApply(ImageBase<TColor> source, Rectangle sourceRectangle) protected override void OnApply(ImageBase<TColor> source, Rectangle sourceRectangle)
@ -88,21 +88,18 @@ namespace ImageSharp.Processing.Processors
/// Create a 1 dimensional Gaussian kernel using the Gaussian G(x) function /// Create a 1 dimensional Gaussian kernel using the Gaussian G(x) function
/// </summary> /// </summary>
/// <param name="horizontal">Whether to calculate a horizontal kernel.</param> /// <param name="horizontal">Whether to calculate a horizontal kernel.</param>
/// <returns>The <see cref="T:float[][]"/></returns> /// <returns>The <see cref="Fast2DArray{T}"/></returns>
private float[][] CreateGaussianKernel(bool horizontal) private Fast2DArray<float> CreateGaussianKernel(bool horizontal)
{ {
int size = this.kernelSize; int size = this.kernelSize;
float weight = this.sigma; float weight = this.sigma;
float[][] kernel = horizontal ? new float[1][] : new float[size][]; Fast2DArray<float> kernel = horizontal
? new Fast2DArray<float>(size, 1)
: new Fast2DArray<float>(1, size);
if (horizontal) float sum = 0F;
{ float midpoint = (size - 1) / 2F;
kernel[0] = new float[size];
}
float sum = 0.0f;
float midpoint = (size - 1) / 2f;
for (int i = 0; i < size; i++) for (int i = 0; i < size; i++)
{ {
float x = i - midpoint; float x = i - midpoint;
@ -110,27 +107,27 @@ namespace ImageSharp.Processing.Processors
sum += gx; sum += gx;
if (horizontal) if (horizontal)
{ {
kernel[0][i] = gx; kernel[0, i] = gx;
} }
else else
{ {
kernel[i] = new[] { gx }; kernel[i, 0] = gx;
} }
} }
// Normalise kernel so that the sum of all weights equals 1 // Normalize kernel so that the sum of all weights equals 1
if (horizontal) if (horizontal)
{ {
for (int i = 0; i < size; i++) for (int i = 0; i < size; i++)
{ {
kernel[0][i] = kernel[0][i] / sum; kernel[0, i] = kernel[0, i] / sum;
} }
} }
else else
{ {
for (int i = 0; i < size; i++) for (int i = 0; i < size; i++)
{ {
kernel[i][0] = kernel[i][0] / sum; kernel[i, 0] = kernel[i, 0] / sum;
} }
} }

33
src/ImageSharp.Processing/Processors/Convolution/GaussianSharpenProcessor.cs

@ -73,12 +73,12 @@ namespace ImageSharp.Processing.Processors
/// <summary> /// <summary>
/// Gets the horizontal gradient operator. /// Gets the horizontal gradient operator.
/// </summary> /// </summary>
public float[][] KernelX { get; } public Fast2DArray<float> KernelX { get; }
/// <summary> /// <summary>
/// Gets the vertical gradient operator. /// Gets the vertical gradient operator.
/// </summary> /// </summary>
public float[][] KernelY { get; } public Fast2DArray<float> KernelY { get; }
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnApply(ImageBase<TColor> source, Rectangle sourceRectangle) protected override void OnApply(ImageBase<TColor> source, Rectangle sourceRectangle)
@ -90,17 +90,14 @@ namespace ImageSharp.Processing.Processors
/// Create a 1 dimensional Gaussian kernel using the Gaussian G(x) function /// Create a 1 dimensional Gaussian kernel using the Gaussian G(x) function
/// </summary> /// </summary>
/// <param name="horizontal">Whether to calculate a horizontal kernel.</param> /// <param name="horizontal">Whether to calculate a horizontal kernel.</param>
/// <returns>The <see cref="T:float[][]"/></returns> /// <returns>The <see cref="Fast2DArray{T}"/></returns>
private float[][] CreateGaussianKernel(bool horizontal) private Fast2DArray<float> CreateGaussianKernel(bool horizontal)
{ {
int size = this.kernelSize; int size = this.kernelSize;
float weight = this.sigma; float weight = this.sigma;
float[][] kernel = horizontal ? new float[1][] : new float[size][]; Fast2DArray<float> kernel = horizontal
? new Fast2DArray<float>(size, 1)
if (horizontal) : new Fast2DArray<float>(1, size);
{
kernel[0] = new float[size];
}
float sum = 0; float sum = 0;
@ -112,11 +109,11 @@ namespace ImageSharp.Processing.Processors
sum += gx; sum += gx;
if (horizontal) if (horizontal)
{ {
kernel[0][i] = gx; kernel[0, i] = gx;
} }
else else
{ {
kernel[i] = new[] { gx }; kernel[i, 0] = gx;
} }
} }
@ -130,12 +127,12 @@ namespace ImageSharp.Processing.Processors
if (i == midpointRounded) if (i == midpointRounded)
{ {
// Calculate central value // Calculate central value
kernel[0][i] = (2f * sum) - kernel[0][i]; kernel[0, i] = (2F * sum) - kernel[0, i];
} }
else else
{ {
// invert value // invert value
kernel[0][i] = -kernel[0][i]; kernel[0, i] = -kernel[0, i];
} }
} }
} }
@ -146,12 +143,12 @@ namespace ImageSharp.Processing.Processors
if (i == midpointRounded) if (i == midpointRounded)
{ {
// Calculate central value // Calculate central value
kernel[i][0] = (2 * sum) - kernel[i][0]; kernel[i, 0] = (2 * sum) - kernel[i, 0];
} }
else else
{ {
// invert value // invert value
kernel[i][0] = -kernel[i][0]; kernel[i, 0] = -kernel[i, 0];
} }
} }
} }
@ -161,14 +158,14 @@ namespace ImageSharp.Processing.Processors
{ {
for (int i = 0; i < size; i++) for (int i = 0; i < size; i++)
{ {
kernel[0][i] = kernel[0][i] / sum; kernel[0, i] = kernel[0, i] / sum;
} }
} }
else else
{ {
for (int i = 0; i < size; i++) for (int i = 0; i < size; i++)
{ {
kernel[i][0] = kernel[i][0] / sum; kernel[i, 0] = kernel[i, 0] / sum;
} }
} }

29
src/ImageSharp/Common/Helpers/Fast2DArray{T}.cs

@ -30,6 +30,22 @@ namespace ImageSharp
/// </summary> /// </summary>
public int Height; public int Height;
/// <summary>
/// Initializes a new instance of the <see cref="Fast2DArray{T}" /> struct.
/// </summary>
/// <param name="width">The width.</param>
/// <param name="height">The height.</param>
public Fast2DArray(int width, int height)
{
this.Height = height;
this.Width = width;
Guard.MustBeGreaterThan(width, 0, nameof(width));
Guard.MustBeGreaterThan(height, 0, nameof(height));
this.Data = new T[this.Width * this.Height];
}
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Fast2DArray{T}"/> struct. /// Initializes a new instance of the <see cref="Fast2DArray{T}"/> struct.
/// </summary> /// </summary>
@ -77,6 +93,19 @@ namespace ImageSharp
} }
} }
/// <summary>
/// Performs an implicit conversion from a 2D array to a <see cref="ImageSharp.Fast2DArray{T}" />.
/// </summary>
/// <param name="data">The source array.</param>
/// <returns>
/// The <see cref="ImageSharp.Fast2DArray{T}"/> represenation on the source data.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator Fast2DArray<T>(T[,] data)
{
return new Fast2DArray<T>(data);
}
/// <summary> /// <summary>
/// Checks the coordinates to ensure they are within bounds. /// Checks the coordinates to ensure they are within bounds.
/// </summary> /// </summary>

6
src/ImageSharp/Common/Helpers/ImageMaths.cs

@ -37,6 +37,7 @@ namespace ImageSharp
/// <returns> /// <returns>
/// The <see cref="int"/> /// The <see cref="int"/>
/// </returns> /// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int GetBitsNeededForColorDepth(int colors) public static int GetBitsNeededForColorDepth(int colors)
{ {
return (int)Math.Ceiling(Math.Log(colors, 2)); return (int)Math.Ceiling(Math.Log(colors, 2));
@ -48,6 +49,7 @@ namespace ImageSharp
/// <param name="x">The x provided to G(x).</param> /// <param name="x">The x provided to G(x).</param>
/// <param name="sigma">The spread of the blur.</param> /// <param name="sigma">The spread of the blur.</param>
/// <returns>The Gaussian G(x)</returns> /// <returns>The Gaussian G(x)</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float Gaussian(float x, float sigma) public static float Gaussian(float x, float sigma)
{ {
const float Numerator = 1.0f; const float Numerator = 1.0f;
@ -72,6 +74,7 @@ namespace ImageSharp
/// <returns> /// <returns>
/// The <see cref="float"/>. /// The <see cref="float"/>.
/// </returns> /// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float GetBcValue(float x, float b, float c) public static float GetBcValue(float x, float b, float c)
{ {
float temp; float temp;
@ -104,6 +107,7 @@ namespace ImageSharp
/// <returns> /// <returns>
/// The <see cref="float"/>. /// The <see cref="float"/>.
/// </returns> /// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float SinC(float x) public static float SinC(float x)
{ {
if (Math.Abs(x) > Constants.Epsilon) if (Math.Abs(x) > Constants.Epsilon)
@ -122,6 +126,7 @@ namespace ImageSharp
/// <returns> /// <returns>
/// The <see cref="float"/> representing the degree as radians. /// The <see cref="float"/> representing the degree as radians.
/// </returns> /// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float DegreesToRadians(float degrees) public static float DegreesToRadians(float degrees)
{ {
return degrees * (float)(Math.PI / 180); return degrees * (float)(Math.PI / 180);
@ -139,6 +144,7 @@ namespace ImageSharp
/// <returns> /// <returns>
/// The bounding <see cref="Rectangle"/>. /// The bounding <see cref="Rectangle"/>.
/// </returns> /// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Rectangle GetBoundingRectangle(Point topLeft, Point bottomRight) public static Rectangle GetBoundingRectangle(Point topLeft, Point bottomRight)
{ {
return new Rectangle(topLeft.X, topLeft.Y, bottomRight.X - topLeft.X, bottomRight.Y - topLeft.Y); return new Rectangle(topLeft.X, topLeft.Y, bottomRight.X - topLeft.X, bottomRight.Y - topLeft.Y);

4
src/ImageSharp/Dithering/ErrorDiffusion/Atkinson.cs

@ -15,12 +15,12 @@ namespace ImageSharp.Dithering
/// The diffusion matrix /// The diffusion matrix
/// </summary> /// </summary>
private static readonly Fast2DArray<float> AtkinsonMatrix = private static readonly Fast2DArray<float> AtkinsonMatrix =
new Fast2DArray<float>(new float[,] new float[,]
{ {
{ 0, 0, 1, 1 }, { 0, 0, 1, 1 },
{ 1, 1, 1, 0 }, { 1, 1, 1, 0 },
{ 0, 1, 0, 0 } { 0, 1, 0, 0 }
}); };
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Atkinson"/> class. /// Initializes a new instance of the <see cref="Atkinson"/> class.

4
src/ImageSharp/Dithering/ErrorDiffusion/Burks.cs

@ -15,11 +15,11 @@ namespace ImageSharp.Dithering
/// The diffusion matrix /// The diffusion matrix
/// </summary> /// </summary>
private static readonly Fast2DArray<float> BurksMatrix = private static readonly Fast2DArray<float> BurksMatrix =
new Fast2DArray<float>(new float[,] new float[,]
{ {
{ 0, 0, 0, 8, 4 }, { 0, 0, 0, 8, 4 },
{ 2, 4, 8, 4, 2 } { 2, 4, 8, 4, 2 }
}); };
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Burks"/> class. /// Initializes a new instance of the <see cref="Burks"/> class.

4
src/ImageSharp/Dithering/ErrorDiffusion/FloydSteinberg.cs

@ -15,11 +15,11 @@ namespace ImageSharp.Dithering
/// The diffusion matrix /// The diffusion matrix
/// </summary> /// </summary>
private static readonly Fast2DArray<float> FloydSteinbergMatrix = private static readonly Fast2DArray<float> FloydSteinbergMatrix =
new Fast2DArray<float>(new float[,] new float[,]
{ {
{ 0, 0, 7 }, { 0, 0, 7 },
{ 3, 5, 1 } { 3, 5, 1 }
}); };
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="FloydSteinberg"/> class. /// Initializes a new instance of the <see cref="FloydSteinberg"/> class.

4
src/ImageSharp/Dithering/ErrorDiffusion/JarvisJudiceNinke.cs

@ -15,12 +15,12 @@ namespace ImageSharp.Dithering
/// The diffusion matrix /// The diffusion matrix
/// </summary> /// </summary>
private static readonly Fast2DArray<float> JarvisJudiceNinkeMatrix = private static readonly Fast2DArray<float> JarvisJudiceNinkeMatrix =
new Fast2DArray<float>(new float[,] new float[,]
{ {
{ 0, 0, 0, 7, 5 }, { 0, 0, 0, 7, 5 },
{ 3, 5, 7, 5, 3 }, { 3, 5, 7, 5, 3 },
{ 1, 3, 5, 3, 1 } { 1, 3, 5, 3, 1 }
}); };
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="JarvisJudiceNinke"/> class. /// Initializes a new instance of the <see cref="JarvisJudiceNinke"/> class.

4
src/ImageSharp/Dithering/ErrorDiffusion/Sierra2.cs

@ -15,11 +15,11 @@ namespace ImageSharp.Dithering
/// The diffusion matrix /// The diffusion matrix
/// </summary> /// </summary>
private static readonly Fast2DArray<float> Sierra2Matrix = private static readonly Fast2DArray<float> Sierra2Matrix =
new Fast2DArray<float>(new float[,] new float[,]
{ {
{ 0, 0, 0, 4, 3 }, { 0, 0, 0, 4, 3 },
{ 1, 2, 3, 2, 1 } { 1, 2, 3, 2, 1 }
}); };
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Sierra2"/> class. /// Initializes a new instance of the <see cref="Sierra2"/> class.

4
src/ImageSharp/Dithering/ErrorDiffusion/Sierra3.cs

@ -15,12 +15,12 @@ namespace ImageSharp.Dithering
/// The diffusion matrix /// The diffusion matrix
/// </summary> /// </summary>
private static readonly Fast2DArray<float> Sierra3Matrix = private static readonly Fast2DArray<float> Sierra3Matrix =
new Fast2DArray<float>(new float[,] new float[,]
{ {
{ 0, 0, 0, 5, 3 }, { 0, 0, 0, 5, 3 },
{ 2, 4, 5, 4, 2 }, { 2, 4, 5, 4, 2 },
{ 0, 2, 3, 2, 0 } { 0, 2, 3, 2, 0 }
}); };
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Sierra3"/> class. /// Initializes a new instance of the <see cref="Sierra3"/> class.

4
src/ImageSharp/Dithering/ErrorDiffusion/SierraLite.cs

@ -15,11 +15,11 @@ namespace ImageSharp.Dithering
/// The diffusion matrix /// The diffusion matrix
/// </summary> /// </summary>
private static readonly Fast2DArray<float> SierraLiteMatrix = private static readonly Fast2DArray<float> SierraLiteMatrix =
new Fast2DArray<float>(new float[,] new float[,]
{ {
{ 0, 0, 2 }, { 0, 0, 2 },
{ 1, 1, 0 } { 1, 1, 0 }
}); };
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="SierraLite"/> class. /// Initializes a new instance of the <see cref="SierraLite"/> class.

4
src/ImageSharp/Dithering/ErrorDiffusion/Stucki.cs

@ -15,12 +15,12 @@ namespace ImageSharp.Dithering
/// The diffusion matrix /// The diffusion matrix
/// </summary> /// </summary>
private static readonly Fast2DArray<float> StuckiMatrix = private static readonly Fast2DArray<float> StuckiMatrix =
new Fast2DArray<float>(new float[,] new float[,]
{ {
{ 0, 0, 0, 8, 4 }, { 0, 0, 0, 8, 4 },
{ 2, 4, 8, 4, 2 }, { 2, 4, 8, 4, 2 },
{ 1, 2, 4, 2, 1 } { 1, 2, 4, 2, 1 }
}); };
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Stucki"/> class. /// Initializes a new instance of the <see cref="Stucki"/> class.

4
src/ImageSharp/Dithering/Ordered/Bayer.cs

@ -18,13 +18,13 @@ namespace ImageSharp.Dithering.Ordered
/// This is calculated by multiplying each value in the original matrix by 16 and subtracting 1 /// This is calculated by multiplying each value in the original matrix by 16 and subtracting 1
/// </summary> /// </summary>
private static readonly Fast2DArray<byte> ThresholdMatrix = private static readonly Fast2DArray<byte> ThresholdMatrix =
new Fast2DArray<byte>(new byte[,] new byte[,]
{ {
{ 15, 143, 47, 175 }, { 15, 143, 47, 175 },
{ 207, 79, 239, 111 }, { 207, 79, 239, 111 },
{ 63, 191, 31, 159 }, { 63, 191, 31, 159 },
{ 255, 127, 223, 95 } { 255, 127, 223, 95 }
}); };
/// <inheritdoc /> /// <inheritdoc />
public Fast2DArray<byte> Matrix { get; } = ThresholdMatrix; public Fast2DArray<byte> Matrix { get; } = ThresholdMatrix;

4
src/ImageSharp/Dithering/Ordered/Ordered.cs

@ -18,13 +18,13 @@ namespace ImageSharp.Dithering.Ordered
/// This is calculated by multiplying each value in the original matrix by 16 /// This is calculated by multiplying each value in the original matrix by 16
/// </summary> /// </summary>
private static readonly Fast2DArray<byte> ThresholdMatrix = private static readonly Fast2DArray<byte> ThresholdMatrix =
new Fast2DArray<byte>(new byte[,] new byte[,]
{ {
{ 0, 128, 32, 160 }, { 0, 128, 32, 160 },
{ 192, 64, 224, 96 }, { 192, 64, 224, 96 },
{ 48, 176, 16, 144 }, { 48, 176, 16, 144 },
{ 240, 112, 208, 80 } { 240, 112, 208, 80 }
}); };
/// <inheritdoc /> /// <inheritdoc />
public Fast2DArray<byte> Matrix { get; } = ThresholdMatrix; public Fast2DArray<byte> Matrix { get; } = ThresholdMatrix;

73
tests/ImageSharp.Benchmarks/General/Array2D.cs

@ -5,25 +5,31 @@
namespace ImageSharp.Benchmarks.General namespace ImageSharp.Benchmarks.General
{ {
using System;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
public class Array2D public class Array2D
{ {
private float[,] data; private float[] flatArray;
private float[,] array2D;
private float[][] jaggedData; private float[][] jaggedData;
private Fast2DArray<float> fastData; private Fast2DArray<float> fastData;
[Params(10, 100, 1000, 10000)] [Params(4, 16, 128)]
public int Count { get; set; } public int Count { get; set; }
public int Index { get; set; } public int Min { get; private set; }
public int Max { get; private set; }
[Setup] [Setup]
public void SetUp() public void SetUp()
{ {
this.data = new float[this.Count, this.Count]; this.flatArray = new float[this.Count * this.Count];
this.array2D = new float[this.Count, this.Count];
this.jaggedData = new float[this.Count][]; this.jaggedData = new float[this.Count][];
for (int i = 0; i < this.Count; i++) for (int i = 0; i < this.Count; i++)
@ -31,27 +37,72 @@ namespace ImageSharp.Benchmarks.General
this.jaggedData[i] = new float[this.Count]; this.jaggedData[i] = new float[this.Count];
} }
this.fastData = new Fast2DArray<float>(this.data); this.fastData = new Fast2DArray<float>(this.array2D);
this.Index = this.Count / 2; this.Min = this.Count / 2 - 10;
this.Min = Math.Max(0, this.Min);
this.Max = this.Min + Math.Min(10, this.Count);
}
[Benchmark(Description = "Emulated 2D array access using flat array")]
public float FlatArrayIndex()
{
float[] a = this.flatArray;
float s = 0;
int count = this.Count;
for (int i = this.Min; i < this.Max; i++)
{
for (int j = this.Min; j < this.Max; j++)
{
s += a[count * i + j];
}
}
return s;
} }
[Benchmark(Baseline = true, Description = "Array access using 2D array")] [Benchmark(Baseline = true, Description = "Array access using 2D array")]
public float ArrayIndex() public float Array2DIndex()
{ {
return this.data[this.Index, this.Index]; float s = 0;
float[,] a = this.array2D;
for (int i = this.Min; i < this.Max; i++)
{
for (int j = this.Min; j < this.Max; j++)
{
s += a[i, j];
}
}
return s;
} }
[Benchmark(Description = "Array access using a jagged array")] [Benchmark(Description = "Array access using a jagged array")]
public float ArrayJaggedIndex() public float ArrayJaggedIndex()
{ {
return this.jaggedData[this.Index][this.Index]; float s = 0;
float[][] a = this.jaggedData;
for (int i = this.Min; i < this.Max; i++)
{
for (int j = this.Min; j < this.Max; j++)
{
s += a[i][j];
}
}
return s;
} }
[Benchmark(Description = "Array access using Fast2DArray")] [Benchmark(Description = "Array access using Fast2DArray")]
public float ArrayFastIndex() public float ArrayFastIndex()
{ {
return this.fastData[this.Index, this.Index]; float s = 0;
Fast2DArray<float> a = this.fastData;
for (int i = this.Min; i < this.Max; i++)
{
for (int j = this.Min; j < this.Max; j++)
{
s += a[i, j];
}
}
return s;
} }
} }
} }

28
tests/ImageSharp.Tests/Common/Fast2DArrayTests.cs

@ -21,9 +21,27 @@ namespace ImageSharp.Tests.Common
public void Fast2DArrayThrowsOnNullInitializer() public void Fast2DArrayThrowsOnNullInitializer()
{ {
Assert.Throws<ArgumentNullException>(() => Assert.Throws<ArgumentNullException>(() =>
{ {
Fast2DArray<float> fast = new Fast2DArray<float>(null); Fast2DArray<float> fast = new Fast2DArray<float>(null);
}); });
}
[Fact]
public void Fast2DArrayThrowsOnEmptyZeroWidth()
{
Assert.Throws<ArgumentOutOfRangeException>(() =>
{
Fast2DArray<float> fast = new Fast2DArray<float>(0, 10);
});
}
[Fact]
public void Fast2DArrayThrowsOnEmptyZeroHeight()
{
Assert.Throws<ArgumentOutOfRangeException>(() =>
{
Fast2DArray<float> fast = new Fast2DArray<float>(10, 0);
});
} }
[Fact] [Fact]
@ -46,7 +64,7 @@ namespace ImageSharp.Tests.Common
[Fact] [Fact]
public void Fast2DArrayGetReturnsCorrectResults() public void Fast2DArrayGetReturnsCorrectResults()
{ {
Fast2DArray<float> fast = new Fast2DArray<float>(FloydSteinbergMatrix); Fast2DArray<float> fast = FloydSteinbergMatrix;
for (int row = 0; row < fast.Height; row++) for (int row = 0; row < fast.Height; row++)
{ {
@ -60,7 +78,7 @@ namespace ImageSharp.Tests.Common
[Fact] [Fact]
public void Fast2DArrayGetSetReturnsCorrectResults() public void Fast2DArrayGetSetReturnsCorrectResults()
{ {
Fast2DArray<float> fast = new Fast2DArray<float>(new float[4, 4]); Fast2DArray<float> fast = new Fast2DArray<float>(4, 4);
const float Val = 5F; const float Val = 5F;
fast[3, 3] = Val; fast[3, 3] = Val;

Loading…
Cancel
Save