Browse Source

Add all samplers.

Former-commit-id: c60883d765b1372be2a9ab88f8494dfa3283d8a6
Former-commit-id: 9fbcdac0c826d57eaa2bafdc5c72ff90079e5f51
Former-commit-id: 2e8a4fba64de7233e5ea741122188eb86612a060
af/merge-core
James Jackson-South 10 years ago
parent
commit
24b5764b98
  1. 33
      src/ImageProcessorCore/Colors/RgbaComponent.cs
  2. 212
      src/ImageProcessorCore/Common/Helpers/ImageMaths.cs
  3. 90
      src/ImageProcessorCore/Filters/Processors/Binarization/ThresholdProcessor.cs
  4. 74
      src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorMatrixFilter.cs
  5. 32
      src/ImageProcessorCore/Filters/Processors/ColorMatrix/GreyscaleBt709Processor.cs
  6. 29
      src/ImageProcessorCore/Filters/Processors/ColorMatrix/IColorMatrixFilter.cs
  7. 118
      src/ImageProcessorCore/Filters/Processors/Convolution/Convolution2DFilter.cs
  8. 95
      src/ImageProcessorCore/Filters/Processors/Convolution/ConvolutionFilter.cs
  9. 28
      src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/EdgeDetector2DFilter.cs
  10. 28
      src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/EdgeDetectorFilter.cs
  11. 21
      src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/IEdgeDetectorFilter.cs
  12. 32
      src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/SobelProcessor.cs
  13. 18
      src/ImageProcessorCore/Image/IImageProcessor.cs
  14. 5
      src/ImageProcessorCore/Image/Image.cs
  15. 10
      src/ImageProcessorCore/Image/ImageExtensions.cs
  16. 28
      src/ImageProcessorCore/ImageProcessor.cs
  17. 2
      src/ImageProcessorCore/Samplers/Crop.cs
  18. 41
      src/ImageProcessorCore/Samplers/EntropyCrop.cs
  19. 6
      src/ImageProcessorCore/Samplers/Processors/CropProcessor.cs
  20. 106
      src/ImageProcessorCore/Samplers/Processors/EntropyCropProcessor.cs
  21. 4
      src/ImageProcessorCore/Samplers/Processors/IImageSampler.cs
  22. 4
      src/ImageProcessorCore/Samplers/Processors/ImageSampler.cs
  23. 53
      src/ImageProcessorCore/Samplers/Processors/Matrix3x2Processor.cs
  24. 10
      src/ImageProcessorCore/Samplers/Processors/ResizeProcessor.cs
  25. 243
      src/ImageProcessorCore/Samplers/Processors/RotateFlipProcessor.cs
  26. 70
      src/ImageProcessorCore/Samplers/Processors/RotateProcessor.cs
  27. 75
      src/ImageProcessorCore/Samplers/Processors/SkewProcessor.cs
  28. 2
      src/ImageProcessorCore/Samplers/Resize.cs
  29. 58
      src/ImageProcessorCore/Samplers/Rotate.cs
  30. 42
      src/ImageProcessorCore/Samplers/RotateFlip.cs
  31. 60
      src/ImageProcessorCore/Samplers/Skew.cs
  32. 3
      tests/ImageProcessorCore.Benchmarks/Config.cs
  33. 12
      tests/ImageProcessorCore.Tests/FileTestBase.cs
  34. 721
      tests/ImageProcessorCore.Tests/Processors/Samplers/SamplerTests.cs

33
src/ImageProcessorCore/Colors/RgbaComponent.cs

@ -0,0 +1,33 @@
// <copyright file="RgbaComponent.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessorCore
{
/// <summary>
/// Enumerates the RGBA (red, green, blue, alpha) color components.
/// </summary>
public enum RgbaComponent
{
/// <summary>
/// The red component.
/// </summary>
R = 0,
/// <summary>
/// The green component.
/// </summary>
G = 1,
/// <summary>
/// The blue component.
/// </summary>
B = 2,
/// <summary>
/// The alpha component.
/// </summary>
A = 3
}
}

212
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.
/// </summary>
/// <typeparam name="T">The pixel format.</typeparam>
/// <typeparam name="TP">The packed format. <example>long, float.</example></typeparam>
/// <param name="bitmap">The <see cref="Image"/> to search within.</param>
/// <param name="componentValue">The color component value to remove.</param>
/// <param name="channel">The <see cref="RgbaComponent"/> channel to test against.</param>
/// <returns>
/// The <see cref="Rectangle"/>.
/// </returns>
//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<PixelAccessor, 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].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<PixelAccessor, 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<PixelAccessor, 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<PixelAccessor, 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<PixelAccessor, 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 (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<T, TP>(ImageBase<T, TP> bitmap, float componentValue, RgbaComponent channel = RgbaComponent.B)
where T : IPackedVector<TP>
where TP : struct
{
const float Epsilon = .00001f;
int width = bitmap.Width;
int height = bitmap.Height;
Point topLeft = new Point();
Point bottomRight = new Point();
Func<IPixelAccessor<T, TP>, 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<IPixelAccessor<T, TP>, 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<IPixelAccessor<T, TP>, 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<IPixelAccessor<T, TP>, 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<IPixelAccessor<T, TP>, 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<T, TP> 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);
}
/// <summary>
/// Ensures that any passed double is correctly rounded to zero

90
src/ImageProcessorCore/Filters/Processors/Binarization/ThresholdProcessor.cs

@ -0,0 +1,90 @@
// <copyright file="ThresholdProcessor.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessorCore.Processors
{
using System.Threading.Tasks;
/// <summary>
/// An <see cref="IImageProcessor"/> to perform binary threshold filtering against an
/// <see cref="Image"/>. The image will be converted to greyscale before thresholding
/// occurs.
/// </summary>
public class ThresholdProcessor<T, TP> : ImageProcessor<T, TP>
where T : IPackedVector<TP>
where TP : struct
{
/// <summary>
/// Initializes a new instance of the <see cref="ThresholdProcessor"/> class.
/// </summary>
/// <param name="threshold">The threshold to split the image. Must be between 0 and 1.</param>
/// <exception cref="ArgumentException">
/// <paramref name="threshold"/> is less than 0 or is greater than 1.
/// </exception>
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());
}
/// <summary>
/// Gets the threshold value.
/// </summary>
public float Value { get; }
/// <summary>
/// The color to use for pixels that are above the threshold.
/// </summary>
public T UpperColor { get; set; }
/// <summary>
/// The color to use for pixels that fall below the threshold.
/// </summary>
public T LowerColor { get; set; }
/// <inheritdoc/>
protected override void OnApply(ImageBase<T, TP> target, ImageBase<T, TP> source, Rectangle targetRectangle, Rectangle sourceRectangle)
{
new GreyscaleBt709Processor<T, TP>().Apply(source, source, sourceRectangle);
}
/// <inheritdoc/>
protected override void Apply(ImageBase<T, TP> target, ImageBase<T, TP> 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<T, TP> sourcePixels = source.Lock())
using (IPixelAccessor<T, TP> 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();
}
});
}
}
}
}

74
src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorMatrixFilter.cs

@ -0,0 +1,74 @@
// <copyright file="ColorMatrixFilter.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessorCore.Processors
{
using System.Numerics;
using System.Threading.Tasks;
/// <summary>
/// The color matrix filter.
/// </summary>
public abstract class ColorMatrixFilter<T, TP> : ImageProcessor<T, TP>, IColorMatrixFilter<T, TP>
where T : IPackedVector<TP>
where TP : struct
{
/// <inheritdoc/>
public abstract Matrix4x4 Matrix { get; }
/// <inheritdoc/>
public virtual bool Compand => true;
/// <inheritdoc/>
protected override void Apply(ImageBase<T, TP> target, ImageBase<T, TP> source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY)
{
int startX = sourceRectangle.X;
int endX = sourceRectangle.Right;
Matrix4x4 matrix = this.Matrix;
using (IPixelAccessor<T, TP> sourcePixels = source.Lock())
using (IPixelAccessor<T, TP> 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();
});
}
}
/// <summary>
/// Applies the color matrix against the given color.
/// </summary>
/// <param name="color">The source color.</param>
/// <param name="matrix">The matrix.</param>
/// <returns>
/// The <see cref="Color"/>.
/// </returns>
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;
}
}
}

32
src/ImageProcessorCore/Filters/Processors/ColorMatrix/GreyscaleBt709Processor.cs

@ -0,0 +1,32 @@
// <copyright file="GreyscaleBt709Processor.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessorCore.Processors
{
using System.Numerics;
/// <summary>
/// Converts the colors of the image to greyscale applying the formula as specified by
/// ITU-R Recommendation BT.709 <see href="https://en.wikipedia.org/wiki/Rec._709#Luma_coefficients"/>.
/// </summary>
public class GreyscaleBt709Processor<T, TP> : ColorMatrixFilter<T, TP>
where T : IPackedVector<TP>
where TP : struct
{
/// <inheritdoc/>
public override Matrix4x4 Matrix => new Matrix4x4()
{
M11 = .2126f,
M12 = .2126f,
M13 = .2126f,
M21 = .7152f,
M22 = .7152f,
M23 = .7152f,
M31 = .0722f,
M32 = .0722f,
M33 = .0722f
};
}
}

29
src/ImageProcessorCore/Filters/Processors/ColorMatrix/IColorMatrixFilter.cs

@ -0,0 +1,29 @@
// <copyright file="IColorMatrixFilter.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessorCore.Processors
{
using System.Numerics;
/// <summary>
/// Encapsulates properties and methods for creating processors that utilize a matrix to
/// alter the image pixels.
/// </summary>
public interface IColorMatrixFilter<T, TP> : IImageProcessor<T, TP>
where T : IPackedVector<TP>
where TP : struct
{
/// <summary>
/// Gets the <see cref="Matrix4x4"/> used to alter the image.
/// </summary>
Matrix4x4 Matrix { get; }
/// <summary>
/// Gets a value indicating whether to compress
/// or expand individual pixel colors the value on processing.
/// </summary>
bool Compand { get; }
}
}

118
src/ImageProcessorCore/Filters/Processors/Convolution/Convolution2DFilter.cs

@ -0,0 +1,118 @@
// <copyright file="Convolution2DFilter.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessorCore.Processors
{
using System;
using System.Numerics;
using System.Threading.Tasks;
/// <summary>
/// Defines a filter that uses two one-dimensional matrices to perform convolution against an image.
/// </summary>
public abstract class Convolution2DFilter<T, TP> : ImageProcessor<T, TP>
where T : IPackedVector<TP>
where TP : struct
{
/// <summary>
/// Gets the horizontal gradient operator.
/// </summary>
public abstract float[,] KernelX { get; }
/// <summary>
/// Gets the vertical gradient operator.
/// </summary>
public abstract float[,] KernelY { get; }
/// <inheritdoc/>
protected override void Apply(ImageBase<T, TP> target, ImageBase<T, TP> 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<T, TP> sourcePixels = source.Lock())
using (IPixelAccessor<T, TP> 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();
}
});
}
}
}
}

95
src/ImageProcessorCore/Filters/Processors/Convolution/ConvolutionFilter.cs

@ -0,0 +1,95 @@
// <copyright file="ConvolutionFilter.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessorCore.Processors
{
using System.Numerics;
using System.Threading.Tasks;
/// <summary>
/// Defines a filter that uses a 2 dimensional matrix to perform convolution against an image.
/// </summary>
public abstract class ConvolutionFilter<T, TP> : ImageProcessor<T, TP>
where T : IPackedVector<TP>
where TP : struct
{
/// <summary>
/// Gets the 2d gradient operator.
/// </summary>
public abstract float[,] KernelXY { get; }
/// <inheritdoc/>
protected override void Apply(ImageBase<T, TP> target, ImageBase<T, TP> 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<T, TP> sourcePixels = source.Lock())
using (IPixelAccessor<T, TP> 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();
}
});
}
}
}
}

28
src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/EdgeDetector2DFilter.cs

@ -0,0 +1,28 @@
// <copyright file="EdgeDetector2DFilter.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessorCore.Processors
{
/// <summary>
/// Defines a filter that detects edges within an image using two
/// one-dimensional matrices.
/// </summary>
public abstract class EdgeDetector2DFilter<T, TP> : Convolution2DFilter<T, TP>, IEdgeDetectorFilter<T, TP>
where T : IPackedVector<TP>
where TP : struct
{
/// <inheritdoc/>
public bool Greyscale { get; set; }
/// <inheritdoc/>
protected override void OnApply(ImageBase<T, TP> target, ImageBase<T, TP> source, Rectangle targetRectangle, Rectangle sourceRectangle)
{
if (this.Greyscale)
{
new GreyscaleBt709Processor<T, TP>().Apply(source, source, sourceRectangle);
}
}
}
}

28
src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/EdgeDetectorFilter.cs

@ -0,0 +1,28 @@
// <copyright file="EdgeDetectorFilter.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessorCore.Processors
{
/// <summary>
/// Defines a filter that detects edges within an image using a single
/// two dimensional matrix.
/// </summary>
public abstract class EdgeDetectorFilter<T, TP> : ConvolutionFilter<T, TP>, IEdgeDetectorFilter<T, TP>
where T : IPackedVector<TP>
where TP : struct
{
/// <inheritdoc/>
public bool Greyscale { get; set; }
/// <inheritdoc/>
protected override void OnApply(ImageBase<T, TP> target, ImageBase<T, TP> source, Rectangle targetRectangle, Rectangle sourceRectangle)
{
if (this.Greyscale)
{
new GreyscaleBt709Processor<T, TP>().Apply(source, source, sourceRectangle);
}
}
}
}

21
src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/IEdgeDetectorFilter.cs

@ -0,0 +1,21 @@
// <copyright file="IEdgeDetectorFilter.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessorCore.Processors
{
/// <summary>
/// Provides properties and methods allowing the detection of edges within an image.
/// </summary>
public interface IEdgeDetectorFilter<T, TP> : IImageProcessor<T, TP>
where T : IPackedVector<TP>
where TP : struct
{
/// <summary>
/// Gets or sets a value indicating whether to convert the
/// image to greyscale before performing edge detection.
/// </summary>
bool Greyscale { get; set; }
}
}

32
src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/SobelProcessor.cs

@ -0,0 +1,32 @@
// <copyright file="SobelProcessor.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessorCore.Processors
{
/// <summary>
/// The Sobel operator filter.
/// <see href="http://en.wikipedia.org/wiki/Sobel_operator"/>
/// </summary>
public class SobelProcessor<T, TP> : EdgeDetector2DFilter<T, TP>
where T : IPackedVector<TP>
where TP : struct
{
/// <inheritdoc/>
public override float[,] KernelX => new float[,]
{
{ -1, 0, 1 },
{ -2, 0, 2 },
{ -1, 0, 1 }
};
/// <inheritdoc/>
public override float[,] KernelY => new float[,]
{
{ 1, 2, 1 },
{ 0, 0, 0 },
{ -1, -2, -1 }
};
}
}

18
src/ImageProcessorCore/Image/IImageProcessor.cs

@ -17,7 +17,11 @@ namespace ImageProcessorCore.Processors
/// <summary>
/// Encapsulates methods to alter the pixels of an image.
/// </summary>
public interface IImageProcessor
/// <typeparam name="T">The pixel format.</typeparam>
/// <typeparam name="TP">The packed format. <example>long, float.</example></typeparam>
public interface IImageProcessor<T, TP>
where T : IPackedVector<TP>
where TP : struct
{
/// <summary>
/// Event fires when each row of the source image has been processed.
@ -36,8 +40,6 @@ namespace ImageProcessorCore.Processors
/// <summary>
/// Applies the process to the specified portion of the specified <see cref="ImageBase{T, TP}"/>.
/// </summary>
/// <typeparam name="T">The pixel format.</typeparam>
/// <typeparam name="TP">The packed format. <example>long, float.</example></typeparam>
/// <param name="target">Target image to apply the process to.</param>
/// <param name="source">The source image. Cannot be null.</param>
/// <param name="sourceRectangle">
@ -53,16 +55,12 @@ namespace ImageProcessorCore.Processors
/// <exception cref="System.ArgumentException">
/// <paramref name="sourceRectangle"/> doesnt fit the dimension of the image.
/// </exception>
void Apply<T, TP>(ImageBase<T, TP> target, ImageBase<T, TP> source, Rectangle sourceRectangle)
where T : IPackedVector<TP>
where TP : struct;
void Apply(ImageBase<T, TP> target, ImageBase<T, TP> source, Rectangle sourceRectangle);
/// <summary>
/// Applies the process to the specified portion of the specified <see cref="ImageBase{T, TP}"/> at the specified
/// location and with the specified size.
/// </summary>
/// <typeparam name="T">The pixel format.</typeparam>
/// <typeparam name="TP">The packed format. <example>long, float.</example></typeparam>
/// <param name="target">Target image to apply the process to.</param>
/// <param name="source">The source image. Cannot be null.</param>
/// <param name="width">The target width.</param>
@ -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.
/// </remarks>
void Apply<T, TP>(ImageBase<T, TP> target, ImageBase<T, TP> source, int width, int height, Rectangle targetRectangle, Rectangle sourceRectangle)
where T : IPackedVector<TP>
where TP : struct;
void Apply(ImageBase<T, TP> target, ImageBase<T, TP> source, int width, int height, Rectangle targetRectangle, Rectangle sourceRectangle);
}
}

5
src/ImageProcessorCore/Image/Image.cs

@ -40,7 +40,7 @@ namespace ImageProcessorCore
/// </summary>
public Image()
{
this.CurrentImageFormat = Bootstrapper.Instance.ImageFormats.First(f => f.GetType() == typeof(BmpFormat));
this.CurrentImageFormat = Bootstrapper.Instance.ImageFormats.First(f => f.GetType() == typeof(PngFormat));
}
/// <summary>
@ -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));
}
/// <summary>

10
src/ImageProcessorCore/Image/ImageExtensions.cs

@ -69,7 +69,7 @@ namespace ImageProcessorCore
/// <param name="stream">The stream to save the image to.</param>
/// <param name="quality">The quality to save the image to representing the number of colors. Between 1 and 256.</param>
/// <exception cref="ArgumentNullException">Thrown if the stream is null.</exception>
public static void SaveAsGif<T, TP>(this ImageBase<T, TP> source, Stream stream, int quality = 256)
internal static void SaveAsGif<T, TP>(this ImageBase<T, TP> source, Stream stream, int quality = 256)
where T : IPackedVector<TP>
where TP : struct
=> new GifEncoder { Quality = quality }.Encode(source, stream);
@ -83,7 +83,7 @@ namespace ImageProcessorCore
/// <param name="source">The image this method extends.</param>
/// <param name="processor">The processor to apply to the image.</param>
/// <returns>The <see cref="Image{T, TP}"/>.</returns>
public static Image<T, TP> Process<T, TP>(this Image<T, TP> source, IImageProcessor processor)
internal static Image<T, TP> Process<T, TP>(this Image<T, TP> source, IImageProcessor<T, TP> processor)
where T : IPackedVector<TP>
where TP : struct
{
@ -102,7 +102,7 @@ namespace ImageProcessorCore
/// </param>
/// <param name="processor">The processors to apply to the image.</param>
/// <returns>The <see cref="Image{T, TP}"/>.</returns>
public static Image<T, TP> Process<T, TP>(this Image<T, TP> source, Rectangle sourceRectangle, IImageProcessor processor)
internal static Image<T, TP> Process<T, TP>(this Image<T, TP> source, Rectangle sourceRectangle, IImageProcessor<T, TP> processor)
where T : IPackedVector<TP>
where TP : struct
{
@ -122,7 +122,7 @@ namespace ImageProcessorCore
/// <param name="height">The target image height.</param>
/// <param name="sampler">The processor to apply to the image.</param>
/// <returns>The <see cref="Image{T, TP}"/>.</returns>
public static Image<T, TP> Process<T, TP>(this Image<T, TP> source, int width, int height, IImageSampler sampler)
internal static Image<T, TP> Process<T, TP>(this Image<T, TP> source, int width, int height, IImageSampler<T, TP> sampler)
where T : IPackedVector<TP>
where TP : struct
{
@ -149,7 +149,7 @@ namespace ImageProcessorCore
/// </param>
/// <param name="sampler">The processor to apply to the image.</param>
/// <returns>The <see cref="Image{T, TP}"/>.</returns>
public static Image<T, TP> Process<T, TP>(this Image<T, TP> source, int width, int height, Rectangle sourceRectangle, Rectangle targetRectangle, IImageSampler sampler)
internal static Image<T, TP> Process<T, TP>(this Image<T, TP> source, int width, int height, Rectangle sourceRectangle, Rectangle targetRectangle, IImageSampler<T, TP> sampler)
where T : IPackedVector<TP>
where TP : struct
{

28
src/ImageProcessorCore/ImageProcessor.cs

@ -12,7 +12,9 @@ namespace ImageProcessorCore.Processors
/// <summary>
/// Allows the application of processors to images.
/// </summary>
public abstract class ImageProcessor : IImageProcessor
public abstract class ImageProcessor<T, TP> : IImageProcessor<T, TP>
where T : IPackedVector<TP>
where TP : struct
{
/// <inheritdoc/>
public event ProgressEventHandler OnProgress;
@ -31,9 +33,7 @@ namespace ImageProcessorCore.Processors
public virtual ParallelOptions ParallelOptions { get; set; } = Bootstrapper.Instance.ParallelOptions;
/// <inheritdoc/>
public void Apply<T, TP>(ImageBase<T, TP> target, ImageBase<T, TP> source, Rectangle sourceRectangle)
where T : IPackedVector<TP>
where TP : struct
public void Apply(ImageBase<T, TP> target, ImageBase<T, TP> source, Rectangle sourceRectangle)
{
try
{
@ -54,9 +54,7 @@ namespace ImageProcessorCore.Processors
}
/// <inheritdoc/>
public void Apply<T, TP>(ImageBase<T, TP> target, ImageBase<T, TP> source, int width, int height, Rectangle targetRectangle = default(Rectangle), Rectangle sourceRectangle = default(Rectangle))
where T : IPackedVector<TP>
where TP : struct
public void Apply(ImageBase<T, TP> target, ImageBase<T, TP> source, int width, int height, Rectangle targetRectangle = default(Rectangle), Rectangle sourceRectangle = default(Rectangle))
{
try
{
@ -103,9 +101,7 @@ namespace ImageProcessorCore.Processors
/// <param name="sourceRectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to draw.
/// </param>
protected virtual void OnApply<T, TP>(ImageBase<T, TP> target, ImageBase<T, TP> source, Rectangle targetRectangle, Rectangle sourceRectangle)
where T : IPackedVector<TP>
where TP : struct
protected virtual void OnApply(ImageBase<T, TP> target, ImageBase<T, TP> source, Rectangle targetRectangle, Rectangle sourceRectangle)
{
}
@ -113,8 +109,6 @@ namespace ImageProcessorCore.Processors
/// Applies the process to the specified portion of the specified <see cref="ImageBase{T, TP}"/> at the specified location
/// and with the specified size.
/// </summary>
/// <typeparam name="T">The pixel format.</typeparam>
/// <typeparam name="TP">The packed format. <example>long, float.</example></typeparam>
/// <param name="target">Target image to apply the process to.</param>
/// <param name="source">The source image. Cannot be null.</param>
/// <param name="targetRectangle">
@ -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.
/// </remarks>
protected abstract void Apply<T, TP>(ImageBase<T, TP> target, ImageBase<T, TP> source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY)
where T : IPackedVector<TP>
where TP : struct;
protected abstract void Apply(ImageBase<T, TP> target, ImageBase<T, TP> source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY);
/// <summary>
/// This method is called after the process is applied to prepare the processor.
/// </summary>
/// <typeparam name="T">The pixel format.</typeparam>
/// <typeparam name="TP">The packed format. <example>long, float.</example></typeparam>
/// <param name="target">Target image to apply the process to.</param>
/// <param name="source">The source image. Cannot be null.</param>
/// <param name="targetRectangle">
@ -148,9 +138,7 @@ namespace ImageProcessorCore.Processors
/// <param name="sourceRectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to draw.
/// </param>
protected virtual void AfterApply<T, TP>(ImageBase<T, TP> target, ImageBase<T, TP> source, Rectangle targetRectangle, Rectangle sourceRectangle)
where T : IPackedVector<TP>
where TP : struct
protected virtual void AfterApply(ImageBase<T, TP> target, ImageBase<T, TP> source, Rectangle targetRectangle, Rectangle sourceRectangle)
{
}

2
src/ImageProcessorCore/Samplers/Crop.cs

@ -60,7 +60,7 @@ namespace ImageProcessorCore
source = source.Resize(sourceRectangle.Width, sourceRectangle.Height);
}
CropProcessor processor = new CropProcessor();
CropProcessor<T, TP> processor = new CropProcessor<T, TP>();
processor.OnProgress += progressHandler;
try

41
src/ImageProcessorCore/Samplers/EntropyCrop.cs

@ -0,0 +1,41 @@
// <copyright file="EntropyCrop.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>-------------------------------------------------------------------------------------------------------------------
namespace ImageProcessorCore
{
using Processors;
/// <summary>
/// Extension methods for the <see cref="Image"/> type.
/// </summary>
public static partial class ImageExtensions
{
/// <summary>
/// Crops an image to the area of greatest entropy.
/// </summary>
/// <typeparam name="T">The pixel format.</typeparam>
/// <typeparam name="TP">The packed format. <example>long, float.</example></typeparam>
/// <param name="source">The image to crop.</param>
/// <param name="threshold">The threshold for entropic density.</param>
/// <param name="progressHandler">A delegate which is called as progress is made processing the image.</param>
/// <returns>The <see cref="Image"/></returns>
public static Image<T, TP> EntropyCrop<T, TP>(this Image<T, TP> source, float threshold = .5f, ProgressEventHandler progressHandler = null)
where T : IPackedVector<TP>
where TP : struct
{
EntropyCropProcessor<T, TP> processor = new EntropyCropProcessor<T, TP>(threshold);
processor.OnProgress += progressHandler;
try
{
return source.Process(source.Width, source.Height, source.Bounds, Rectangle.Empty, processor);
}
finally
{
processor.OnProgress -= progressHandler;
}
}
}
}

6
src/ImageProcessorCore/Samplers/Processors/CropProcessor.cs

@ -10,10 +10,12 @@ namespace ImageProcessorCore.Processors
/// <summary>
/// Provides methods to allow the cropping of an image.
/// </summary>
public class CropProcessor : ImageSampler
public class CropProcessor<T, TP> : ImageSampler<T, TP>
where T : IPackedVector<TP>
where TP : struct
{
/// <inheritdoc/>
protected override void Apply<T, TP>(ImageBase<T, TP> target, ImageBase<T, TP> source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY)
protected override void Apply(ImageBase<T, TP> target, ImageBase<T, TP> source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY)
{
int startX = targetRectangle.X;
int endX = targetRectangle.Right;

106
src/ImageProcessorCore/Samplers/Processors/EntropyCropProcessor.cs

@ -0,0 +1,106 @@
// <copyright file="EntropyCropProcessor.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessorCore.Processors
{
using System;
using System.Threading.Tasks;
/// <summary>
/// Provides methods to allow the cropping of an image to preserve areas of highest
/// entropy.
/// </summary>
public class EntropyCropProcessor<T, TP> : ImageSampler<T, TP>
where T : IPackedVector<TP>
where TP : struct
{
/// <summary>
/// The rectangle for cropping
/// </summary>
private Rectangle cropRectangle;
/// <summary>
/// Initializes a new instance of the <see cref="EntropyCropProcessor"/> class.
/// </summary>
/// <param name="threshold">The threshold to split the image. Must be between 0 and 1.</param>
/// <exception cref="ArgumentException">
/// <paramref name="threshold"/> is less than 0 or is greater than 1.
/// </exception>
public EntropyCropProcessor(float threshold)
{
Guard.MustBeBetweenOrEqualTo(threshold, 0, 1, nameof(threshold));
this.Value = threshold;
}
/// <summary>
/// Gets the threshold value.
/// </summary>
public float Value { get; }
/// <inheritdoc/>
protected override void OnApply(ImageBase<T, TP> target, ImageBase<T, TP> source, Rectangle targetRectangle, Rectangle sourceRectangle)
{
ImageBase<T, TP> temp = new Image<T, TP>(source.Width, source.Height);
// Detect the edges.
new SobelProcessor<T, TP>().Apply(temp, source, sourceRectangle);
// Apply threshold binarization filter.
new ThresholdProcessor<T, TP>(.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;
}
/// <inheritdoc/>
protected override void Apply(ImageBase<T, TP> target, ImageBase<T, TP> 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<T, TP> sourcePixels = source.Lock())
using (IPixelAccessor<T, TP> 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();
});
}
}
/// <inheritdoc/>
protected override void AfterApply(ImageBase<T, TP> target, ImageBase<T, TP> source, Rectangle targetRectangle, Rectangle sourceRectangle)
{
// Copy the pixels over.
if (source.Bounds == target.Bounds)
{
target.ClonePixels(target.Width, target.Height, source.Pixels);
}
}
}
}

4
src/ImageProcessorCore/Samplers/Processors/IImageSampler.cs

@ -8,7 +8,9 @@ namespace ImageProcessorCore.Processors
/// <summary>
/// Acts as a marker for generic parameters that require an image sampler.
/// </summary>
public interface IImageSampler : IImageProcessor
public interface IImageSampler<T, TP> : IImageProcessor<T, TP>
where T : IPackedVector<TP>
where TP : struct
{
/// <summary>
/// Gets or sets a value indicating whether to compress

4
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.
/// </summary>
public abstract class ImageSampler : ImageProcessor, IImageSampler
public abstract class ImageSampler<T, TP> : ImageProcessor<T, TP>, IImageSampler<T, TP>
where T : IPackedVector<TP>
where TP : struct
{
/// <inheritdoc/>
public virtual bool Compand { get; set; } = false;

53
src/ImageProcessorCore/Samplers/Processors/Matrix3x2Processor.cs

@ -0,0 +1,53 @@
// <copyright file="Matrix3x2Processor.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessorCore.Processors
{
using System.Numerics;
/// <summary>
/// Provides methods to transform an image using a <see cref="Matrix3x2"/>.
/// </summary>
public abstract class Matrix3x2Processor<T, TP> : ImageSampler<T, TP>
where T : IPackedVector<TP>
where TP : struct
{
/// <summary>
/// Creates a new target to contain the results of the matrix transform.
/// </summary>
/// <typeparam name="T">The pixel format.</typeparam>
/// <typeparam name="TP">The packed format. <example>long, float.</example></typeparam>
/// <param name="target">Target image to apply the process to.</param>
/// <param name="sourceRectangle">The source rectangle.</param>
/// <param name="processMatrix">The processing matrix.</param>
protected static void CreateNewTarget(ImageBase<T, TP> 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]);
}
}
/// <summary>
/// Gets a transform matrix adjusted to center upon the target image bounds.
/// </summary>
/// <typeparam name="T">The pixel format.</typeparam>
/// <typeparam name="TP">The packed format. <example>long, float.</example></typeparam>
/// <param name="target">Target image to apply the process to.</param>
/// <param name="source">The source image.</param>
/// <param name="matrix">The transform matrix.</param>
/// <returns>
/// The <see cref="Matrix3x2"/>.
/// </returns>
protected static Matrix3x2 GetCenteredMatrix(ImageBase<T, TP> target, ImageBase<T, TP> 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;
}
}
}

10
src/ImageProcessorCore/Samplers/Processors/ResizeProcessor.cs

@ -12,7 +12,9 @@ namespace ImageProcessorCore.Processors
/// <summary>
/// Provides methods that allow the resizing of images using various algorithms.
/// </summary>
public class ResizeProcessor : ImageSampler
public class ResizeProcessor<T, TP> : ImageSampler<T, TP>
where T : IPackedVector<TP>
where TP : struct
{
/// <summary>
/// Initializes a new instance of the <see cref="ResizeProcessor"/> class.
@ -43,7 +45,7 @@ namespace ImageProcessorCore.Processors
protected Weights[] VerticalWeights { get; set; }
/// <inheritdoc/>
protected override void OnApply<T, TP>(ImageBase<T, TP> target, ImageBase<T, TP> source, Rectangle targetRectangle, Rectangle sourceRectangle)
protected override void OnApply(ImageBase<T, TP> target, ImageBase<T, TP> source, Rectangle targetRectangle, Rectangle sourceRectangle)
{
if (!(this.Sampler is NearestNeighborResampler))
{
@ -53,7 +55,7 @@ namespace ImageProcessorCore.Processors
}
/// <inheritdoc/>
protected override void Apply<T, TP>(ImageBase<T, TP> target, ImageBase<T, TP> source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY)
protected override void Apply(ImageBase<T, TP> target, ImageBase<T, TP> 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
}
/// <inheritdoc/>
protected override void AfterApply<T, TP>(ImageBase<T, TP> target, ImageBase<T, TP> source, Rectangle targetRectangle, Rectangle sourceRectangle)
protected override void AfterApply(ImageBase<T, TP> target, ImageBase<T, TP> source, Rectangle targetRectangle, Rectangle sourceRectangle)
{
// Copy the pixels over.
if (source.Bounds == target.Bounds && sourceRectangle == targetRectangle)

243
src/ImageProcessorCore/Samplers/Processors/RotateFlipProcessor.cs

@ -0,0 +1,243 @@
// <copyright file="RotateFlipProcessor.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessorCore.Processors
{
using System;
using System.Threading.Tasks;
/// <summary>
/// Provides methods that allow the rotation and flipping of an image around its center point.
/// </summary>
public class RotateFlipProcessor<T, TP> : ImageSampler<T, TP>
where T : IPackedVector<TP>
where TP : struct
{
/// <summary>
/// Initializes a new instance of the <see cref="RotateFlipProcessor"/> class.
/// </summary>
/// <param name="rotateType">The <see cref="RotateType"/> used to perform rotation.</param>
/// <param name="flipType">The <see cref="FlipType"/> used to perform flipping.</param>
public RotateFlipProcessor(RotateType rotateType, FlipType flipType)
{
this.RotateType = rotateType;
this.FlipType = flipType;
}
/// <summary>
/// Gets the <see cref="FlipType"/> used to perform flipping.
/// </summary>
public FlipType FlipType { get; }
/// <summary>
/// Gets the <see cref="RotateType"/> used to perform rotation.
/// </summary>
public RotateType RotateType { get; }
/// <inheritdoc/>
protected override void Apply(ImageBase<T, TP> target, ImageBase<T, TP> 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;
}
}
/// <summary>
/// Rotates the image 270 degrees clockwise at the centre point.
/// </summary>
/// <typeparam name="T">The pixel format.</typeparam>
/// <typeparam name="TP">The packed format. <example>long, float.</example></typeparam>
/// <param name="target">The target image.</param>
/// <param name="source">The source image.</param>
private void Rotate270(ImageBase<T, TP> target, ImageBase<T, TP> source)
{
int width = source.Width;
int height = source.Height;
Image<T, TP> temp = new Image<T, TP>(height, width);
using (IPixelAccessor<T, TP> sourcePixels = source.Lock())
using (IPixelAccessor<T, TP> 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);
}
/// <summary>
/// Rotates the image 180 degrees clockwise at the centre point.
/// </summary>
/// <typeparam name="T">The pixel format.</typeparam>
/// <typeparam name="TP">The packed format. <example>long, float.</example></typeparam>
/// <param name="target">The target image.</param>
/// <param name="source">The source image.</param>
private void Rotate180(ImageBase<T, TP> target, ImageBase<T, TP> source)
{
int width = source.Width;
int height = source.Height;
using (IPixelAccessor<T, TP> sourcePixels = source.Lock())
using (IPixelAccessor<T, TP> 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();
});
}
}
/// <summary>
/// Rotates the image 90 degrees clockwise at the centre point.
/// </summary>
/// <typeparam name="T">The pixel format.</typeparam>
/// <typeparam name="TP">The packed format. <example>long, float.</example></typeparam>
/// <param name="target">The target image.</param>
/// <param name="source">The source image.</param>
private void Rotate90(ImageBase<T, TP> target, ImageBase<T, TP> source)
{
int width = source.Width;
int height = source.Height;
Image<T, TP> temp = new Image<T, TP>(height, width);
using (IPixelAccessor<T, TP> sourcePixels = source.Lock())
using (IPixelAccessor<T, TP> 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);
}
/// <summary>
/// Swaps the image at the X-axis, which goes horizontally through the middle
/// at half the height of the image.
/// </summary>
/// <typeparam name="T">The pixel format.</typeparam>
/// <typeparam name="TP">The packed format. <example>long, float.</example></typeparam>
/// <param name="target">Target image to apply the process to.</param>
private void FlipX(ImageBase<T, TP> target)
{
int width = target.Width;
int height = target.Height;
int halfHeight = (int)Math.Ceiling(target.Height * .5);
Image<T, TP> temp = new Image<T, TP>(width, height);
temp.ClonePixels(width, height, target.Pixels);
using (IPixelAccessor<T, TP> targetPixels = target.Lock())
using (IPixelAccessor<T, TP> 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();
});
}
}
/// <summary>
/// Swaps the image at the Y-axis, which goes vertically through the middle
/// at half of the width of the image.
/// </summary>
/// <typeparam name="T">The pixel format.</typeparam>
/// <typeparam name="TP">The packed format. <example>long, float.</example></typeparam>
/// <param name="target">Target image to apply the process to.</param>
private void FlipY(ImageBase<T, TP> target)
{
int width = target.Width;
int height = target.Height;
int halfWidth = (int)Math.Ceiling(width / 2d);
Image<T, TP> temp = new Image<T, TP>(width, height);
temp.ClonePixels(width, height, target.Pixels);
using (IPixelAccessor<T, TP> targetPixels = target.Lock())
using (IPixelAccessor<T, TP> 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();
});
}
}
}
}

70
src/ImageProcessorCore/Samplers/Processors/RotateProcessor.cs

@ -0,0 +1,70 @@
// <copyright file="RotateProcessor.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessorCore.Processors
{
using System.Numerics;
using System.Threading.Tasks;
/// <summary>
/// Provides methods that allow the rotating of images.
/// </summary>
public class RotateProcessor<T, TP> : Matrix3x2Processor<T, TP>
where T : IPackedVector<TP>
where TP : struct
{
/// <summary>
/// The tranform matrix to apply.
/// </summary>
private Matrix3x2 processMatrix;
/// <summary>
/// Gets or sets the angle of processMatrix in degrees.
/// </summary>
public float Angle { get; set; }
/// <summary>
/// Gets or sets a value indicating whether to expand the canvas to fit the rotated image.
/// </summary>
public bool Expand { get; set; } = true;
/// <inheritdoc/>
protected override void OnApply(ImageBase<T, TP> target, ImageBase<T, TP> source, Rectangle targetRectangle, Rectangle sourceRectangle)
{
processMatrix = Point.CreateRotation(new Point(0, 0), -this.Angle);
if (this.Expand)
{
CreateNewTarget(target, sourceRectangle, processMatrix);
}
}
/// <inheritdoc/>
protected override void Apply(ImageBase<T, TP> target, ImageBase<T, TP> source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY)
{
Matrix3x2 matrix = GetCenteredMatrix(target, source, this.processMatrix);
using (IPixelAccessor<T, TP> sourcePixels = source.Lock())
using (IPixelAccessor<T, TP> 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();
});
}
}
}
}

75
src/ImageProcessorCore/Samplers/Processors/SkewProcessor.cs

@ -0,0 +1,75 @@
// <copyright file="SkewProcessor.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessorCore.Processors
{
using System.Numerics;
using System.Threading.Tasks;
/// <summary>
/// Provides methods that allow the skewing of images.
/// </summary>
public class SkewProcessor<T, TP> : Matrix3x2Processor<T, TP>
where T : IPackedVector<TP>
where TP : struct
{
/// <summary>
/// The tranform matrix to apply.
/// </summary>
private Matrix3x2 processMatrix;
/// <summary>
/// Gets or sets the angle of rotation along the x-axis in degrees.
/// </summary>
public float AngleX { get; set; }
/// <summary>
/// Gets or sets the angle of rotation along the y-axis in degrees.
/// </summary>
public float AngleY { get; set; }
/// <summary>
/// Gets or sets a value indicating whether to expand the canvas to fit the skewed image.
/// </summary>
public bool Expand { get; set; } = true;
/// <inheritdoc/>
protected override void OnApply(ImageBase<T, TP> target, ImageBase<T, TP> 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);
}
}
/// <inheritdoc/>
protected override void Apply(ImageBase<T, TP> target, ImageBase<T, TP> source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY)
{
Matrix3x2 matrix = GetCenteredMatrix(target, source, this.processMatrix);
using (IPixelAccessor<T, TP> sourcePixels = source.Lock())
using (IPixelAccessor<T, TP> 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();
});
}
}
}
}

2
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<T, TP> processor = new ResizeProcessor<T, TP>(sampler) { Compand = compand };
processor.OnProgress += progressHandler;
try

58
src/ImageProcessorCore/Samplers/Rotate.cs

@ -0,0 +1,58 @@
// <copyright file="Rotate.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessorCore
{
using Processors;
/// <summary>
/// Extension methods for the <see cref="Image"/> type.
/// </summary>
public static partial class ImageExtensions
{
/// <summary>
/// Rotates an image by the given angle in degrees, expanding the image to fit the rotated result.
/// </summary>
/// <typeparam name="T">The pixel format.</typeparam>
/// <typeparam name="TP">The packed format. <example>long, float.</example></typeparam>
/// <param name="source">The image to rotate.</param>
/// <param name="degrees">The angle in degrees to perform the rotation.</param>
/// <param name="progressHandler">A delegate which is called as progress is made processing the image.</param>
/// <returns>The <see cref="Image"/></returns>
public static Image<T, TP> Rotate<T, TP>(this Image<T, TP> source, float degrees, ProgressEventHandler progressHandler = null)
where T : IPackedVector<TP>
where TP : struct
{
return Rotate(source, degrees, true, progressHandler);
}
/// <summary>
/// Rotates an image by the given angle in degrees.
/// </summary>
/// <typeparam name="T">The pixel format.</typeparam>
/// <typeparam name="TP">The packed format. <example>long, float.</example></typeparam>
/// <param name="source">The image to rotate.</param>
/// <param name="degrees">The angle in degrees to perform the rotation.</param>
/// <param name="expand">Whether to expand the image to fit the rotated result.</param>
/// <param name="progressHandler">A delegate which is called as progress is made processing the image.</param>
/// <returns>The <see cref="Image"/></returns>
public static Image<T, TP> Rotate<T, TP>(this Image<T, TP> source, float degrees, bool expand, ProgressEventHandler progressHandler = null)
where T : IPackedVector<TP>
where TP : struct
{
RotateProcessor<T, TP> processor = new RotateProcessor<T, TP> { Angle = degrees, Expand = expand };
processor.OnProgress += progressHandler;
try
{
return source.Process(source.Width, source.Height, source.Bounds, source.Bounds, processor);
}
finally
{
processor.OnProgress -= progressHandler;
}
}
}
}

42
src/ImageProcessorCore/Samplers/RotateFlip.cs

@ -0,0 +1,42 @@
// <copyright file="RotateFlip.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessorCore
{
using Processors;
/// <summary>
/// Extension methods for the <see cref="Image"/> type.
/// </summary>
public static partial class ImageExtensions
{
/// <summary>
/// Rotates and flips an image by the given instructions.
/// </summary>
/// <typeparam name="T">The pixel format.</typeparam>
/// <typeparam name="TP">The packed format. <example>long, float.</example></typeparam>
/// <param name="source">The image to rotate, flip, or both.</param>
/// <param name="rotateType">The <see cref="RotateType"/> to perform the rotation.</param>
/// <param name="flipType">The <see cref="FlipType"/> to perform the flip.</param>
/// <param name="progressHandler">A delegate which is called as progress is made processing the image.</param>
/// <returns>The <see cref="Image"/></returns>
public static Image<T, TP> RotateFlip<T, TP>(this Image<T, TP> source, RotateType rotateType, FlipType flipType, ProgressEventHandler progressHandler = null)
where T : IPackedVector<TP>
where TP : struct
{
RotateFlipProcessor<T, TP> processor = new RotateFlipProcessor<T, TP>(rotateType, flipType);
processor.OnProgress += progressHandler;
try
{
return source.Process(source.Width, source.Height, source.Bounds, source.Bounds, processor);
}
finally
{
processor.OnProgress -= progressHandler;
}
}
}
}

60
src/ImageProcessorCore/Samplers/Skew.cs

@ -0,0 +1,60 @@
// <copyright file="Skew.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessorCore
{
using Processors;
/// <summary>
/// Extension methods for the <see cref="Image"/> type.
/// </summary>
public static partial class ImageExtensions
{
/// <summary>
/// Skews an image by the given angles in degrees, expanding the image to fit the skewed result.
/// </summary>
/// <typeparam name="T">The pixel format.</typeparam>
/// <typeparam name="TP">The packed format. <example>long, float.</example></typeparam>
/// <param name="source">The image to skew.</param>
/// <param name="degreesX">The angle in degrees to perform the rotation along the x-axis.</param>
/// <param name="degreesY">The angle in degrees to perform the rotation along the y-axis.</param>
/// <param name="progressHandler">A delegate which is called as progress is made processing the image.</param>
/// <returns>The <see cref="Image"/></returns>
public static Image<T, TP> Skew<T, TP>(this Image<T, TP> source, float degreesX, float degreesY, ProgressEventHandler progressHandler = null)
where T : IPackedVector<TP>
where TP : struct
{
return Skew(source, degreesX, degreesY, true, progressHandler);
}
/// <summary>
/// Skews an image by the given angles in degrees.
/// </summary>
/// <typeparam name="T">The pixel format.</typeparam>
/// <typeparam name="TP">The packed format. <example>long, float.</example></typeparam>
/// <param name="source">The image to skew.</param>
/// <param name="degreesX">The angle in degrees to perform the rotation along the x-axis.</param>
/// <param name="degreesY">The angle in degrees to perform the rotation along the y-axis.</param>
/// <param name="expand">Whether to expand the image to fit the skewed result.</param>
/// <param name="progressHandler">A delegate which is called as progress is made processing the image.</param>
/// <returns>The <see cref="Image"/></returns>
public static Image<T, TP> Skew<T, TP>(this Image<T, TP> source, float degreesX, float degreesY, bool expand, ProgressEventHandler progressHandler = null)
where T : IPackedVector<TP>
where TP : struct
{
SkewProcessor<T, TP> processor = new SkewProcessor<T, TP> { 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;
}
}
}
}

3
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());
}
}
}

12
tests/ImageProcessorCore.Tests/FileTestBase.cs

@ -19,17 +19,17 @@ namespace ImageProcessorCore.Tests
/// </summary>
protected static readonly List<string> Files = new List<string>
{
"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
};

721
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<string, IImageSampler> Samplers = new TheoryData<string, IImageSampler>
{
{ "Resize", new ResizeProcessor(new BicubicResampler()) },
//{ "Crop", new CropProcessor() }
};
public static readonly TheoryData<RotateType, FlipType> RotateFlips = new TheoryData<RotateType, FlipType>
{
{ 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);
}
}
}
}
Loading…
Cancel
Save