diff --git a/src/ImageProcessorCore/Samplers/Processors/Matrix3x2Processor.cs b/src/ImageProcessorCore/Samplers/Processors/Matrix3x2Processor.cs new file mode 100644 index 000000000..e9b0441e3 --- /dev/null +++ b/src/ImageProcessorCore/Samplers/Processors/Matrix3x2Processor.cs @@ -0,0 +1,47 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Processors +{ + using System.Numerics; + + /// + /// Provides methods to transform an image using a . + /// + public abstract class Matrix3x2Processor : ImageSampler + { + /// + /// Creates a new target to contain the results of the matrix transform. + /// + /// Target image to apply the process to. + /// The source rectangle. + /// The processing matrix. + protected static void CreateNewTarget(ImageBase target, Rectangle sourceRectangle, Matrix3x2 processMatrix) + { + Matrix3x2 sizeMatrix; + if (Matrix3x2.Invert(processMatrix, out sizeMatrix)) + { + Rectangle rectangle = ImageMaths.GetBoundingRectangle(sourceRectangle, sizeMatrix); + target.SetPixels(rectangle.Width, rectangle.Height, new float[rectangle.Width * rectangle.Height * 4]); + } + } + + /// + /// Gets a transform matrix adjusted to center upon the target image bounds. + /// + /// Target image to apply the process to. + /// The source image. + /// The transform matrix. + /// + /// The . + /// + protected static Matrix3x2 GetCenteredMatrix(ImageBase target, ImageBase source, Matrix3x2 matrix) + { + Matrix3x2 translationToTargetCenter = Matrix3x2.CreateTranslation(-target.Width / 2f, -target.Height / 2f); + Matrix3x2 translateToSourceCenter = Matrix3x2.CreateTranslation(source.Width / 2f, source.Height / 2f); + return (translationToTargetCenter * matrix) * translateToSourceCenter; + } + } +} diff --git a/src/ImageProcessorCore/Samplers/Processors/RotateProcessor.cs b/src/ImageProcessorCore/Samplers/Processors/RotateProcessor.cs index 59d665d2e..d12bd3ec2 100644 --- a/src/ImageProcessorCore/Samplers/Processors/RotateProcessor.cs +++ b/src/ImageProcessorCore/Samplers/Processors/RotateProcessor.cs @@ -11,85 +11,60 @@ namespace ImageProcessorCore.Processors /// /// Provides methods that allow the rotating of images. /// - public class RotateProcessor : ImageSampler + public class RotateProcessor : Matrix3x2Processor { + /// + /// The tranform matrix to apply. + /// + private Matrix3x2 processMatrix; + /// public override int Parallelism { get; set; } = 1; /// - /// Gets or sets the angle of rotation in degrees. + /// Gets or sets the angle of processMatrix in degrees. /// public float Angle { get; set; } - /// - /// Gets or sets the center point. - /// - public Point Center { get; set; } - /// /// Gets or sets a value indicating whether to expand the canvas to fit the rotated image. /// - public bool Expand { get; set; } + public bool Expand { get; set; } = true; /// protected override void OnApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) { + processMatrix = Point.CreateRotation(new Point(0, 0), -this.Angle); if (this.Expand) { - Point centre = this.Center == Point.Empty ? Rectangle.Center(sourceRectangle) : this.Center; - Matrix3x2 rotation = Point.CreateRotation(centre, -this.Angle); - Matrix3x2 invertedRotation; - Matrix3x2.Invert(rotation, out invertedRotation); - Rectangle bounds = ImageMaths.GetBoundingRectangle(source.Bounds, invertedRotation); - target.SetPixels(bounds.Width, bounds.Height, new float[bounds.Width * bounds.Height * 4]); + CreateNewTarget(target, sourceRectangle, processMatrix); } } /// protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) { - int height = target.Height; - int startX = 0; - int endX = target.Width; - Point centre = this.Center == Point.Empty ? Rectangle.Center(target.Bounds) : this.Center; - - //Matrix3x2 invertedRotation; - Matrix3x2 rotation = Point.CreateRotation(centre, -this.Angle); - //Matrix3x2.Invert(rotation, out invertedRotation); - //Vector2 rightTop = Vector2.Transform(new Vector2(source.Width, 0), invertedRotation); - //Vector2 leftBottom = Vector2.Transform(new Vector2(0, source.Height), invertedRotation); - - //if (this.Angle < 0) - //{ - // rotation = Point.CreateRotation(new Point((int)-leftBottom.X, (int)leftBottom.Y), -this.Angle); - //} - - //if (this.Angle > 0) - //{ - // rotation = Point.CreateRotation(new Point((int)rightTop.X, (int)-rightTop.Y), -this.Angle); - //} + Matrix3x2 matrix = GetCenteredMatrix(target, source, this.processMatrix); - // Since we are not working in parallel we use full height and width - // of the first pass image. using (PixelAccessor sourcePixels = source.Lock()) using (PixelAccessor targetPixels = target.Lock()) { Parallel.For( 0, - height, + target.Height, y => + { + for (int x = 0; x < target.Width; x++) { - for (int x = startX; x < endX; x++) + Point transformedPoint = Point.Rotate(new Point(x, y), matrix); + if (source.Bounds.Contains(transformedPoint.X, transformedPoint.Y)) { - Point rotated = Point.Rotate(new Point(x, y), rotation); - if (source.Bounds.Contains(rotated.X, rotated.Y)) - { - targetPixels[x, y] = sourcePixels[rotated.X, rotated.Y]; - } + targetPixels[x, y] = sourcePixels[transformedPoint.X, transformedPoint.Y]; } + } - this.OnRowProcessed(); - }); + OnRowProcessed(); + }); } } } diff --git a/src/ImageProcessorCore/Samplers/Processors/SkewProcessor.cs b/src/ImageProcessorCore/Samplers/Processors/SkewProcessor.cs index 07760cf2a..43e54a1c7 100644 --- a/src/ImageProcessorCore/Samplers/Processors/SkewProcessor.cs +++ b/src/ImageProcessorCore/Samplers/Processors/SkewProcessor.cs @@ -11,8 +11,13 @@ namespace ImageProcessorCore.Processors /// /// Provides methods that allow the skewing of images. /// - public class SkewProcessor : ImageSampler + public class SkewProcessor : Matrix3x2Processor { + /// + /// The tranform matrix to apply. + /// + private Matrix3x2 processMatrix; + /// public override int Parallelism { get; set; } = 1; @@ -26,77 +31,45 @@ namespace ImageProcessorCore.Processors /// public float AngleY { get; set; } - /// - /// Gets or sets the center point. - /// - public Point Center { get; set; } - /// /// Gets or sets a value indicating whether to expand the canvas to fit the skewed image. /// - public bool Expand { get; set; } + public bool Expand { get; set; } = true; /// protected override void OnApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) { + this.processMatrix = Point.CreateSkew(new Point(0, 0), -this.AngleX, -this.AngleY); if (this.Expand) { - Point centre = this.Center; - Matrix3x2 skew = Point.CreateSkew(centre, -this.AngleX, -this.AngleY); - Matrix3x2 invertedSkew; - Matrix3x2.Invert(skew, out invertedSkew); - Rectangle bounds = ImageMaths.GetBoundingRectangle(source.Bounds, invertedSkew); - target.SetPixels(bounds.Width, bounds.Height, new float[bounds.Width * bounds.Height * 4]); + CreateNewTarget(target, sourceRectangle, this.processMatrix); } } /// protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) { - int height = target.Height; - int startX = 0; - int endX = target.Width; - Point centre = this.Center; - - Matrix3x2 invertedSkew; - Matrix3x2 skew = Point.CreateSkew(centre, -this.AngleX, -this.AngleY); - Matrix3x2.Invert(skew, out invertedSkew); - Vector2 rightTop = Vector2.Transform(new Vector2(source.Width, 0), invertedSkew); - Vector2 leftBottom = Vector2.Transform(new Vector2(0, source.Height), invertedSkew); - - if (this.AngleX < 0 && this.AngleY > 0) - { - skew = Point.CreateSkew(new Point((int)-leftBottom.X, (int)leftBottom.Y), -this.AngleX, -this.AngleY); - } - - if (this.AngleX > 0 && this.AngleY < 0) - { - skew = Point.CreateSkew(new Point((int)rightTop.X, (int)-rightTop.Y), -this.AngleX, -this.AngleY); - } - - if (this.AngleX < 0 && this.AngleY < 0) - { - skew = Point.CreateSkew(new Point(target.Width - 1, target.Height - 1), -this.AngleX, -this.AngleY); - } + Matrix3x2 matrix = GetCenteredMatrix(target, source, this.processMatrix); using (PixelAccessor sourcePixels = source.Lock()) using (PixelAccessor targetPixels = target.Lock()) { Parallel.For( 0, - height, + target.Height, y => - { - for (int x = startX; x < endX; x++) { - Point skewed = Point.Skew(new Point(x, y), skew); - if (source.Bounds.Contains(skewed.X, skewed.Y)) + for (int x = 0; x < target.Width; x++) { - targetPixels[x, y] = sourcePixels[skewed.X, skewed.Y]; + 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]; + } } - } - this.OnRowProcessed(); - }); + + OnRowProcessed(); + }); } } } diff --git a/src/ImageProcessorCore/Samplers/Rotate.cs b/src/ImageProcessorCore/Samplers/Rotate.cs index ea24f9650..fa30d9d34 100644 --- a/src/ImageProcessorCore/Samplers/Rotate.cs +++ b/src/ImageProcessorCore/Samplers/Rotate.cs @@ -1,7 +1,7 @@ // // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. -// ------------------------------------------------------------------------------------------------------------------- +// namespace ImageProcessorCore { @@ -21,21 +21,20 @@ namespace ImageProcessorCore /// The public static Image Rotate(this Image source, float degrees, ProgressEventHandler progressHandler = null) { - return Rotate(source, degrees, Point.Empty, true, progressHandler); + return Rotate(source, degrees, true, progressHandler); } /// - /// Rotates an image by the given angle in degrees around the given center point. + /// Rotates an image by the given angle in degrees. /// /// The image to rotate. /// The angle in degrees to perform the rotation. - /// The center point at which to rotate the image. /// Whether to expand the image to fit the rotated result. /// A delegate which is called as progress is made processing the image. /// The - public static Image Rotate(this Image source, float degrees, Point center, bool expand, ProgressEventHandler progressHandler = null) + public static Image Rotate(this Image source, float degrees, bool expand, ProgressEventHandler progressHandler = null) { - RotateProcessor processor = new RotateProcessor { Angle = degrees, Center = center, Expand = expand }; + RotateProcessor processor = new RotateProcessor { Angle = degrees, Expand = expand }; processor.OnProgress += progressHandler; try diff --git a/src/ImageProcessorCore/Samplers/Skew.cs b/src/ImageProcessorCore/Samplers/Skew.cs index 7b0587a52..904f1d89d 100644 --- a/src/ImageProcessorCore/Samplers/Skew.cs +++ b/src/ImageProcessorCore/Samplers/Skew.cs @@ -1,7 +1,7 @@ // // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. -// ------------------------------------------------------------------------------------------------------------------- +// namespace ImageProcessorCore { @@ -22,22 +22,21 @@ namespace ImageProcessorCore /// The public static Image Skew(this Image source, float degreesX, float degreesY, ProgressEventHandler progressHandler = null) { - return Skew(source, degreesX, degreesY, Point.Empty, true, progressHandler); + return Skew(source, degreesX, degreesY, true, progressHandler); } /// - /// Skews an image by the given angles in degrees around the given center point. + /// 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. - /// The center point at which to skew the image. /// Whether to expand the image to fit the skewed result. /// A delegate which is called as progress is made processing the image. /// The - public static Image Skew(this Image source, float degreesX, float degreesY, Point center, bool expand, ProgressEventHandler progressHandler = null) + public static Image Skew(this Image source, float degreesX, float degreesY, bool expand, ProgressEventHandler progressHandler = null) { - SkewProcessor processor = new SkewProcessor { AngleX = degreesX, AngleY = degreesY, Center = center, Expand = expand }; + SkewProcessor processor = new SkewProcessor { AngleX = degreesX, AngleY = degreesY, Expand = expand }; processor.OnProgress += progressHandler; try diff --git a/tests/ImageProcessorCore.Tests/Processors/Samplers/SamplerTests.cs b/tests/ImageProcessorCore.Tests/Processors/Samplers/SamplerTests.cs index 8c12e14f1..e81607b58 100644 --- a/tests/ImageProcessorCore.Tests/Processors/Samplers/SamplerTests.cs +++ b/tests/ImageProcessorCore.Tests/Processors/Samplers/SamplerTests.cs @@ -429,7 +429,7 @@ namespace ImageProcessorCore.Tests Image image = new Image(stream); using (FileStream output = File.OpenWrite($"TestOutput/Rotate/{filename}")) { - image.Rotate(63, this.ProgressUpdate) + image.Rotate(-170, this.ProgressUpdate) .Save(output); } } @@ -454,7 +454,7 @@ namespace ImageProcessorCore.Tests Image image = new Image(stream); using (FileStream output = File.OpenWrite($"TestOutput/Skew/{filename}")) { - image.Skew(-15, 12, this.ProgressUpdate) + image.Skew(-20, -10, this.ProgressUpdate) .Save(output); } }