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