diff --git a/src/ImageProcessorCore/Samplers/Processors/ProcessMatrixHelper.cs b/src/ImageProcessorCore/Samplers/Processors/ProcessMatrixHelper.cs new file mode 100644 index 000000000..428fc4084 --- /dev/null +++ b/src/ImageProcessorCore/Samplers/Processors/ProcessMatrixHelper.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using System.Threading.Tasks; + +namespace ImageProcessorCore +{ + + public static class ProcessMatrixHelper + { + public 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]); + } + } + public static Matrix3x2 Matrix3X2(ImageBase target, ImageBase source, Matrix3x2 processMatrix) + { + Matrix3x2 translationToTargetCenter = Matrix3x2.CreateTranslation(-target.Width / 2f, -target.Height / 2f); + Matrix3x2 translateToSourceCenter = Matrix3x2.CreateTranslation(source.Width / 2f, source.Height / 2f); + Matrix3x2 apply = (translationToTargetCenter * processMatrix) * translateToSourceCenter; + return apply; + } + + public static void DrawHorizontalData(ImageBase target, ImageBase source, int y, Matrix3x2 apply) + { + for (int x = 0; x < target.Width; x++) + { + Point rotated = Point.Rotate(new Point(x, y), apply); + if (source.Bounds.Contains(rotated.X, rotated.Y)) + { + target[x, y] = source[rotated.X, rotated.Y]; + } + } + } + } +} diff --git a/src/ImageProcessorCore/Samplers/Processors/RotateProcessor.cs b/src/ImageProcessorCore/Samplers/Processors/RotateProcessor.cs index 0b6dd1a11..7a8e5f981 100644 --- a/src/ImageProcessorCore/Samplers/Processors/RotateProcessor.cs +++ b/src/ImageProcessorCore/Samplers/Processors/RotateProcessor.cs @@ -13,12 +13,10 @@ namespace ImageProcessorCore /// public class RotateProcessor : ImageSampler { - /// - /// The image used for storing the first pass pixels. - /// + private Matrix3x2 processMatrix; /// - /// The angle of rotation in degrees. + /// The angle of processMatrix in degrees. /// private float angle; @@ -26,7 +24,7 @@ namespace ImageProcessorCore 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 { @@ -40,8 +38,6 @@ namespace ImageProcessorCore this.angle = value; } } - - /// /// Gets or sets a value indicating whether to expand the canvas to fit the rotated image. /// @@ -50,50 +46,26 @@ namespace ImageProcessorCore /// protected override void OnApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) { + processMatrix = Point.CreateRotation(new Point(0, 0), -this.angle); if (this.Expand) { - // First find out how big the target rectangle should be. - Point centre = Rectangle.Center(sourceRectangle); - Matrix3x2 rotation = Point.CreateRotation(centre, -this.angle); - Rectangle rectangle = ImageMaths.GetBoundingRectangle(sourceRectangle, rotation); - target.SetPixels(rectangle.Width, rectangle.Height, new float[rectangle.Width * rectangle.Height * 4]); + processMatrix = Point.CreateRotation(new Point(0,0), -this.angle); + ProcessMatrixHelper.CreateNewTarget(target, sourceRectangle,processMatrix); } } /// protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) { - - Matrix3x2 rotation = Point.CreateRotation(new Point(0, 0), -this.angle); - Matrix3x2 tran = Matrix3x2.CreateTranslation(-target.Width / 2f, -target.Height / 2f); - rotation = tran * rotation; - Matrix3x2 tran2 = Matrix3x2.CreateTranslation(source.Width / 2f, source.Height / 2f); - rotation = rotation * tran2; - - + var apply = ProcessMatrixHelper.Matrix3X2(target, source,processMatrix); Parallel.For( 0, target.Height, y => { - for (int x = 0; x < target.Width; x++) - { - // Rotate at the centre point - Point rotated = Point.Rotate(new Point(x, y), rotation); - if (source.Bounds.Contains(rotated.X, rotated.Y)) - { - target[x, y] = source[rotated.X, rotated.Y]; - } - } - - this.OnRowProcessed(); + ProcessMatrixHelper.DrawHorizontalData(target, source, y, apply); + OnRowProcessed(); }); } - - /// - protected override void AfterApply(ImageBase source, ImageBase target, Rectangle targetRectangle, Rectangle sourceRectangle) - { - - } } } \ No newline at end of file diff --git a/src/ImageProcessorCore/Samplers/Processors/SkewProcessor.cs b/src/ImageProcessorCore/Samplers/Processors/SkewProcessor.cs index 1bfcfb7e2..99b9f5457 100644 --- a/src/ImageProcessorCore/Samplers/Processors/SkewProcessor.cs +++ b/src/ImageProcessorCore/Samplers/Processors/SkewProcessor.cs @@ -13,10 +13,7 @@ namespace ImageProcessorCore /// public class SkewProcessor : ImageSampler { - /// - /// The image used for storing the first pass pixels. - /// - private Image firstPass; + private Matrix3x2 processMatrix; /// /// The angle of rotation along the x-axis. @@ -43,16 +40,6 @@ namespace ImageProcessorCore set { - if (value > 360) - { - value -= 360; - } - - if (value < 0) - { - value += 360; - } - this.angleX = value; } } @@ -69,16 +56,6 @@ namespace ImageProcessorCore set { - if (value > 360) - { - value -= 360; - } - - if (value < 0) - { - value += 360; - } - this.angleY = value; } } @@ -96,69 +73,26 @@ namespace ImageProcessorCore /// protected override void OnApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) { - // If we are expanding we need to pad the bounds of the source rectangle. - // We can use the resizer in nearest neighbor mode to do this fairly quickly. + processMatrix = Point.CreateSkew(new Point(0, 0), -this.angleX, -this.angleY); if (this.Expand) { - // First find out how big the target rectangle should be. - Point centre = this.Center == Point.Empty ? Rectangle.Center(sourceRectangle) : this.Center; - Matrix3x2 skew = Point.CreateSkew(centre, -this.angleX, -this.angleY); - Rectangle rectangle = ImageMaths.GetBoundingRectangle(sourceRectangle, skew); - ResizeOptions options = new ResizeOptions - { - Size = new Size(rectangle.Width, rectangle.Height), - Mode = ResizeMode.BoxPad - }; - - // Get the padded bounds and resize the image. - Rectangle bounds = ResizeHelper.CalculateTargetLocationAndBounds(source, options); - this.firstPass = new Image(rectangle.Width, rectangle.Height); - target.SetPixels(rectangle.Width, rectangle.Height, new float[rectangle.Width * rectangle.Height * 4]); - new ResizeProcessor(new NearestNeighborResampler()).Apply(this.firstPass, source, rectangle.Width, rectangle.Height, bounds, sourceRectangle); - } - else - { - // Just clone the pixels across. - this.firstPass = new Image(source.Width, source.Height); - this.firstPass.ClonePixels(source.Width, source.Height, source.Pixels); + ProcessMatrixHelper.CreateNewTarget(target, sourceRectangle, processMatrix); } } /// protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) { - int height = this.firstPass.Height; - int startX = 0; - int endX = this.firstPass.Width; - Point centre = this.Center == Point.Empty ? Rectangle.Center(this.firstPass.Bounds) : this.Center; - Matrix3x2 skew = Point.CreateSkew(centre, -this.angleX, -this.angleY); - - // Since we are not working in parallel we use full height and width - // of the first pass image. + var apply = ProcessMatrixHelper.Matrix3X2(target, source, processMatrix); Parallel.For( 0, - height, + target.Height, y => { - for (int x = startX; x < endX; x++) - { - // Skew at the centre point - Point skewed = Point.Skew(new Point(x, y), skew); - if (this.firstPass.Bounds.Contains(skewed.X, skewed.Y)) - { - target[x, y] = this.firstPass[skewed.X, skewed.Y]; - } - } - - this.OnRowProcessed(); + ProcessMatrixHelper.DrawHorizontalData(target, source, y, apply); + OnRowProcessed(); }); - } - /// - protected override void AfterApply(ImageBase source, ImageBase target, Rectangle targetRectangle, Rectangle sourceRectangle) - { - // Cleanup. - this.firstPass.Dispose(); } } } \ 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 29ab3be26..cffc39427 100644 --- a/tests/ImageProcessorCore.Tests/Processors/Samplers/SamplerTests.cs +++ b/tests/ImageProcessorCore.Tests/Processors/Samplers/SamplerTests.cs @@ -471,7 +471,7 @@ using (Image image = new Image(stream)) using (FileStream output = File.OpenWrite($"TestOutput/Rotate/{filename}")) { - image.Rotate(20, this.ProgressUpdate) + image.Rotate(-170, this.ProgressUpdate) .Save(output); } @@ -500,7 +500,7 @@ using (Image image = new Image(stream)) using (FileStream output = File.OpenWrite($"TestOutput/Skew/{filename}")) { - image.Skew(20, 10, this.ProgressUpdate) + image.Skew(-20, -10, this.ProgressUpdate) .Save(output); }