diff --git a/src/ImageProcessorCore/Colors/RgbaComponent.cs b/src/ImageProcessorCore/Colors/RgbaComponent.cs
new file mode 100644
index 000000000..946c47a37
--- /dev/null
+++ b/src/ImageProcessorCore/Colors/RgbaComponent.cs
@@ -0,0 +1,33 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageProcessorCore
+{
+ ///
+ /// Enumerates the RGBA (red, green, blue, alpha) color components.
+ ///
+ public enum RgbaComponent
+ {
+ ///
+ /// The red component.
+ ///
+ R = 0,
+
+ ///
+ /// The green component.
+ ///
+ G = 1,
+
+ ///
+ /// The blue component.
+ ///
+ B = 2,
+
+ ///
+ /// The alpha component.
+ ///
+ A = 3
+ }
+}
diff --git a/src/ImageProcessorCore/Common/Helpers/ImageMaths.cs b/src/ImageProcessorCore/Common/Helpers/ImageMaths.cs
index 6ec45549b..9b890d1c0 100644
--- a/src/ImageProcessorCore/Common/Helpers/ImageMaths.cs
+++ b/src/ImageProcessorCore/Common/Helpers/ImageMaths.cs
@@ -156,116 +156,120 @@ namespace ImageProcessorCore
/// Finds the bounding rectangle based on the first instance of any color component other
/// than the given one.
///
+ /// The pixel format.
+ /// The packed format. long, float.
/// The to search within.
/// The color component value to remove.
/// The channel to test against.
///
/// The .
///
- //public static Rectangle GetFilteredBoundingRectangle(ImageBase bitmap, float componentValue, RgbaComponent channel = RgbaComponent.B)
- //{
- // const float Epsilon = .00001f;
- // int width = bitmap.Width;
- // int height = bitmap.Height;
- // Point topLeft = new Point();
- // Point bottomRight = new Point();
-
- // Func delegateFunc;
-
- // // Determine which channel to check against
- // switch (channel)
- // {
- // case RgbaComponent.R:
- // delegateFunc = (pixels, x, y, b) => Math.Abs(pixels[x, y].R - b) > Epsilon;
- // break;
-
- // case RgbaComponent.G:
- // delegateFunc = (pixels, x, y, b) => Math.Abs(pixels[x, y].G - b) > Epsilon;
- // break;
-
- // case RgbaComponent.A:
- // delegateFunc = (pixels, x, y, b) => Math.Abs(pixels[x, y].A - b) > Epsilon;
- // break;
-
- // default:
- // delegateFunc = (pixels, x, y, b) => Math.Abs(pixels[x, y].B - b) > Epsilon;
- // break;
- // }
-
- // Func getMinY = pixels =>
- // {
- // for (int y = 0; y < height; y++)
- // {
- // for (int x = 0; x < width; x++)
- // {
- // if (delegateFunc(pixels, x, y, componentValue))
- // {
- // return y;
- // }
- // }
- // }
-
- // return 0;
- // };
-
- // Func getMaxY = pixels =>
- // {
- // for (int y = height - 1; y > -1; y--)
- // {
- // for (int x = 0; x < width; x++)
- // {
- // if (delegateFunc(pixels, x, y, componentValue))
- // {
- // return y;
- // }
- // }
- // }
-
- // return height;
- // };
-
- // Func getMinX = pixels =>
- // {
- // for (int x = 0; x < width; x++)
- // {
- // for (int y = 0; y < height; y++)
- // {
- // if (delegateFunc(pixels, x, y, componentValue))
- // {
- // return x;
- // }
- // }
- // }
-
- // return 0;
- // };
-
- // Func getMaxX = pixels =>
- // {
- // for (int x = width - 1; x > -1; x--)
- // {
- // for (int y = 0; y < height; y++)
- // {
- // if (delegateFunc(pixels, x, y, componentValue))
- // {
- // return x;
- // }
- // }
- // }
-
- // return height;
- // };
-
- // using (PixelAccessor bitmapPixels = bitmap.Lock())
- // {
- // topLeft.Y = getMinY(bitmapPixels);
- // topLeft.X = getMinX(bitmapPixels);
- // bottomRight.Y = (getMaxY(bitmapPixels) + 1).Clamp(0, height);
- // bottomRight.X = (getMaxX(bitmapPixels) + 1).Clamp(0, width);
- // }
-
- // return GetBoundingRectangle(topLeft, bottomRight);
- //}
+ public static Rectangle GetFilteredBoundingRectangle(ImageBase bitmap, float componentValue, RgbaComponent channel = RgbaComponent.B)
+ where T : IPackedVector
+ where TP : struct
+ {
+ const float Epsilon = .00001f;
+ int width = bitmap.Width;
+ int height = bitmap.Height;
+ Point topLeft = new Point();
+ Point bottomRight = new Point();
+
+ Func, int, int, float, bool> delegateFunc;
+
+ // Determine which channel to check against
+ switch (channel)
+ {
+ case RgbaComponent.R:
+ delegateFunc = (pixels, x, y, b) => Math.Abs(pixels[x, y].ToBytes()[0] - b) > Epsilon;
+ break;
+
+ case RgbaComponent.G:
+ delegateFunc = (pixels, x, y, b) => Math.Abs(pixels[x, y].ToBytes()[1] - b) > Epsilon;
+ break;
+
+ case RgbaComponent.B:
+ delegateFunc = (pixels, x, y, b) => Math.Abs(pixels[x, y].ToBytes()[2] - b) > Epsilon;
+ break;
+
+ default:
+ delegateFunc = (pixels, x, y, b) => Math.Abs(pixels[x, y].ToBytes()[3] - b) > Epsilon;
+ break;
+ }
+
+ Func, int> getMinY = pixels =>
+ {
+ for (int y = 0; y < height; y++)
+ {
+ for (int x = 0; x < width; x++)
+ {
+ if (delegateFunc(pixels, x, y, componentValue))
+ {
+ return y;
+ }
+ }
+ }
+
+ return 0;
+ };
+
+ Func, int> getMaxY = pixels =>
+ {
+ for (int y = height - 1; y > -1; y--)
+ {
+ for (int x = 0; x < width; x++)
+ {
+ if (delegateFunc(pixels, x, y, componentValue))
+ {
+ return y;
+ }
+ }
+ }
+
+ return height;
+ };
+
+ Func, int> getMinX = pixels =>
+ {
+ for (int x = 0; x < width; x++)
+ {
+ for (int y = 0; y < height; y++)
+ {
+ if (delegateFunc(pixels, x, y, componentValue))
+ {
+ return x;
+ }
+ }
+ }
+
+ return 0;
+ };
+
+ Func, int> getMaxX = pixels =>
+ {
+ for (int x = width - 1; x > -1; x--)
+ {
+ for (int y = 0; y < height; y++)
+ {
+ if (delegateFunc(pixels, x, y, componentValue))
+ {
+ return x;
+ }
+ }
+ }
+
+ return height;
+ };
+
+ using (IPixelAccessor bitmapPixels = bitmap.Lock())
+ {
+ topLeft.Y = getMinY(bitmapPixels);
+ topLeft.X = getMinX(bitmapPixels);
+ bottomRight.Y = (getMaxY(bitmapPixels) + 1).Clamp(0, height);
+ bottomRight.X = (getMaxX(bitmapPixels) + 1).Clamp(0, width);
+ }
+
+ return GetBoundingRectangle(topLeft, bottomRight);
+ }
///
/// Ensures that any passed double is correctly rounded to zero
diff --git a/src/ImageProcessorCore/Filters/Processors/Binarization/ThresholdProcessor.cs b/src/ImageProcessorCore/Filters/Processors/Binarization/ThresholdProcessor.cs
new file mode 100644
index 000000000..856ae6d61
--- /dev/null
+++ b/src/ImageProcessorCore/Filters/Processors/Binarization/ThresholdProcessor.cs
@@ -0,0 +1,90 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageProcessorCore.Processors
+{
+ using System.Threading.Tasks;
+
+ ///
+ /// An to perform binary threshold filtering against an
+ /// . The image will be converted to greyscale before thresholding
+ /// occurs.
+ ///
+ public class ThresholdProcessor : ImageProcessor
+ where T : IPackedVector
+ where TP : struct
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The threshold to split the image. Must be between 0 and 1.
+ ///
+ /// is less than 0 or is greater than 1.
+ ///
+ public ThresholdProcessor(float threshold)
+ {
+ // TODO: Check limit.
+ Guard.MustBeBetweenOrEqualTo(threshold, 0, 1, nameof(threshold));
+ this.Value = threshold;
+ this.UpperColor.PackVector(Color.White.ToVector4());
+ this.LowerColor.PackVector(Color.Black.ToVector4());
+ }
+
+ ///
+ /// Gets the threshold value.
+ ///
+ public float Value { get; }
+
+ ///
+ /// The color to use for pixels that are above the threshold.
+ ///
+ public T UpperColor { get; set; }
+
+ ///
+ /// The color to use for pixels that fall below the threshold.
+ ///
+ public T LowerColor { get; set; }
+
+ ///
+ protected override void OnApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle)
+ {
+ new GreyscaleBt709Processor().Apply(source, source, sourceRectangle);
+ }
+
+ ///
+ protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY)
+ {
+ float threshold = this.Value;
+ T upper = this.UpperColor;
+ T lower = this.LowerColor;
+ int sourceY = sourceRectangle.Y;
+ int sourceBottom = sourceRectangle.Bottom;
+ int startX = sourceRectangle.X;
+ int endX = sourceRectangle.Right;
+
+ using (IPixelAccessor sourcePixels = source.Lock())
+ using (IPixelAccessor targetPixels = target.Lock())
+ {
+ Parallel.For(
+ startY,
+ endY,
+ y =>
+ {
+ if (y >= sourceY && y < sourceBottom)
+ {
+ for (int x = startX; x < endX; x++)
+ {
+ T color = sourcePixels[x, y];
+
+ // Any channel will do since it's greyscale.
+ targetPixels[x, y] = color.ToVector4().X >= threshold ? upper : lower;
+ }
+ this.OnRowProcessed();
+ }
+ });
+ }
+ }
+ }
+}
diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorMatrixFilter.cs b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorMatrixFilter.cs
new file mode 100644
index 000000000..0b7600fbc
--- /dev/null
+++ b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorMatrixFilter.cs
@@ -0,0 +1,74 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageProcessorCore.Processors
+{
+ using System.Numerics;
+ using System.Threading.Tasks;
+
+ ///
+ /// The color matrix filter.
+ ///
+ public abstract class ColorMatrixFilter : ImageProcessor, IColorMatrixFilter
+ where T : IPackedVector
+ where TP : struct
+ {
+ ///
+ public abstract Matrix4x4 Matrix { get; }
+
+ ///
+ public virtual bool Compand => true;
+
+ ///
+ protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY)
+ {
+ int startX = sourceRectangle.X;
+ int endX = sourceRectangle.Right;
+ Matrix4x4 matrix = this.Matrix;
+
+ using (IPixelAccessor sourcePixels = source.Lock())
+ using (IPixelAccessor targetPixels = target.Lock())
+ {
+ Parallel.For(
+ startY,
+ endY,
+ y =>
+ {
+ for (int x = startX; x < endX; x++)
+ {
+ targetPixels[x, y] = this.ApplyMatrix(sourcePixels[x, y], matrix);
+ }
+
+ this.OnRowProcessed();
+ });
+ }
+ }
+
+ ///
+ /// Applies the color matrix against the given color.
+ ///
+ /// The source color.
+ /// The matrix.
+ ///
+ /// The .
+ ///
+ private T ApplyMatrix(T color, Matrix4x4 matrix)
+ {
+ bool compand = this.Compand;
+
+ //if (compand)
+ //{
+ // color = Color.Expand(color);
+ //}
+
+ Vector4 transformed = Vector4.Transform(color.ToVector4(), matrix);
+ //Vector3 transformed = Vector3.Transform(color.ToVector3(), matrix);
+ //return compand ? Color.Compress(new Color(transformed, color.A)) : new Color(transformed, color.A);
+ T packed = default(T);
+ packed.PackVector(transformed);
+ return packed;
+ }
+ }
+}
diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/GreyscaleBt709Processor.cs b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/GreyscaleBt709Processor.cs
new file mode 100644
index 000000000..f8741b00b
--- /dev/null
+++ b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/GreyscaleBt709Processor.cs
@@ -0,0 +1,32 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageProcessorCore.Processors
+{
+ using System.Numerics;
+
+ ///
+ /// Converts the colors of the image to greyscale applying the formula as specified by
+ /// ITU-R Recommendation BT.709 .
+ ///
+ public class GreyscaleBt709Processor : ColorMatrixFilter
+ where T : IPackedVector
+ where TP : struct
+ {
+ ///
+ public override Matrix4x4 Matrix => new Matrix4x4()
+ {
+ M11 = .2126f,
+ M12 = .2126f,
+ M13 = .2126f,
+ M21 = .7152f,
+ M22 = .7152f,
+ M23 = .7152f,
+ M31 = .0722f,
+ M32 = .0722f,
+ M33 = .0722f
+ };
+ }
+}
diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/IColorMatrixFilter.cs b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/IColorMatrixFilter.cs
new file mode 100644
index 000000000..8e46f56b6
--- /dev/null
+++ b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/IColorMatrixFilter.cs
@@ -0,0 +1,29 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageProcessorCore.Processors
+{
+ using System.Numerics;
+
+ ///
+ /// Encapsulates properties and methods for creating processors that utilize a matrix to
+ /// alter the image pixels.
+ ///
+ public interface IColorMatrixFilter : IImageProcessor
+ where T : IPackedVector
+ where TP : struct
+ {
+ ///
+ /// Gets the used to alter the image.
+ ///
+ Matrix4x4 Matrix { get; }
+
+ ///
+ /// Gets a value indicating whether to compress
+ /// or expand individual pixel colors the value on processing.
+ ///
+ bool Compand { get; }
+ }
+}
diff --git a/src/ImageProcessorCore/Filters/Processors/Convolution/Convolution2DFilter.cs b/src/ImageProcessorCore/Filters/Processors/Convolution/Convolution2DFilter.cs
new file mode 100644
index 000000000..cc8ac82e3
--- /dev/null
+++ b/src/ImageProcessorCore/Filters/Processors/Convolution/Convolution2DFilter.cs
@@ -0,0 +1,118 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageProcessorCore.Processors
+{
+ using System;
+ using System.Numerics;
+ using System.Threading.Tasks;
+
+ ///
+ /// Defines a filter that uses two one-dimensional matrices to perform convolution against an image.
+ ///
+ public abstract class Convolution2DFilter : ImageProcessor
+ where T : IPackedVector
+ where TP : struct
+ {
+ ///
+ /// 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 kernelYHeight = kernelY.GetLength(0);
+ int kernelYWidth = kernelY.GetLength(1);
+ int kernelXHeight = kernelX.GetLength(0);
+ int kernelXWidth = kernelX.GetLength(1);
+ int radiusY = kernelYHeight >> 1;
+ int radiusX = kernelXWidth >> 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;
+
+ using (IPixelAccessor sourcePixels = source.Lock())
+ using (IPixelAccessor targetPixels = target.Lock())
+ {
+ 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 < kernelYHeight; fy++)
+ {
+ int fyr = fy - radiusY;
+ int offsetY = y + fyr;
+
+ offsetY = offsetY.Clamp(0, maxY);
+
+ for (int fx = 0; fx < kernelXWidth; fx++)
+ {
+ int fxr = fx - radiusX;
+ int offsetX = x + fxr;
+
+ offsetX = offsetX.Clamp(0, maxX);
+
+ Vector4 currentColor = sourcePixels[offsetX, offsetY].ToVector4();
+ float r = currentColor.X;
+ float g = currentColor.Y;
+ float b = currentColor.Z;
+
+ if (fy < kernelXHeight)
+ {
+ rX += kernelX[fy, fx] * r;
+ gX += kernelX[fy, fx] * g;
+ bX += kernelX[fy, fx] * b;
+ }
+
+ if (fx < kernelYWidth)
+ {
+ 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));
+
+ Vector4 targetColor = targetPixels[x, y].ToVector4();
+ T packed = default(T);
+ packed.PackVector(new Vector4(red, green, blue, targetColor.Z));
+ targetPixels[x, y] = packed;
+ }
+ this.OnRowProcessed();
+ }
+ });
+ }
+ }
+ }
+}
diff --git a/src/ImageProcessorCore/Filters/Processors/Convolution/ConvolutionFilter.cs b/src/ImageProcessorCore/Filters/Processors/Convolution/ConvolutionFilter.cs
new file mode 100644
index 000000000..ced19abab
--- /dev/null
+++ b/src/ImageProcessorCore/Filters/Processors/Convolution/ConvolutionFilter.cs
@@ -0,0 +1,95 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageProcessorCore.Processors
+{
+ using System.Numerics;
+ using System.Threading.Tasks;
+
+ ///
+ /// Defines a filter that uses a 2 dimensional matrix to perform convolution against an image.
+ ///
+ public abstract class ConvolutionFilter : ImageProcessor
+ where T : IPackedVector
+ where TP : struct
+ {
+ ///
+ /// Gets the 2d gradient operator.
+ ///
+ public abstract float[,] KernelXY { get; }
+
+ ///
+ protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY)
+ {
+ float[,] kernelX = this.KernelXY;
+ 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;
+
+ using (IPixelAccessor sourcePixels = source.Lock())
+ using (IPixelAccessor targetPixels = target.Lock())
+ {
+ 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);
+
+ Vector4 currentColor = sourcePixels[offsetX, offsetY].ToVector4();
+ float r = currentColor.X;
+ float g = currentColor.Y;
+ float b = currentColor.Z;
+
+ rX += kernelX[fy, fx] * r;
+ gX += kernelX[fy, fx] * g;
+ bX += kernelX[fy, fx] * b;
+ }
+ }
+
+ float red = rX;
+ float green = gX;
+ float blue = bX;
+
+ Vector4 targetColor = targetPixels[x, y].ToVector4();
+ T packed = default(T);
+ packed.PackVector(new Vector4(red, green, blue, targetColor.Z));
+ targetPixels[x, y] = packed;
+
+ }
+ this.OnRowProcessed();
+ }
+ });
+ }
+ }
+ }
+}
diff --git a/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/EdgeDetector2DFilter.cs b/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/EdgeDetector2DFilter.cs
new file mode 100644
index 000000000..4340f4d60
--- /dev/null
+++ b/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/EdgeDetector2DFilter.cs
@@ -0,0 +1,28 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageProcessorCore.Processors
+{
+ ///
+ /// Defines a filter that detects edges within an image using two
+ /// one-dimensional matrices.
+ ///
+ public abstract class EdgeDetector2DFilter : Convolution2DFilter, IEdgeDetectorFilter
+ where T : IPackedVector
+ where TP : struct
+ {
+ ///
+ public bool Greyscale { get; set; }
+
+ ///
+ protected override void OnApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle)
+ {
+ if (this.Greyscale)
+ {
+ new GreyscaleBt709Processor().Apply(source, source, sourceRectangle);
+ }
+ }
+ }
+}
diff --git a/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/EdgeDetectorFilter.cs b/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/EdgeDetectorFilter.cs
new file mode 100644
index 000000000..2762f664f
--- /dev/null
+++ b/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/EdgeDetectorFilter.cs
@@ -0,0 +1,28 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageProcessorCore.Processors
+{
+ ///
+ /// Defines a filter that detects edges within an image using a single
+ /// two dimensional matrix.
+ ///
+ public abstract class EdgeDetectorFilter : ConvolutionFilter, IEdgeDetectorFilter
+ where T : IPackedVector
+ where TP : struct
+ {
+ ///
+ public bool Greyscale { get; set; }
+
+ ///
+ protected override void OnApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle)
+ {
+ if (this.Greyscale)
+ {
+ new GreyscaleBt709Processor().Apply(source, source, sourceRectangle);
+ }
+ }
+ }
+}
diff --git a/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/IEdgeDetectorFilter.cs b/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/IEdgeDetectorFilter.cs
new file mode 100644
index 000000000..d2e2979f9
--- /dev/null
+++ b/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/IEdgeDetectorFilter.cs
@@ -0,0 +1,21 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageProcessorCore.Processors
+{
+ ///
+ /// Provides properties and methods allowing the detection of edges within an image.
+ ///
+ public interface IEdgeDetectorFilter : IImageProcessor
+ where T : IPackedVector
+ where TP : struct
+ {
+ ///
+ /// Gets or sets a value indicating whether to convert the
+ /// image to greyscale before performing edge detection.
+ ///
+ bool Greyscale { get; set; }
+ }
+}
diff --git a/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/SobelProcessor.cs b/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/SobelProcessor.cs
new file mode 100644
index 000000000..a323b7cfe
--- /dev/null
+++ b/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/SobelProcessor.cs
@@ -0,0 +1,32 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageProcessorCore.Processors
+{
+ ///
+ /// The Sobel operator filter.
+ ///
+ ///
+ public class SobelProcessor : EdgeDetector2DFilter
+ where T : IPackedVector
+ where TP : struct
+ {
+ ///
+ public override float[,] KernelX => new float[,]
+ {
+ { -1, 0, 1 },
+ { -2, 0, 2 },
+ { -1, 0, 1 }
+ };
+
+ ///
+ public override float[,] KernelY => new float[,]
+ {
+ { 1, 2, 1 },
+ { 0, 0, 0 },
+ { -1, -2, -1 }
+ };
+ }
+}
diff --git a/src/ImageProcessorCore/Image/IImageProcessor.cs b/src/ImageProcessorCore/Image/IImageProcessor.cs
index 0164077c5..cba045963 100644
--- a/src/ImageProcessorCore/Image/IImageProcessor.cs
+++ b/src/ImageProcessorCore/Image/IImageProcessor.cs
@@ -17,7 +17,11 @@ namespace ImageProcessorCore.Processors
///
/// Encapsulates methods to alter the pixels of an image.
///
- public interface IImageProcessor
+ /// The pixel format.
+ /// The packed format. long, float.
+ public interface IImageProcessor
+ where T : IPackedVector
+ where TP : struct
{
///
/// Event fires when each row of the source image has been processed.
@@ -36,8 +40,6 @@ namespace ImageProcessorCore.Processors
///
/// Applies the process to the specified portion of the specified .
///
- /// The pixel format.
- /// The packed format. long, float.
/// Target image to apply the process to.
/// The source image. Cannot be null.
///
@@ -53,16 +55,12 @@ namespace ImageProcessorCore.Processors
///
/// doesnt fit the dimension of the image.
///
- void Apply(ImageBase target, ImageBase source, Rectangle sourceRectangle)
- where T : IPackedVector
- where TP : struct;
+ void Apply(ImageBase target, ImageBase source, Rectangle sourceRectangle);
///
/// Applies the process to the specified portion of the specified at the specified
/// location and with the specified size.
///
- /// The pixel format.
- /// The packed format. long, float.
/// Target image to apply the process to.
/// The source image. Cannot be null.
/// The target width.
@@ -78,8 +76,6 @@ namespace ImageProcessorCore.Processors
/// The method keeps the source image unchanged and returns the
/// the result of image process as new image.
///
- void Apply(ImageBase target, ImageBase source, int width, int height, Rectangle targetRectangle, Rectangle sourceRectangle)
- where T : IPackedVector
- where TP : struct;
+ void Apply(ImageBase target, ImageBase source, int width, int height, Rectangle targetRectangle, Rectangle sourceRectangle);
}
}
diff --git a/src/ImageProcessorCore/Image/Image.cs b/src/ImageProcessorCore/Image/Image.cs
index 748fada55..0b716f0dd 100644
--- a/src/ImageProcessorCore/Image/Image.cs
+++ b/src/ImageProcessorCore/Image/Image.cs
@@ -40,7 +40,7 @@ namespace ImageProcessorCore
///
public Image()
{
- this.CurrentImageFormat = Bootstrapper.Instance.ImageFormats.First(f => f.GetType() == typeof(BmpFormat));
+ this.CurrentImageFormat = Bootstrapper.Instance.ImageFormats.First(f => f.GetType() == typeof(PngFormat));
}
///
@@ -52,8 +52,7 @@ namespace ImageProcessorCore
public Image(int width, int height)
: base(width, height)
{
- // TODO: Change to PNG
- this.CurrentImageFormat = Bootstrapper.Instance.ImageFormats.First(f => f.GetType() == typeof(BmpFormat));
+ this.CurrentImageFormat = Bootstrapper.Instance.ImageFormats.First(f => f.GetType() == typeof(PngFormat));
}
///
diff --git a/src/ImageProcessorCore/Image/ImageExtensions.cs b/src/ImageProcessorCore/Image/ImageExtensions.cs
index 28ba60f08..c97b6dfa6 100644
--- a/src/ImageProcessorCore/Image/ImageExtensions.cs
+++ b/src/ImageProcessorCore/Image/ImageExtensions.cs
@@ -69,7 +69,7 @@ namespace ImageProcessorCore
/// The stream to save the image to.
/// The quality to save the image to representing the number of colors. Between 1 and 256.
/// Thrown if the stream is null.
- public static void SaveAsGif(this ImageBase source, Stream stream, int quality = 256)
+ internal static void SaveAsGif(this ImageBase source, Stream stream, int quality = 256)
where T : IPackedVector
where TP : struct
=> new GifEncoder { Quality = quality }.Encode(source, stream);
@@ -83,7 +83,7 @@ namespace ImageProcessorCore
/// The image this method extends.
/// The processor to apply to the image.
/// The .
- public static Image Process(this Image source, IImageProcessor processor)
+ internal static Image Process(this Image source, IImageProcessor processor)
where T : IPackedVector
where TP : struct
{
@@ -102,7 +102,7 @@ namespace ImageProcessorCore
///
/// The processors to apply to the image.
/// The .
- public static Image Process(this Image source, Rectangle sourceRectangle, IImageProcessor processor)
+ internal static Image Process(this Image source, Rectangle sourceRectangle, IImageProcessor processor)
where T : IPackedVector
where TP : struct
{
@@ -122,7 +122,7 @@ namespace ImageProcessorCore
/// The target image height.
/// The processor to apply to the image.
/// The .
- public static Image Process(this Image source, int width, int height, IImageSampler sampler)
+ internal static Image Process(this Image source, int width, int height, IImageSampler sampler)
where T : IPackedVector
where TP : struct
{
@@ -149,7 +149,7 @@ namespace ImageProcessorCore
///
/// The processor to apply to the image.
/// The .
- public static Image Process(this Image source, int width, int height, Rectangle sourceRectangle, Rectangle targetRectangle, IImageSampler sampler)
+ internal static Image Process(this Image source, int width, int height, Rectangle sourceRectangle, Rectangle targetRectangle, IImageSampler sampler)
where T : IPackedVector
where TP : struct
{
diff --git a/src/ImageProcessorCore/ImageProcessor.cs b/src/ImageProcessorCore/ImageProcessor.cs
index ae0b23987..09cc54ff5 100644
--- a/src/ImageProcessorCore/ImageProcessor.cs
+++ b/src/ImageProcessorCore/ImageProcessor.cs
@@ -12,7 +12,9 @@ namespace ImageProcessorCore.Processors
///
/// Allows the application of processors to images.
///
- public abstract class ImageProcessor : IImageProcessor
+ public abstract class ImageProcessor : IImageProcessor
+ where T : IPackedVector
+ where TP : struct
{
///
public event ProgressEventHandler OnProgress;
@@ -31,9 +33,7 @@ namespace ImageProcessorCore.Processors
public virtual ParallelOptions ParallelOptions { get; set; } = Bootstrapper.Instance.ParallelOptions;
///
- public void Apply(ImageBase target, ImageBase source, Rectangle sourceRectangle)
- where T : IPackedVector
- where TP : struct
+ public void Apply(ImageBase target, ImageBase source, Rectangle sourceRectangle)
{
try
{
@@ -54,9 +54,7 @@ namespace ImageProcessorCore.Processors
}
///
- public void Apply(ImageBase target, ImageBase source, int width, int height, Rectangle targetRectangle = default(Rectangle), Rectangle sourceRectangle = default(Rectangle))
- where T : IPackedVector
- where TP : struct
+ public void Apply(ImageBase target, ImageBase source, int width, int height, Rectangle targetRectangle = default(Rectangle), Rectangle sourceRectangle = default(Rectangle))
{
try
{
@@ -103,9 +101,7 @@ namespace ImageProcessorCore.Processors
///
/// The structure that specifies the portion of the image object to draw.
///
- protected virtual void OnApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle)
- where T : IPackedVector
- where TP : struct
+ protected virtual void OnApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle)
{
}
@@ -113,8 +109,6 @@ namespace ImageProcessorCore.Processors
/// Applies the process to the specified portion of the specified at the specified location
/// and with the specified size.
///
- /// The pixel format.
- /// The packed format. long, float.
/// Target image to apply the process to.
/// The source image. Cannot be null.
///
@@ -130,15 +124,11 @@ namespace ImageProcessorCore.Processors
/// The method keeps the source image unchanged and returns the
/// the result of image process as new image.
///
- protected abstract void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY)
- where T : IPackedVector
- where TP : struct;
+ protected abstract void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY);
///
/// This method is called after the process is applied to prepare the processor.
///
- /// The pixel format.
- /// The packed format. long, float.
/// Target image to apply the process to.
/// The source image. Cannot be null.
///
@@ -148,9 +138,7 @@ namespace ImageProcessorCore.Processors
///
/// The structure that specifies the portion of the image object to draw.
///
- protected virtual void AfterApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle)
- where T : IPackedVector
- where TP : struct
+ protected virtual void AfterApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle)
{
}
diff --git a/src/ImageProcessorCore/Samplers/Crop.cs b/src/ImageProcessorCore/Samplers/Crop.cs
index 6ff3f49c0..ff3196c43 100644
--- a/src/ImageProcessorCore/Samplers/Crop.cs
+++ b/src/ImageProcessorCore/Samplers/Crop.cs
@@ -60,7 +60,7 @@ namespace ImageProcessorCore
source = source.Resize(sourceRectangle.Width, sourceRectangle.Height);
}
- CropProcessor processor = new CropProcessor();
+ CropProcessor processor = new CropProcessor();
processor.OnProgress += progressHandler;
try
diff --git a/src/ImageProcessorCore/Samplers/EntropyCrop.cs b/src/ImageProcessorCore/Samplers/EntropyCrop.cs
new file mode 100644
index 000000000..123378173
--- /dev/null
+++ b/src/ImageProcessorCore/Samplers/EntropyCrop.cs
@@ -0,0 +1,41 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+// -------------------------------------------------------------------------------------------------------------------
+
+namespace ImageProcessorCore
+{
+ using Processors;
+
+ ///
+ /// Extension methods for the type.
+ ///
+ public static partial class ImageExtensions
+ {
+ ///
+ /// Crops an image to the area of greatest entropy.
+ ///
+ /// The pixel format.
+ /// The packed format. long, float.
+ /// The image to crop.
+ /// The threshold for entropic density.
+ /// A delegate which is called as progress is made processing the image.
+ /// The
+ public static Image EntropyCrop(this Image source, float threshold = .5f, ProgressEventHandler progressHandler = null)
+ where T : IPackedVector
+ where TP : struct
+ {
+ EntropyCropProcessor processor = new EntropyCropProcessor(threshold);
+ processor.OnProgress += progressHandler;
+
+ try
+ {
+ return source.Process(source.Width, source.Height, source.Bounds, Rectangle.Empty, processor);
+ }
+ finally
+ {
+ processor.OnProgress -= progressHandler;
+ }
+ }
+ }
+}
diff --git a/src/ImageProcessorCore/Samplers/Processors/CropProcessor.cs b/src/ImageProcessorCore/Samplers/Processors/CropProcessor.cs
index 2029c33bd..099071112 100644
--- a/src/ImageProcessorCore/Samplers/Processors/CropProcessor.cs
+++ b/src/ImageProcessorCore/Samplers/Processors/CropProcessor.cs
@@ -10,10 +10,12 @@ namespace ImageProcessorCore.Processors
///
/// Provides methods to allow the cropping of an image.
///
- public class CropProcessor : ImageSampler
+ public class CropProcessor : ImageSampler
+ where T : IPackedVector
+ where TP : struct
{
///
- protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY)
+ protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY)
{
int startX = targetRectangle.X;
int endX = targetRectangle.Right;
diff --git a/src/ImageProcessorCore/Samplers/Processors/EntropyCropProcessor.cs b/src/ImageProcessorCore/Samplers/Processors/EntropyCropProcessor.cs
new file mode 100644
index 000000000..29afff11a
--- /dev/null
+++ b/src/ImageProcessorCore/Samplers/Processors/EntropyCropProcessor.cs
@@ -0,0 +1,106 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageProcessorCore.Processors
+{
+ using System;
+ using System.Threading.Tasks;
+
+ ///
+ /// Provides methods to allow the cropping of an image to preserve areas of highest
+ /// entropy.
+ ///
+ public class EntropyCropProcessor : ImageSampler
+ where T : IPackedVector
+ where TP : struct
+ {
+ ///
+ /// The rectangle for cropping
+ ///
+ private Rectangle cropRectangle;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The threshold to split the image. Must be between 0 and 1.
+ ///
+ /// is less than 0 or is greater than 1.
+ ///
+ public EntropyCropProcessor(float threshold)
+ {
+ Guard.MustBeBetweenOrEqualTo(threshold, 0, 1, nameof(threshold));
+ this.Value = threshold;
+ }
+
+ ///
+ /// Gets the threshold value.
+ ///
+ public float Value { get; }
+
+ ///
+ protected override void OnApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle)
+ {
+ ImageBase temp = new Image(source.Width, source.Height);
+
+ // Detect the edges.
+ new SobelProcessor().Apply(temp, source, sourceRectangle);
+
+ // Apply threshold binarization filter.
+ new ThresholdProcessor(.5f).Apply(temp, temp, sourceRectangle);
+
+ // Search for the first white pixels
+ Rectangle rectangle = ImageMaths.GetFilteredBoundingRectangle(temp, 0);
+
+ // Reset the target pixel to the correct size.
+ target.SetPixels(rectangle.Width, rectangle.Height, new T[rectangle.Width * rectangle.Height]);
+ this.cropRectangle = rectangle;
+ }
+
+ ///
+ protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY)
+ {
+ // Jump out, we'll deal with that later.
+ if (source.Bounds == target.Bounds)
+ {
+ return;
+ }
+
+ int targetY = this.cropRectangle.Y;
+ int targetBottom = this.cropRectangle.Bottom;
+ int startX = this.cropRectangle.X;
+ int endX = this.cropRectangle.Right;
+
+ using (IPixelAccessor sourcePixels = source.Lock())
+ using (IPixelAccessor targetPixels = target.Lock())
+ {
+ Parallel.For(
+ startY,
+ endY,
+ y =>
+ {
+ if (y >= targetY && y < targetBottom)
+ {
+ for (int x = startX; x < endX; x++)
+ {
+ targetPixels[x - startX, y - targetY] = sourcePixels[x, y];
+ }
+ }
+
+ this.OnRowProcessed();
+ });
+ }
+ }
+
+ ///
+ protected override void AfterApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle)
+ {
+ // Copy the pixels over.
+ if (source.Bounds == target.Bounds)
+ {
+ target.ClonePixels(target.Width, target.Height, source.Pixels);
+ }
+ }
+ }
+}
diff --git a/src/ImageProcessorCore/Samplers/Processors/IImageSampler.cs b/src/ImageProcessorCore/Samplers/Processors/IImageSampler.cs
index 76a2c5a4d..59326dee4 100644
--- a/src/ImageProcessorCore/Samplers/Processors/IImageSampler.cs
+++ b/src/ImageProcessorCore/Samplers/Processors/IImageSampler.cs
@@ -8,7 +8,9 @@ namespace ImageProcessorCore.Processors
///
/// Acts as a marker for generic parameters that require an image sampler.
///
- public interface IImageSampler : IImageProcessor
+ public interface IImageSampler : IImageProcessor
+ where T : IPackedVector
+ where TP : struct
{
///
/// Gets or sets a value indicating whether to compress
diff --git a/src/ImageProcessorCore/Samplers/Processors/ImageSampler.cs b/src/ImageProcessorCore/Samplers/Processors/ImageSampler.cs
index adfe77432..597fbb6fa 100644
--- a/src/ImageProcessorCore/Samplers/Processors/ImageSampler.cs
+++ b/src/ImageProcessorCore/Samplers/Processors/ImageSampler.cs
@@ -9,7 +9,9 @@ namespace ImageProcessorCore.Processors
/// Applies sampling methods to an image.
/// All processors requiring resampling or resizing should inherit from this.
///
- public abstract class ImageSampler : ImageProcessor, IImageSampler
+ public abstract class ImageSampler : ImageProcessor, IImageSampler
+ where T : IPackedVector
+ where TP : struct
{
///
public virtual bool Compand { get; set; } = false;
diff --git a/src/ImageProcessorCore/Samplers/Processors/Matrix3x2Processor.cs b/src/ImageProcessorCore/Samplers/Processors/Matrix3x2Processor.cs
new file mode 100644
index 000000000..1d791f909
--- /dev/null
+++ b/src/ImageProcessorCore/Samplers/Processors/Matrix3x2Processor.cs
@@ -0,0 +1,53 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageProcessorCore.Processors
+{
+ using System.Numerics;
+
+ ///
+ /// Provides methods to transform an image using a .
+ ///
+ public abstract class Matrix3x2Processor : ImageSampler
+ where T : IPackedVector
+ where TP : struct
+ {
+ ///
+ /// Creates a new target to contain the results of the matrix transform.
+ ///
+ /// The pixel format.
+ /// The packed format. long, float.
+ /// Target image to apply the process to.
+ /// The source rectangle.
+ /// The processing matrix.
+ protected static void CreateNewTarget(ImageBase target, Rectangle sourceRectangle, Matrix3x2 processMatrix)
+ {
+ Matrix3x2 sizeMatrix;
+ if (Matrix3x2.Invert(processMatrix, out sizeMatrix))
+ {
+ Rectangle rectangle = ImageMaths.GetBoundingRectangle(sourceRectangle, sizeMatrix);
+ target.SetPixels(rectangle.Width, rectangle.Height, new T[rectangle.Width * rectangle.Height]);
+ }
+ }
+
+ ///
+ /// Gets a transform matrix adjusted to center upon the target image bounds.
+ ///
+ /// The pixel format.
+ /// The packed format. long, float.
+ /// Target image to apply the process to.
+ /// The source image.
+ /// The transform matrix.
+ ///
+ /// The .
+ ///
+ protected static Matrix3x2 GetCenteredMatrix(ImageBase target, ImageBase source, Matrix3x2 matrix)
+ {
+ Matrix3x2 translationToTargetCenter = Matrix3x2.CreateTranslation(-target.Width / 2f, -target.Height / 2f);
+ Matrix3x2 translateToSourceCenter = Matrix3x2.CreateTranslation(source.Width / 2f, source.Height / 2f);
+ return (translationToTargetCenter * matrix) * translateToSourceCenter;
+ }
+ }
+}
diff --git a/src/ImageProcessorCore/Samplers/Processors/ResizeProcessor.cs b/src/ImageProcessorCore/Samplers/Processors/ResizeProcessor.cs
index 3f78ed0be..cd98b49c5 100644
--- a/src/ImageProcessorCore/Samplers/Processors/ResizeProcessor.cs
+++ b/src/ImageProcessorCore/Samplers/Processors/ResizeProcessor.cs
@@ -12,7 +12,9 @@ namespace ImageProcessorCore.Processors
///
/// Provides methods that allow the resizing of images using various algorithms.
///
- public class ResizeProcessor : ImageSampler
+ public class ResizeProcessor : ImageSampler
+ where T : IPackedVector
+ where TP : struct
{
///
/// Initializes a new instance of the class.
@@ -43,7 +45,7 @@ namespace ImageProcessorCore.Processors
protected Weights[] VerticalWeights { get; set; }
///
- protected override void OnApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle)
+ protected override void OnApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle)
{
if (!(this.Sampler is NearestNeighborResampler))
{
@@ -53,7 +55,7 @@ namespace ImageProcessorCore.Processors
}
///
- protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY)
+ protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY)
{
// Jump out, we'll deal with that later.
if (source.Bounds == target.Bounds && sourceRectangle == targetRectangle)
@@ -203,7 +205,7 @@ namespace ImageProcessorCore.Processors
}
///
- protected override void AfterApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle)
+ protected override void AfterApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle)
{
// Copy the pixels over.
if (source.Bounds == target.Bounds && sourceRectangle == targetRectangle)
diff --git a/src/ImageProcessorCore/Samplers/Processors/RotateFlipProcessor.cs b/src/ImageProcessorCore/Samplers/Processors/RotateFlipProcessor.cs
new file mode 100644
index 000000000..5ca46cd44
--- /dev/null
+++ b/src/ImageProcessorCore/Samplers/Processors/RotateFlipProcessor.cs
@@ -0,0 +1,243 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageProcessorCore.Processors
+{
+ using System;
+ using System.Threading.Tasks;
+
+ ///
+ /// Provides methods that allow the rotation and flipping of an image around its center point.
+ ///
+ public class RotateFlipProcessor : ImageSampler
+ where T : IPackedVector
+ where TP : struct
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The used to perform rotation.
+ /// The used to perform flipping.
+ public RotateFlipProcessor(RotateType rotateType, FlipType flipType)
+ {
+ this.RotateType = rotateType;
+ this.FlipType = flipType;
+ }
+
+ ///
+ /// Gets the used to perform flipping.
+ ///
+ public FlipType FlipType { get; }
+
+ ///
+ /// Gets the used to perform rotation.
+ ///
+ public RotateType RotateType { get; }
+
+ ///
+ protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY)
+ {
+ switch (this.RotateType)
+ {
+ case RotateType.Rotate90:
+ this.Rotate90(target, source);
+ break;
+ case RotateType.Rotate180:
+ this.Rotate180(target, source);
+ break;
+ case RotateType.Rotate270:
+ this.Rotate270(target, source);
+ break;
+ default:
+ target.ClonePixels(target.Width, target.Height, source.Pixels);
+ break;
+ }
+
+ switch (this.FlipType)
+ {
+ // No default needed as we have already set the pixels.
+ case FlipType.Vertical:
+ this.FlipX(target);
+ break;
+ case FlipType.Horizontal:
+ this.FlipY(target);
+ break;
+ }
+ }
+
+ ///
+ /// Rotates the image 270 degrees clockwise at the centre point.
+ ///
+ /// The pixel format.
+ /// The packed format. long, float.
+ /// The target image.
+ /// The source image.
+ private void Rotate270(ImageBase target, ImageBase source)
+ {
+ int width = source.Width;
+ int height = source.Height;
+ Image temp = new Image(height, width);
+
+ using (IPixelAccessor sourcePixels = source.Lock())
+ using (IPixelAccessor tempPixels = temp.Lock())
+ {
+ Parallel.For(
+ 0,
+ height,
+ y =>
+ {
+ for (int x = 0; x < width; x++)
+ {
+ int newX = height - y - 1;
+ newX = height - newX - 1;
+ int newY = width - x - 1;
+ newY = width - newY - 1;
+ tempPixels[newX, newY] = sourcePixels[x, y];
+ }
+
+ this.OnRowProcessed();
+ });
+ }
+
+ target.SetPixels(height, width, temp.Pixels);
+ }
+
+ ///
+ /// Rotates the image 180 degrees clockwise at the centre point.
+ ///
+ /// The pixel format.
+ /// The packed format. long, float.
+ /// The target image.
+ /// The source image.
+ private void Rotate180(ImageBase target, ImageBase source)
+ {
+ int width = source.Width;
+ int height = source.Height;
+
+ using (IPixelAccessor sourcePixels = source.Lock())
+ using (IPixelAccessor targetPixels = target.Lock())
+ {
+ Parallel.For(
+ 0,
+ height,
+ y =>
+ {
+ for (int x = 0; x < width; x++)
+ {
+ int newX = width - x - 1;
+ int newY = height - y - 1;
+ targetPixels[newX, newY] = sourcePixels[x, y];
+ }
+
+ this.OnRowProcessed();
+ });
+ }
+ }
+
+ ///
+ /// Rotates the image 90 degrees clockwise at the centre point.
+ ///
+ /// The pixel format.
+ /// The packed format. long, float.
+ /// The target image.
+ /// The source image.
+ private void Rotate90(ImageBase target, ImageBase source)
+ {
+ int width = source.Width;
+ int height = source.Height;
+ Image temp = new Image(height, width);
+
+ using (IPixelAccessor sourcePixels = source.Lock())
+ using (IPixelAccessor tempPixels = temp.Lock())
+ {
+ Parallel.For(
+ 0,
+ height,
+ y =>
+ {
+ for (int x = 0; x < width; x++)
+ {
+ int newX = height - y - 1;
+ tempPixels[newX, x] = sourcePixels[x, y];
+ }
+
+ this.OnRowProcessed();
+ });
+ }
+
+ target.SetPixels(height, width, temp.Pixels);
+ }
+
+ ///
+ /// Swaps the image at the X-axis, which goes horizontally through the middle
+ /// at half the height of the image.
+ ///
+ /// The pixel format.
+ /// The packed format. long, float.
+ /// Target image to apply the process to.
+ private void FlipX(ImageBase target)
+ {
+ int width = target.Width;
+ int height = target.Height;
+ int halfHeight = (int)Math.Ceiling(target.Height * .5);
+ Image temp = new Image(width, height);
+ temp.ClonePixels(width, height, target.Pixels);
+
+ using (IPixelAccessor targetPixels = target.Lock())
+ using (IPixelAccessor tempPixels = temp.Lock())
+ {
+ Parallel.For(
+ 0,
+ halfHeight,
+ y =>
+ {
+ for (int x = 0; x < width; x++)
+ {
+ int newY = height - y - 1;
+ targetPixels[x, y] = tempPixels[x, newY];
+ targetPixels[x, newY] = tempPixels[x, y];
+ }
+
+ this.OnRowProcessed();
+ });
+ }
+ }
+
+ ///
+ /// Swaps the image at the Y-axis, which goes vertically through the middle
+ /// at half of the width of the image.
+ ///
+ /// The pixel format.
+ /// The packed format. long, float.
+ /// Target image to apply the process to.
+ private void FlipY(ImageBase target)
+ {
+ int width = target.Width;
+ int height = target.Height;
+ int halfWidth = (int)Math.Ceiling(width / 2d);
+ Image temp = new Image(width, height);
+ temp.ClonePixels(width, height, target.Pixels);
+
+ using (IPixelAccessor targetPixels = target.Lock())
+ using (IPixelAccessor tempPixels = temp.Lock())
+ {
+ Parallel.For(
+ 0,
+ height,
+ y =>
+ {
+ for (int x = 0; x < halfWidth; x++)
+ {
+ int newX = width - x - 1;
+ targetPixels[x, y] = tempPixels[newX, y];
+ targetPixels[newX, y] = tempPixels[x, y];
+ }
+
+ this.OnRowProcessed();
+ });
+ }
+ }
+ }
+}
diff --git a/src/ImageProcessorCore/Samplers/Processors/RotateProcessor.cs b/src/ImageProcessorCore/Samplers/Processors/RotateProcessor.cs
new file mode 100644
index 000000000..71ae1aad2
--- /dev/null
+++ b/src/ImageProcessorCore/Samplers/Processors/RotateProcessor.cs
@@ -0,0 +1,70 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageProcessorCore.Processors
+{
+ using System.Numerics;
+ using System.Threading.Tasks;
+
+ ///
+ /// Provides methods that allow the rotating of images.
+ ///
+ public class RotateProcessor : Matrix3x2Processor
+ where T : IPackedVector
+ where TP : struct
+ {
+ ///
+ /// The tranform matrix to apply.
+ ///
+ private Matrix3x2 processMatrix;
+
+ ///
+ /// Gets or sets the angle of processMatrix in degrees.
+ ///
+ public float Angle { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether to expand the canvas to fit the rotated image.
+ ///
+ public bool Expand { get; set; } = true;
+
+ ///
+ protected override void OnApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle)
+ {
+ processMatrix = Point.CreateRotation(new Point(0, 0), -this.Angle);
+ if (this.Expand)
+ {
+ CreateNewTarget(target, sourceRectangle, processMatrix);
+ }
+ }
+
+ ///
+ protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY)
+ {
+ Matrix3x2 matrix = GetCenteredMatrix(target, source, this.processMatrix);
+
+ using (IPixelAccessor sourcePixels = source.Lock())
+ using (IPixelAccessor targetPixels = target.Lock())
+ {
+ Parallel.For(
+ 0,
+ target.Height,
+ y =>
+ {
+ for (int x = 0; x < target.Width; x++)
+ {
+ Point transformedPoint = Point.Rotate(new Point(x, y), matrix);
+ if (source.Bounds.Contains(transformedPoint.X, transformedPoint.Y))
+ {
+ targetPixels[x, y] = sourcePixels[transformedPoint.X, transformedPoint.Y];
+ }
+ }
+
+ OnRowProcessed();
+ });
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageProcessorCore/Samplers/Processors/SkewProcessor.cs b/src/ImageProcessorCore/Samplers/Processors/SkewProcessor.cs
new file mode 100644
index 000000000..a595d3ee6
--- /dev/null
+++ b/src/ImageProcessorCore/Samplers/Processors/SkewProcessor.cs
@@ -0,0 +1,75 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageProcessorCore.Processors
+{
+ using System.Numerics;
+ using System.Threading.Tasks;
+
+ ///
+ /// Provides methods that allow the skewing of images.
+ ///
+ public class SkewProcessor : Matrix3x2Processor
+ where T : IPackedVector
+ where TP : struct
+ {
+ ///
+ /// The tranform matrix to apply.
+ ///
+ private Matrix3x2 processMatrix;
+
+ ///
+ /// Gets or sets the angle of rotation along the x-axis in degrees.
+ ///
+ public float AngleX { get; set; }
+
+ ///
+ /// Gets or sets the angle of rotation along the y-axis in degrees.
+ ///
+ public float AngleY { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether to expand the canvas to fit the skewed image.
+ ///
+ public bool Expand { get; set; } = true;
+
+ ///
+ protected override void OnApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle)
+ {
+ this.processMatrix = Point.CreateSkew(new Point(0, 0), -this.AngleX, -this.AngleY);
+ if (this.Expand)
+ {
+ CreateNewTarget(target, sourceRectangle, this.processMatrix);
+ }
+ }
+
+ ///
+ protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY)
+ {
+ Matrix3x2 matrix = GetCenteredMatrix(target, source, this.processMatrix);
+
+ using (IPixelAccessor sourcePixels = source.Lock())
+ using (IPixelAccessor targetPixels = target.Lock())
+ {
+ Parallel.For(
+ 0,
+ target.Height,
+ y =>
+ {
+ for (int x = 0; x < target.Width; x++)
+ {
+ Point transformedPoint = Point.Skew(new Point(x, y), matrix);
+ if (source.Bounds.Contains(transformedPoint.X, transformedPoint.Y))
+ {
+ targetPixels[x, y] = sourcePixels[transformedPoint.X, transformedPoint.Y];
+ }
+ }
+
+ OnRowProcessed();
+ });
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageProcessorCore/Samplers/Resize.cs b/src/ImageProcessorCore/Samplers/Resize.cs
index aa2089d07..e9d3093f4 100644
--- a/src/ImageProcessorCore/Samplers/Resize.cs
+++ b/src/ImageProcessorCore/Samplers/Resize.cs
@@ -138,7 +138,7 @@ namespace ImageProcessorCore
Guard.MustBeGreaterThan(width, 0, nameof(width));
Guard.MustBeGreaterThan(height, 0, nameof(height));
- ResizeProcessor processor = new ResizeProcessor(sampler) { Compand = compand };
+ ResizeProcessor processor = new ResizeProcessor(sampler) { Compand = compand };
processor.OnProgress += progressHandler;
try
diff --git a/src/ImageProcessorCore/Samplers/Rotate.cs b/src/ImageProcessorCore/Samplers/Rotate.cs
new file mode 100644
index 000000000..8a53b6498
--- /dev/null
+++ b/src/ImageProcessorCore/Samplers/Rotate.cs
@@ -0,0 +1,58 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageProcessorCore
+{
+ using Processors;
+
+ ///
+ /// Extension methods for the type.
+ ///
+ public static partial class ImageExtensions
+ {
+ ///
+ /// Rotates an image by the given angle in degrees, expanding the image to fit the rotated result.
+ ///
+ /// The pixel format.
+ /// The packed format. long, float.
+ /// The image to rotate.
+ /// The angle in degrees to perform the rotation.
+ /// A delegate which is called as progress is made processing the image.
+ /// The
+ public static Image Rotate(this Image source, float degrees, ProgressEventHandler progressHandler = null)
+ where T : IPackedVector
+ where TP : struct
+ {
+ return Rotate(source, degrees, true, progressHandler);
+ }
+
+ ///
+ /// Rotates an image by the given angle in degrees.
+ ///
+ /// The pixel format.
+ /// The packed format. long, float.
+ /// The image to rotate.
+ /// The angle in degrees to perform the rotation.
+ /// Whether to expand the image to fit the rotated result.
+ /// A delegate which is called as progress is made processing the image.
+ /// The
+ public static Image Rotate(this Image source, float degrees, bool expand, ProgressEventHandler progressHandler = null)
+ where T : IPackedVector
+ where TP : struct
+ {
+ RotateProcessor processor = new RotateProcessor { Angle = degrees, Expand = expand };
+ processor.OnProgress += progressHandler;
+
+ try
+ {
+ return source.Process(source.Width, source.Height, source.Bounds, source.Bounds, processor);
+ }
+ finally
+ {
+ processor.OnProgress -= progressHandler;
+ }
+ }
+ }
+}
diff --git a/src/ImageProcessorCore/Samplers/RotateFlip.cs b/src/ImageProcessorCore/Samplers/RotateFlip.cs
new file mode 100644
index 000000000..407737dd3
--- /dev/null
+++ b/src/ImageProcessorCore/Samplers/RotateFlip.cs
@@ -0,0 +1,42 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageProcessorCore
+{
+ using Processors;
+
+ ///
+ /// Extension methods for the type.
+ ///
+ public static partial class ImageExtensions
+ {
+ ///
+ /// Rotates and flips an image by the given instructions.
+ ///
+ /// The pixel format.
+ /// The packed format. long, float.
+ /// The image to rotate, flip, or both.
+ /// The to perform the rotation.
+ /// The to perform the flip.
+ /// A delegate which is called as progress is made processing the image.
+ /// The
+ public static Image RotateFlip(this Image source, RotateType rotateType, FlipType flipType, ProgressEventHandler progressHandler = null)
+ where T : IPackedVector
+ where TP : struct
+ {
+ RotateFlipProcessor processor = new RotateFlipProcessor(rotateType, flipType);
+ processor.OnProgress += progressHandler;
+
+ try
+ {
+ return source.Process(source.Width, source.Height, source.Bounds, source.Bounds, processor);
+ }
+ finally
+ {
+ processor.OnProgress -= progressHandler;
+ }
+ }
+ }
+}
diff --git a/src/ImageProcessorCore/Samplers/Skew.cs b/src/ImageProcessorCore/Samplers/Skew.cs
new file mode 100644
index 000000000..08e07ffd3
--- /dev/null
+++ b/src/ImageProcessorCore/Samplers/Skew.cs
@@ -0,0 +1,60 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageProcessorCore
+{
+ using Processors;
+
+ ///
+ /// Extension methods for the type.
+ ///
+ public static partial class ImageExtensions
+ {
+ ///
+ /// Skews an image by the given angles in degrees, expanding the image to fit the skewed result.
+ ///
+ /// The pixel format.
+ /// The packed format. long, float.
+ /// The image to skew.
+ /// The angle in degrees to perform the rotation along the x-axis.
+ /// The angle in degrees to perform the rotation along the y-axis.
+ /// A delegate which is called as progress is made processing the image.
+ /// The
+ public static Image Skew(this Image source, float degreesX, float degreesY, ProgressEventHandler progressHandler = null)
+ where T : IPackedVector
+ where TP : struct
+ {
+ return Skew(source, degreesX, degreesY, true, progressHandler);
+ }
+
+ ///
+ /// Skews an image by the given angles in degrees.
+ ///
+ /// The pixel format.
+ /// The packed format. long, float.
+ /// The image to skew.
+ /// The angle in degrees to perform the rotation along the x-axis.
+ /// The angle in degrees to perform the rotation along the y-axis.
+ /// Whether to expand the image to fit the skewed result.
+ /// A delegate which is called as progress is made processing the image.
+ /// The
+ public static Image Skew(this Image source, float degreesX, float degreesY, bool expand, ProgressEventHandler progressHandler = null)
+ where T : IPackedVector
+ where TP : struct
+ {
+ SkewProcessor processor = new SkewProcessor { AngleX = degreesX, AngleY = degreesY, Expand = expand };
+ processor.OnProgress += progressHandler;
+
+ try
+ {
+ return source.Process(source.Width, source.Height, source.Bounds, source.Bounds, processor);
+ }
+ finally
+ {
+ processor.OnProgress -= progressHandler;
+ }
+ }
+ }
+}
diff --git a/tests/ImageProcessorCore.Benchmarks/Config.cs b/tests/ImageProcessorCore.Benchmarks/Config.cs
index e2eb1f0ff..126ecdaed 100644
--- a/tests/ImageProcessorCore.Benchmarks/Config.cs
+++ b/tests/ImageProcessorCore.Benchmarks/Config.cs
@@ -8,7 +8,8 @@ namespace ImageProcessorCore.Benchmarks
{
// Uncomment if you want to use any of the diagnoser
this.Add(new BenchmarkDotNet.Diagnostics.Windows.MemoryDiagnoser());
- this.Add(new BenchmarkDotNet.Diagnostics.Windows.InliningDiagnoser());
+ // System.Drawing doesn't like this.
+ // this.Add(new BenchmarkDotNet.Diagnostics.Windows.InliningDiagnoser());
}
}
}
\ No newline at end of file
diff --git a/tests/ImageProcessorCore.Tests/FileTestBase.cs b/tests/ImageProcessorCore.Tests/FileTestBase.cs
index 8348d309c..23b75e63a 100644
--- a/tests/ImageProcessorCore.Tests/FileTestBase.cs
+++ b/tests/ImageProcessorCore.Tests/FileTestBase.cs
@@ -19,17 +19,17 @@ namespace ImageProcessorCore.Tests
///
protected static readonly List Files = new List
{
- "TestImages/Formats/Jpg/Floorplan.jpeg", // Perf: Enable for local testing only
+ //"TestImages/Formats/Jpg/Floorplan.jpeg", // Perf: Enable for local testing only
"TestImages/Formats/Jpg/Calliphora.jpg",
- "TestImages/Formats/Jpg/fb.jpg", // Perf: Enable for local testing only
- "TestImages/Formats/Jpg/progress.jpg", // Perf: Enable for local testing only
+ //"TestImages/Formats/Jpg/fb.jpg", // Perf: Enable for local testing only
+ //"TestImages/Formats/Jpg/progress.jpg", // Perf: Enable for local testing only
//"TestImages/Formats/Jpg/gamma_dalai_lama_gray.jpg", // Perf: Enable for local testing only
- //"TestImages/Formats/Bmp/Car.bmp",
+ "TestImages/Formats/Bmp/Car.bmp",
// "TestImages/Formats/Bmp/neg_height.bmp", // Perf: Enable for local testing only
//"TestImages/Formats/Png/blur.png", // Perf: Enable for local testing only
//"TestImages/Formats/Png/indexed.png", // Perf: Enable for local testing only
- //"TestImages/Formats/Png/splash.png",
- //"TestImages/Formats/Gif/rings.gif",
+ "TestImages/Formats/Png/splash.png",
+ "TestImages/Formats/Gif/rings.gif",
//"TestImages/Formats/Gif/giphy.gif" // Perf: Enable for local testing only
};
diff --git a/tests/ImageProcessorCore.Tests/Processors/Samplers/SamplerTests.cs b/tests/ImageProcessorCore.Tests/Processors/Samplers/SamplerTests.cs
index 9d9726a9c..0cb8ff025 100644
--- a/tests/ImageProcessorCore.Tests/Processors/Samplers/SamplerTests.cs
+++ b/tests/ImageProcessorCore.Tests/Processors/Samplers/SamplerTests.cs
@@ -7,8 +7,6 @@ namespace ImageProcessorCore.Tests
{
using System.IO;
- using Processors;
-
using Xunit;
public class SamplerTests : FileTestBase
@@ -24,7 +22,7 @@ namespace ImageProcessorCore.Tests
//{ "Lanczos3", new Lanczos3Resampler() },
//{ "Lanczos5", new Lanczos5Resampler() },
//{ "Lanczos8", new Lanczos8Resampler() },
- { "MitchellNetravali", new MitchellNetravaliResampler() },
+ //{ "MitchellNetravali", new MitchellNetravaliResampler() },
//{ "Hermite", new HermiteResampler() },
//{ "Spline", new SplineResampler() },
//{ "Robidoux", new RobidouxResampler() },
@@ -33,12 +31,6 @@ namespace ImageProcessorCore.Tests
//{ "Welch", new WelchResampler() }
};
- public static readonly TheoryData Samplers = new TheoryData
- {
- { "Resize", new ResizeProcessor(new BicubicResampler()) },
- //{ "Crop", new CropProcessor() }
- };
-
public static readonly TheoryData RotateFlips = new TheoryData
{
{ RotateType.None, FlipType.Vertical },
@@ -48,56 +40,29 @@ namespace ImageProcessorCore.Tests
{ RotateType.Rotate270, FlipType.None },
};
- //[Theory]
- //[MemberData("Samplers")]
- //public void SampleImage(string name, IImageSampler processor)
- //{
- // if (!Directory.Exists("TestOutput/Sample"))
- // {
- // Directory.CreateDirectory("TestOutput/Sample");
- // }
-
- // foreach (string file in Files)
- // {
- // using (FileStream stream = File.OpenRead(file))
- // {
- // Image image = new Image(stream);
- // string filename = Path.GetFileNameWithoutExtension(file) + "-" + name + Path.GetExtension(file);
-
- // using (FileStream output = File.OpenWrite($"TestOutput/Sample/{ Path.GetFileName(filename) }"))
- // {
- // processor.OnProgress += this.ProgressUpdate;
- // image = image.Process(image.Width / 2, image.Height / 2, processor);
- // image.Save(output);
- // processor.OnProgress -= this.ProgressUpdate;
- // }
- // }
- // }
- //}
-
- //[Fact]
- //public void ImageShouldPad()
- //{
- // if (!Directory.Exists("TestOutput/Pad"))
- // {
- // Directory.CreateDirectory("TestOutput/Pad");
- // }
+ [Fact]
+ public void ImageShouldPad()
+ {
+ if (!Directory.Exists("TestOutput/Pad"))
+ {
+ Directory.CreateDirectory("TestOutput/Pad");
+ }
- // foreach (string file in Files)
- // {
- // using (FileStream stream = File.OpenRead(file))
- // {
- // string filename = Path.GetFileName(file);
+ foreach (string file in Files)
+ {
+ using (FileStream stream = File.OpenRead(file))
+ {
+ string filename = Path.GetFileName(file);
- // Image image = new Image(stream);
- // using (FileStream output = File.OpenWrite($"TestOutput/Pad/{filename}"))
- // {
- // image.Pad(image.Width + 50, image.Height + 50, this.ProgressUpdate)
- // .Save(output);
- // }
- // }
- // }
- //}
+ Image image = new Image(stream);
+ using (FileStream output = File.OpenWrite($"TestOutput/Pad/{filename}"))
+ {
+ image.Pad(image.Width + 50, image.Height + 50, this.ProgressUpdate)
+ .Save(output);
+ }
+ }
+ }
+ }
[Theory]
[MemberData("ReSamplers")]
@@ -114,7 +79,7 @@ namespace ImageProcessorCore.Tests
{
string filename = Path.GetFileNameWithoutExtension(file) + "-" + name + Path.GetExtension(file);
- Image image = new Image(stream) {Quality=256};
+ Image image = new Image(stream);
using (FileStream output = File.OpenWrite($"TestOutput/Resize/{filename}"))
{
image.Resize(image.Width / 2, image.Height / 2, sampler, false, this.ProgressUpdate)
@@ -124,237 +89,237 @@ namespace ImageProcessorCore.Tests
}
}
- //[Fact]
- //public void ImageShouldResizeWidthAndKeepAspect()
- //{
- // if (!Directory.Exists("TestOutput/Resize"))
- // {
- // Directory.CreateDirectory("TestOutput/Resize");
- // }
-
- // var name = "FixedWidth";
+ [Fact]
+ public void ImageShouldResizeWidthAndKeepAspect()
+ {
+ if (!Directory.Exists("TestOutput/Resize"))
+ {
+ Directory.CreateDirectory("TestOutput/Resize");
+ }
- // foreach (string file in Files)
- // {
- // using (FileStream stream = File.OpenRead(file))
- // {
- // string filename = Path.GetFileNameWithoutExtension(file) + "-" + name + Path.GetExtension(file);
+ var name = "FixedWidth";
- // Image image = new Image(stream);
- // using (FileStream output = File.OpenWrite($"TestOutput/Resize/{filename}"))
- // {
- // image.Resize(image.Width / 3, 0, new TriangleResampler(), false, this.ProgressUpdate)
- // .Save(output);
- // }
- // }
- // }
- //}
+ foreach (string file in Files)
+ {
+ using (FileStream stream = File.OpenRead(file))
+ {
+ string filename = Path.GetFileNameWithoutExtension(file) + "-" + name + Path.GetExtension(file);
- //[Fact]
- //public void ImageShouldResizeHeightAndKeepAspect()
- //{
- // if (!Directory.Exists("TestOutput/Resize"))
- // {
- // Directory.CreateDirectory("TestOutput/Resize");
- // }
+ Image image = new Image(stream);
+ using (FileStream output = File.OpenWrite($"TestOutput/Resize/{filename}"))
+ {
+ image.Resize(image.Width / 3, 0, new TriangleResampler(), false, this.ProgressUpdate)
+ .Save(output);
+ }
+ }
+ }
+ }
- // string name = "FixedHeight";
+ [Fact]
+ public void ImageShouldResizeHeightAndKeepAspect()
+ {
+ if (!Directory.Exists("TestOutput/Resize"))
+ {
+ Directory.CreateDirectory("TestOutput/Resize");
+ }
- // foreach (string file in Files)
- // {
- // using (FileStream stream = File.OpenRead(file))
- // {
- // string filename = Path.GetFileNameWithoutExtension(file) + "-" + name + Path.GetExtension(file);
+ string name = "FixedHeight";
- // Image image = new Image(stream);
- // using (FileStream output = File.OpenWrite($"TestOutput/Resize/{filename}"))
- // {
- // image.Resize(0, image.Height / 3, new TriangleResampler(), false, this.ProgressUpdate)
- // .Save(output);
- // }
- // }
- // }
- //}
+ foreach (string file in Files)
+ {
+ using (FileStream stream = File.OpenRead(file))
+ {
+ string filename = Path.GetFileNameWithoutExtension(file) + "-" + name + Path.GetExtension(file);
- //[Fact]
- //public void ImageShouldResizeWithCropMode()
- //{
- // if (!Directory.Exists("TestOutput/ResizeCrop"))
- // {
- // Directory.CreateDirectory("TestOutput/ResizeCrop");
- // }
+ Image image = new Image(stream);
+ using (FileStream output = File.OpenWrite($"TestOutput/Resize/{filename}"))
+ {
+ image.Resize(0, image.Height / 3, new TriangleResampler(), false, this.ProgressUpdate)
+ .Save(output);
+ }
+ }
+ }
+ }
- // foreach (string file in Files)
- // {
- // using (FileStream stream = File.OpenRead(file))
- // {
- // string filename = Path.GetFileName(file);
+ [Fact]
+ public void ImageShouldResizeWithCropMode()
+ {
+ if (!Directory.Exists("TestOutput/ResizeCrop"))
+ {
+ Directory.CreateDirectory("TestOutput/ResizeCrop");
+ }
- // Image image = new Image(stream);
- // using (FileStream output = File.OpenWrite($"TestOutput/ResizeCrop/{filename}"))
- // {
- // ResizeOptions options = new ResizeOptions()
- // {
- // Size = new Size(image.Width / 2, image.Height)
- // };
+ foreach (string file in Files)
+ {
+ using (FileStream stream = File.OpenRead(file))
+ {
+ string filename = Path.GetFileName(file);
- // image.Resize(options, this.ProgressUpdate)
- // .Save(output);
- // }
- // }
- // }
- //}
+ Image image = new Image(stream);
+ using (FileStream output = File.OpenWrite($"TestOutput/ResizeCrop/{filename}"))
+ {
+ ResizeOptions options = new ResizeOptions()
+ {
+ Size = new Size(image.Width / 2, image.Height)
+ };
- //[Fact]
- //public void ImageShouldResizeWithPadMode()
- //{
- // if (!Directory.Exists("TestOutput/ResizePad"))
- // {
- // Directory.CreateDirectory("TestOutput/ResizePad");
- // }
+ image.Resize(options, this.ProgressUpdate)
+ .Save(output);
+ }
+ }
+ }
+ }
- // foreach (string file in Files)
- // {
- // using (FileStream stream = File.OpenRead(file))
- // {
- // string filename = Path.GetFileName(file);
+ [Fact]
+ public void ImageShouldResizeWithPadMode()
+ {
+ if (!Directory.Exists("TestOutput/ResizePad"))
+ {
+ Directory.CreateDirectory("TestOutput/ResizePad");
+ }
- // Image image = new Image(stream);
- // using (FileStream output = File.OpenWrite($"TestOutput/ResizePad/{filename}"))
- // {
- // ResizeOptions options = new ResizeOptions()
- // {
- // Size = new Size(image.Width + 200, image.Height),
- // Mode = ResizeMode.Pad
- // };
+ foreach (string file in Files)
+ {
+ using (FileStream stream = File.OpenRead(file))
+ {
+ string filename = Path.GetFileName(file);
- // image.Resize(options, this.ProgressUpdate)
- // .Save(output);
- // }
- // }
- // }
- //}
+ Image image = new Image(stream);
+ using (FileStream output = File.OpenWrite($"TestOutput/ResizePad/{filename}"))
+ {
+ ResizeOptions options = new ResizeOptions()
+ {
+ Size = new Size(image.Width + 200, image.Height),
+ Mode = ResizeMode.Pad
+ };
- //[Fact]
- //public void ImageShouldResizeWithBoxPadMode()
- //{
- // if (!Directory.Exists("TestOutput/ResizeBoxPad"))
- // {
- // Directory.CreateDirectory("TestOutput/ResizeBoxPad");
- // }
+ image.Resize(options, this.ProgressUpdate)
+ .Save(output);
+ }
+ }
+ }
+ }
- // foreach (string file in Files)
- // {
- // using (FileStream stream = File.OpenRead(file))
- // {
- // string filename = Path.GetFileName(file);
+ [Fact]
+ public void ImageShouldResizeWithBoxPadMode()
+ {
+ if (!Directory.Exists("TestOutput/ResizeBoxPad"))
+ {
+ Directory.CreateDirectory("TestOutput/ResizeBoxPad");
+ }
- // Image image = new Image(stream);
- // using (FileStream output = File.OpenWrite($"TestOutput/ResizeBoxPad/{filename}"))
- // {
- // ResizeOptions options = new ResizeOptions()
- // {
- // Size = new Size(image.Width + 200, image.Height + 200),
- // Mode = ResizeMode.BoxPad
- // };
+ foreach (string file in Files)
+ {
+ using (FileStream stream = File.OpenRead(file))
+ {
+ string filename = Path.GetFileName(file);
- // image.Resize(options, this.ProgressUpdate)
- // .Save(output);
- // }
- // }
- // }
- //}
+ Image image = new Image(stream);
+ using (FileStream output = File.OpenWrite($"TestOutput/ResizeBoxPad/{filename}"))
+ {
+ ResizeOptions options = new ResizeOptions()
+ {
+ Size = new Size(image.Width + 200, image.Height + 200),
+ Mode = ResizeMode.BoxPad
+ };
- //[Fact]
- //public void ImageShouldResizeWithMaxMode()
- //{
- // if (!Directory.Exists("TestOutput/ResizeMax"))
- // {
- // Directory.CreateDirectory("TestOutput/ResizeMax");
- // }
+ image.Resize(options, this.ProgressUpdate)
+ .Save(output);
+ }
+ }
+ }
+ }
- // foreach (string file in Files)
- // {
- // using (FileStream stream = File.OpenRead(file))
- // {
- // string filename = Path.GetFileName(file);
+ [Fact]
+ public void ImageShouldResizeWithMaxMode()
+ {
+ if (!Directory.Exists("TestOutput/ResizeMax"))
+ {
+ Directory.CreateDirectory("TestOutput/ResizeMax");
+ }
- // Image image = new Image(stream);
- // using (FileStream output = File.OpenWrite($"TestOutput/ResizeMax/{filename}"))
- // {
- // ResizeOptions options = new ResizeOptions()
- // {
- // Size = new Size(300, 300),
- // Mode = ResizeMode.Max,
- // //Sampler = new NearestNeighborResampler()
- // };
+ foreach (string file in Files)
+ {
+ using (FileStream stream = File.OpenRead(file))
+ {
+ string filename = Path.GetFileName(file);
- // image.Resize(options, this.ProgressUpdate)
- // .Save(output);
- // }
- // }
- // }
- //}
+ Image image = new Image(stream);
+ using (FileStream output = File.OpenWrite($"TestOutput/ResizeMax/{filename}"))
+ {
+ ResizeOptions options = new ResizeOptions()
+ {
+ Size = new Size(300, 300),
+ Mode = ResizeMode.Max,
+ //Sampler = new NearestNeighborResampler()
+ };
+
+ image.Resize(options, this.ProgressUpdate)
+ .Save(output);
+ }
+ }
+ }
+ }
- //[Fact]
- //public void ImageShouldResizeWithMinMode()
- //{
- // if (!Directory.Exists("TestOutput/ResizeMin"))
- // {
- // Directory.CreateDirectory("TestOutput/ResizeMin");
- // }
+ [Fact]
+ public void ImageShouldResizeWithMinMode()
+ {
+ if (!Directory.Exists("TestOutput/ResizeMin"))
+ {
+ Directory.CreateDirectory("TestOutput/ResizeMin");
+ }
- // foreach (string file in Files)
- // {
- // using (FileStream stream = File.OpenRead(file))
- // {
- // string filename = Path.GetFileName(file);
+ foreach (string file in Files)
+ {
+ using (FileStream stream = File.OpenRead(file))
+ {
+ string filename = Path.GetFileName(file);
- // Image image = new Image(stream);
- // using (FileStream output = File.OpenWrite($"TestOutput/ResizeMin/{filename}"))
- // {
- // ResizeOptions options = new ResizeOptions()
- // {
- // Size = new Size(image.Width - 50, image.Height - 25),
- // Mode = ResizeMode.Min
- // };
+ Image image = new Image(stream);
+ using (FileStream output = File.OpenWrite($"TestOutput/ResizeMin/{filename}"))
+ {
+ ResizeOptions options = new ResizeOptions()
+ {
+ Size = new Size(image.Width - 50, image.Height - 25),
+ Mode = ResizeMode.Min
+ };
- // image.Resize(options, this.ProgressUpdate)
- // .Save(output);
- // }
- // }
- // }
- //}
+ image.Resize(options, this.ProgressUpdate)
+ .Save(output);
+ }
+ }
+ }
+ }
- //[Fact]
- //public void ImageShouldResizeWithStretchMode()
- //{
- // if (!Directory.Exists("TestOutput/ResizeStretch"))
- // {
- // Directory.CreateDirectory("TestOutput/ResizeStretch");
- // }
+ [Fact]
+ public void ImageShouldResizeWithStretchMode()
+ {
+ if (!Directory.Exists("TestOutput/ResizeStretch"))
+ {
+ Directory.CreateDirectory("TestOutput/ResizeStretch");
+ }
- // foreach (string file in Files)
- // {
- // using (FileStream stream = File.OpenRead(file))
- // {
- // string filename = Path.GetFileName(file);
+ foreach (string file in Files)
+ {
+ using (FileStream stream = File.OpenRead(file))
+ {
+ string filename = Path.GetFileName(file);
- // Image image = new Image(stream);
- // using (FileStream output = File.OpenWrite($"TestOutput/ResizeStretch/{filename}"))
- // {
- // ResizeOptions options = new ResizeOptions()
- // {
- // Size = new Size(image.Width - 200, image.Height),
- // Mode = ResizeMode.Stretch
- // };
+ Image image = new Image(stream);
+ using (FileStream output = File.OpenWrite($"TestOutput/ResizeStretch/{filename}"))
+ {
+ ResizeOptions options = new ResizeOptions()
+ {
+ Size = new Size(image.Width - 200, image.Height),
+ Mode = ResizeMode.Stretch
+ };
- // image.Resize(options, this.ProgressUpdate)
- // .Save(output);
- // }
- // }
- // }
- //}
+ image.Resize(options, this.ProgressUpdate)
+ .Save(output);
+ }
+ }
+ }
+ }
//[Fact]
//public void ImageShouldNotDispose()
@@ -387,140 +352,140 @@ namespace ImageProcessorCore.Tests
// }
//}
- //[Theory]
- //[MemberData("RotateFlips")]
- //public void ImageShouldRotateFlip(RotateType rotateType, FlipType flipType)
- //{
- // if (!Directory.Exists("TestOutput/RotateFlip"))
- // {
- // Directory.CreateDirectory("TestOutput/RotateFlip");
- // }
+ [Theory]
+ [MemberData("RotateFlips")]
+ public void ImageShouldRotateFlip(RotateType rotateType, FlipType flipType)
+ {
+ if (!Directory.Exists("TestOutput/RotateFlip"))
+ {
+ Directory.CreateDirectory("TestOutput/RotateFlip");
+ }
- // foreach (string file in Files)
- // {
- // using (FileStream stream = File.OpenRead(file))
- // {
- // string filename = Path.GetFileNameWithoutExtension(file) + "-" + rotateType + flipType + Path.GetExtension(file);
+ foreach (string file in Files)
+ {
+ using (FileStream stream = File.OpenRead(file))
+ {
+ string filename = Path.GetFileNameWithoutExtension(file) + "-" + rotateType + flipType + Path.GetExtension(file);
- // Image image = new Image(stream);
- // using (FileStream output = File.OpenWrite($"TestOutput/RotateFlip/{filename}"))
- // {
- // image.RotateFlip(rotateType, flipType, this.ProgressUpdate)
- // .Save(output);
- // }
- // }
- // }
- //}
+ Image image = new Image(stream);
+ using (FileStream output = File.OpenWrite($"TestOutput/RotateFlip/{filename}"))
+ {
+ image.RotateFlip(rotateType, flipType, this.ProgressUpdate)
+ .Save(output);
+ }
+ }
+ }
+ }
- //[Fact]
- //public void ImageShouldRotate()
- //{
- // if (!Directory.Exists("TestOutput/Rotate"))
- // {
- // Directory.CreateDirectory("TestOutput/Rotate");
- // }
+ [Fact]
+ public void ImageShouldRotate()
+ {
+ if (!Directory.Exists("TestOutput/Rotate"))
+ {
+ Directory.CreateDirectory("TestOutput/Rotate");
+ }
- // foreach (string file in Files)
- // {
- // using (FileStream stream = File.OpenRead(file))
- // {
- // string filename = Path.GetFileName(file);
+ foreach (string file in Files)
+ {
+ using (FileStream stream = File.OpenRead(file))
+ {
+ string filename = Path.GetFileName(file);
- // Image image = new Image(stream);
- // using (FileStream output = File.OpenWrite($"TestOutput/Rotate/{filename}"))
- // {
- // image.Rotate(-170, this.ProgressUpdate)
- // .Save(output);
- // }
- // }
- // }
- //}
+ Image image = new Image(stream);
+ using (FileStream output = File.OpenWrite($"TestOutput/Rotate/{filename}"))
+ {
+ image.Rotate(-170, this.ProgressUpdate)
+ .Save(output);
+ }
+ }
+ }
+ }
- //[Fact]
- //public void ImageShouldSkew()
- //{
- // if (!Directory.Exists("TestOutput/Skew"))
- // {
- // Directory.CreateDirectory("TestOutput/Skew");
- // }
+ [Fact]
+ public void ImageShouldSkew()
+ {
+ if (!Directory.Exists("TestOutput/Skew"))
+ {
+ Directory.CreateDirectory("TestOutput/Skew");
+ }
- // // Matches live example http://www.w3schools.com/css/tryit.asp?filename=trycss3_transform_skew
- // foreach (string file in Files)
- // {
- // using (FileStream stream = File.OpenRead(file))
- // {
- // string filename = Path.GetFileName(file);
+ // Matches live example http://www.w3schools.com/css/tryit.asp?filename=trycss3_transform_skew
+ foreach (string file in Files)
+ {
+ using (FileStream stream = File.OpenRead(file))
+ {
+ string filename = Path.GetFileName(file);
- // Image image = new Image(stream);
- // using (FileStream output = File.OpenWrite($"TestOutput/Skew/{filename}"))
- // {
- // image.Skew(-20, -10, this.ProgressUpdate)
- // .Save(output);
- // }
- // }
- // }
- //}
+ Image image = new Image(stream);
+ using (FileStream output = File.OpenWrite($"TestOutput/Skew/{filename}"))
+ {
+ image.Skew(-20, -10, this.ProgressUpdate)
+ .Save(output);
+ }
+ }
+ }
+ }
- //[Fact]
- //public void ImageShouldEntropyCrop()
- //{
- // if (!Directory.Exists("TestOutput/EntropyCrop"))
- // {
- // Directory.CreateDirectory("TestOutput/EntropyCrop");
- // }
+ [Fact]
+ public void ImageShouldEntropyCrop()
+ {
+ if (!Directory.Exists("TestOutput/EntropyCrop"))
+ {
+ Directory.CreateDirectory("TestOutput/EntropyCrop");
+ }
- // foreach (string file in Files)
- // {
- // using (FileStream stream = File.OpenRead(file))
- // {
- // string filename = Path.GetFileNameWithoutExtension(file) + "-EntropyCrop" + Path.GetExtension(file);
+ foreach (string file in Files)
+ {
+ using (FileStream stream = File.OpenRead(file))
+ {
+ string filename = Path.GetFileNameWithoutExtension(file) + "-EntropyCrop" + Path.GetExtension(file);
- // Image image = new Image(stream);
- // using (FileStream output = File.OpenWrite($"TestOutput/EntropyCrop/{filename}"))
- // {
- // image.EntropyCrop(.5f, this.ProgressUpdate).Save(output);
- // }
- // }
- // }
- //}
+ Image image = new Image(stream);
+ using (FileStream output = File.OpenWrite($"TestOutput/EntropyCrop/{filename}"))
+ {
+ image.EntropyCrop(.5f, this.ProgressUpdate).Save(output);
+ }
+ }
+ }
+ }
- //[Fact]
- //public void ImageShouldCrop()
- //{
- // if (!Directory.Exists("TestOutput/Crop"))
- // {
- // Directory.CreateDirectory("TestOutput/Crop");
- // }
+ [Fact]
+ public void ImageShouldCrop()
+ {
+ if (!Directory.Exists("TestOutput/Crop"))
+ {
+ Directory.CreateDirectory("TestOutput/Crop");
+ }
- // foreach (string file in Files)
- // {
- // using (FileStream stream = File.OpenRead(file))
- // {
+ foreach (string file in Files)
+ {
+ using (FileStream stream = File.OpenRead(file))
+ {
- // string filename = Path.GetFileNameWithoutExtension(file) + "-Crop" + Path.GetExtension(file);
+ string filename = Path.GetFileNameWithoutExtension(file) + "-Crop" + Path.GetExtension(file);
- // Image image = new Image(stream);
- // using (FileStream output = File.OpenWrite($"TestOutput/Crop/{filename}"))
- // {
- // image.Crop(image.Width / 2, image.Height / 2, this.ProgressUpdate).Save(output);
- // }
- // }
- // }
- //}
+ Image image = new Image(stream);
+ using (FileStream output = File.OpenWrite($"TestOutput/Crop/{filename}"))
+ {
+ image.Crop(image.Width / 2, image.Height / 2, this.ProgressUpdate).Save(output);
+ }
+ }
+ }
+ }
- //[Theory]
- //[InlineData(-2, 0)]
- //[InlineData(-1, 0)]
- //[InlineData(0, 1)]
- //[InlineData(1, 0)]
- //[InlineData(2, 0)]
- //[InlineData(2, 0)]
- //public static void Lanczos3WindowOscillatesCorrectly(float x, float expected)
- //{
- // Lanczos3Resampler sampler = new Lanczos3Resampler();
- // float result = sampler.GetValue(x);
+ [Theory]
+ [InlineData(-2, 0)]
+ [InlineData(-1, 0)]
+ [InlineData(0, 1)]
+ [InlineData(1, 0)]
+ [InlineData(2, 0)]
+ [InlineData(2, 0)]
+ public static void Lanczos3WindowOscillatesCorrectly(float x, float expected)
+ {
+ Lanczos3Resampler sampler = new Lanczos3Resampler();
+ float result = sampler.GetValue(x);
- // Assert.Equal(result, expected);
- //}
+ Assert.Equal(result, expected);
+ }
}
-}
+}
\ No newline at end of file