diff --git a/README.md b/README.md
index d00b693dd..bf0087d07 100644
--- a/README.md
+++ b/README.md
@@ -92,9 +92,10 @@ git clone https://github.com/JimBobSquarePants/ImageProcessor
- [x] Rectangular Crop
- [ ] Elliptical Crop
- [x] Entropy Crop
-- Rotation
+- Rotation/Skew
- [x] Flip (90, 270, FlipType etc)
- - [x] Rotate by angle
+ - [x] Rotate by angle and center point.
+ - [x] Skew by x/y angles and center point.
- ColorMatrix operations (Uses Matrix4x4)
- [x] BlackWhite
- [x] Greyscale BT709
diff --git a/src/ImageProcessorCore/Numerics/Point.cs b/src/ImageProcessorCore/Numerics/Point.cs
index 12d0dc59b..370592406 100644
--- a/src/ImageProcessorCore/Numerics/Point.cs
+++ b/src/ImageProcessorCore/Numerics/Point.cs
@@ -171,6 +171,21 @@ namespace ImageProcessorCore
return new Point(Vector2.Transform(point.backingVector, Matrix3x2.CreateRotation(radians, origin.backingVector)));
}
+ ///
+ /// Skews a point around a given origin by the specified angles in degrees.
+ ///
+ /// The point to rotate
+ /// The center point to rotate around.
+ /// The x-angle in degrees.
+ /// The y-angle in degrees.
+ ///
+ public static Point Skew(Point point, Point origin, float degreesX, float degreesY)
+ {
+ float radiansX = (float)ImageMaths.DegreesToRadians(degreesX);
+ float radiansY = (float)ImageMaths.DegreesToRadians(degreesY);
+ return new Point(Vector2.Transform(point.backingVector, Matrix3x2.CreateSkew(degreesX, degreesY, origin.backingVector)));
+ }
+
///
public override int GetHashCode()
{
diff --git a/src/ImageProcessorCore/Samplers/ImageSamplerExtensions.cs b/src/ImageProcessorCore/Samplers/ImageSamplerExtensions.cs
index d741ccfd3..f020a2522 100644
--- a/src/ImageProcessorCore/Samplers/ImageSamplerExtensions.cs
+++ b/src/ImageProcessorCore/Samplers/ImageSamplerExtensions.cs
@@ -170,13 +170,26 @@ namespace ImageProcessorCore.Samplers
///
/// Rotates an image by the given angle in degrees.
///
- /// The image to resize.
+ /// The image to rotate.
/// The angle in degrees to perform the rotation.
/// A delegate which is called as progress is made processing the image.
/// The
public static Image Rotate(this Image source, float degrees, ProgressEventHandler progressHandler = null)
{
- Rotate processor = new Rotate { Angle = degrees };
+ return Rotate(source, degrees, Rectangle.Center(source.Bounds), progressHandler);
+ }
+
+ ///
+ /// Rotates an image by the given angle in degrees around the given center point.
+ ///
+ /// The image to rotate.
+ /// The angle in degrees to perform the rotation.
+ /// The center point at which to skew the image.
+ /// A delegate which is called as progress is made processing the image.
+ /// The
+ public static Image Rotate(this Image source, float degrees, Point center, ProgressEventHandler progressHandler = null)
+ {
+ Rotate processor = new Rotate { Angle = degrees, Center = center };
processor.OnProgress += progressHandler;
try
@@ -192,7 +205,7 @@ namespace ImageProcessorCore.Samplers
///
/// Rotates and flips an image by the given instructions.
///
- /// The image to resize.
+ /// The image to rotate, flip, or both.
/// The to perform the rotation.
/// The to perform the flip.
/// A delegate which is called as progress is made processing the image.
@@ -211,5 +224,42 @@ namespace ImageProcessorCore.Samplers
processor.OnProgress -= progressHandler;
}
}
+
+ ///
+ /// Skews an image by the given angles in degrees.
+ ///
+ /// The image to skew.
+ /// The angle in degrees to perform the rotation along the x-axis.
+ /// The angle in degrees to perform the rotation along the y-axis.
+ /// A delegate which is called as progress is made processing the image.
+ /// The
+ public static Image Skew(this Image source, float degreesX, float degreesY, ProgressEventHandler progressHandler = null)
+ {
+ return Skew(source, degreesX, degreesY, Rectangle.Center(source.Bounds), progressHandler);
+ }
+
+ ///
+ /// Skews an image by the given angles in degrees around the given center point.
+ ///
+ /// The image to skew.
+ /// The angle in degrees to perform the rotation along the x-axis.
+ /// The angle in degrees to perform the rotation along the y-axis.
+ /// The center point at which to skew the image.
+ /// A delegate which is called as progress is made processing the image.
+ /// The
+ public static Image Skew(this Image source, float degreesX, float degreesY, Point center, ProgressEventHandler progressHandler = null)
+ {
+ Skew processor = new Skew { AngleX = degreesX, AngleY = degreesY, Center = center };
+ processor.OnProgress += progressHandler;
+
+ try
+ {
+ return source.Process(source.Width, source.Height, source.Bounds, source.Bounds, processor);
+ }
+ finally
+ {
+ processor.OnProgress -= progressHandler;
+ }
+ }
}
}
diff --git a/src/ImageProcessorCore/Samplers/Rotate.cs b/src/ImageProcessorCore/Samplers/Rotate.cs
index 42032ae2a..b85667730 100644
--- a/src/ImageProcessorCore/Samplers/Rotate.cs
+++ b/src/ImageProcessorCore/Samplers/Rotate.cs
@@ -8,7 +8,7 @@ namespace ImageProcessorCore.Samplers
using System.Threading.Tasks;
///
- /// Provides methods that allow the rotating of images using various algorithms.
+ /// Provides methods that allow the rotating of images.
///
public class Rotate : ImageSampler
{
@@ -43,6 +43,11 @@ namespace ImageProcessorCore.Samplers
}
}
+ ///
+ /// Gets or sets the center point.
+ ///
+ public Point Center { get; set; }
+
///
protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY)
{
@@ -51,7 +56,7 @@ namespace ImageProcessorCore.Samplers
int startX = targetRectangle.X;
int endX = targetRectangle.Right;
float negativeAngle = -this.angle;
- Point centre = Rectangle.Center(sourceRectangle);
+ Point centre = this.Center == Point.Empty ? Rectangle.Center(sourceRectangle) : this.Center;
// Scaling factors
float widthFactor = source.Width / (float)target.Width;
diff --git a/src/ImageProcessorCore/Samplers/Skew.cs b/src/ImageProcessorCore/Samplers/Skew.cs
new file mode 100644
index 000000000..17e42833f
--- /dev/null
+++ b/src/ImageProcessorCore/Samplers/Skew.cs
@@ -0,0 +1,124 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageProcessorCore.Samplers
+{
+ using System.Threading.Tasks;
+
+ ///
+ /// Provides methods that allow the skewing of images.
+ ///
+ public class Skew : ImageSampler
+ {
+ ///
+ /// The angle of rotation along the x-axis.
+ ///
+ private float angleX;
+
+ ///
+ /// The angle of rotation along the y-axis.
+ ///
+ private float angleY;
+
+ ///
+ /// Gets or sets the angle of rotation along the x-axis in degrees.
+ ///
+ public float AngleX
+ {
+ get
+ {
+ return this.angleX;
+ }
+
+ set
+ {
+ if (value > 360)
+ {
+ value -= 360;
+ }
+
+ if (value < 0)
+ {
+ value += 360;
+ }
+
+ this.angleX = value;
+ }
+ }
+
+ ///
+ /// Gets or sets the angle of rotation along the y-axis in degrees.
+ ///
+ public float AngleY
+ {
+ get
+ {
+ return this.angleY;
+ }
+
+ set
+ {
+ if (value > 360)
+ {
+ value -= 360;
+ }
+
+ if (value < 0)
+ {
+ value += 360;
+ }
+
+ this.angleY = value;
+ }
+ }
+
+ ///
+ /// Gets or sets the center point.
+ ///
+ public Point Center { get; set; }
+
+ ///
+ protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY)
+ {
+ int targetY = targetRectangle.Y;
+ int targetBottom = targetRectangle.Bottom;
+ int startX = targetRectangle.X;
+ int endX = targetRectangle.Right;
+ float negativeAngleX = -this.angleX;
+ float negativeAngleY = -this.angleY;
+ Point centre = this.Center == Point.Empty ? Rectangle.Center(sourceRectangle) : this.Center;
+
+ // Scaling factors
+ float widthFactor = source.Width / (float)target.Width;
+ float heightFactor = source.Height / (float)target.Height;
+
+ Parallel.For(
+ startY,
+ endY,
+ y =>
+ {
+ if (y >= targetY && y < targetBottom)
+ {
+ // Y coordinates of source points
+ int originY = (int)((y - targetY) * heightFactor);
+
+ for (int x = startX; x < endX; x++)
+ {
+ // X coordinates of source points
+ int originX = (int)((x - startX) * widthFactor);
+
+ // Skew at the centre point
+ Point skewed = Point.Skew(new Point(originX, originY), centre, negativeAngleX, negativeAngleY);
+ if (sourceRectangle.Contains(skewed.X, skewed.Y))
+ {
+ target[x, y] = source[skewed.X, skewed.Y];
+ }
+ }
+ this.OnRowProcessed();
+ }
+ });
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/ImageProcessorCore.Tests/Processors/Samplers/SamplerTests.cs b/tests/ImageProcessorCore.Tests/Processors/Samplers/SamplerTests.cs
index df1914c3e..d80556721 100644
--- a/tests/ImageProcessorCore.Tests/Processors/Samplers/SamplerTests.cs
+++ b/tests/ImageProcessorCore.Tests/Processors/Samplers/SamplerTests.cs
@@ -202,7 +202,32 @@
using (FileStream output = File.OpenWrite($"TestOutput/Rotate/{filename}"))
{
image.Rotate(45, this.ProgressUpdate)
- .BackgroundColor(Color.Pink)
+ .Save(output);
+ }
+
+ Trace.WriteLine($"{watch.ElapsedMilliseconds}ms");
+ }
+ }
+ }
+
+ [Fact]
+ public void ImageShouldSkew()
+ {
+ if (!Directory.Exists("TestOutput/Skew"))
+ {
+ Directory.CreateDirectory("TestOutput/Skew");
+ }
+
+ foreach (string file in Files)
+ {
+ using (FileStream stream = File.OpenRead(file))
+ {
+ Stopwatch watch = Stopwatch.StartNew();
+ Image image = new Image(stream);
+ string filename = Path.GetFileName(file);
+ using (FileStream output = File.OpenWrite($"TestOutput/Skew/{filename}"))
+ {
+ image.Skew(45, 45, this.ProgressUpdate)
.Save(output);
}