From fb6ac13e17ebe8e99c5e5fed8ec5ca9f402d8eb7 Mon Sep 17 00:00:00 2001 From: James South Date: Wed, 13 Apr 2016 23:09:37 +1000 Subject: [PATCH] Add Skew, Enhance Rotate Former-commit-id: 55c3fee46aa082cc2cca3232580ed42d6d40a1e1 Former-commit-id: ab1b89f66aca40b6d2afe262ab937d768cae67af Former-commit-id: ef13eccfc48a91a3adc99eaf0b746486d069bac1 --- README.md | 5 +- src/ImageProcessorCore/Numerics/Point.cs | 15 +++ .../Samplers/ImageSamplerExtensions.cs | 56 +++++++- src/ImageProcessorCore/Samplers/Rotate.cs | 9 +- src/ImageProcessorCore/Samplers/Skew.cs | 124 ++++++++++++++++++ .../Processors/Samplers/SamplerTests.cs | 27 +++- 6 files changed, 228 insertions(+), 8 deletions(-) create mode 100644 src/ImageProcessorCore/Samplers/Skew.cs 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); }