mirror of https://github.com/SixLabors/ImageSharp
Browse Source
Former-commit-id: c60883d765b1372be2a9ab88f8494dfa3283d8a6 Former-commit-id: 9fbcdac0c826d57eaa2bafdc5c72ff90079e5f51 Former-commit-id: 2e8a4fba64de7233e5ea741122188eb86612a060af/merge-core
34 changed files with 1827 additions and 538 deletions
@ -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 |
||||
|
} |
||||
|
} |
||||
@ -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(); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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 |
||||
|
}; |
||||
|
} |
||||
|
} |
||||
@ -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; } |
||||
|
} |
||||
|
} |
||||
@ -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(); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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(); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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; } |
||||
|
} |
||||
|
} |
||||
@ -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 } |
||||
|
}; |
||||
|
} |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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(); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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(); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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(); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue