From b6940cbbd1c9963760e84c5c8a7d561738ad533c Mon Sep 17 00:00:00 2001 From: Sverre Rekvin Date: Fri, 1 Jul 2016 22:59:20 +0200 Subject: [PATCH 1/4] maybe working Former-commit-id: 696365d1b984a55ecb1063cb21e0a2f48b454274 Former-commit-id: 11caf1a2b1f7c67874c8ac1fbf33c53318068c8e Former-commit-id: 4cb2557cbbf7e5d6eba652eac57d969a1d2705fd --- .../Samplers/Processors/RotateProcessor.cs | 46 ++++++------------- 1 file changed, 15 insertions(+), 31 deletions(-) diff --git a/src/ImageProcessorCore/Samplers/Processors/RotateProcessor.cs b/src/ImageProcessorCore/Samplers/Processors/RotateProcessor.cs index 33c31bf9c..7822eb7f0 100644 --- a/src/ImageProcessorCore/Samplers/Processors/RotateProcessor.cs +++ b/src/ImageProcessorCore/Samplers/Processors/RotateProcessor.cs @@ -16,7 +16,6 @@ namespace ImageProcessorCore /// /// The image used for storing the first pass pixels. /// - private Image firstPass; /// /// The angle of rotation in degrees. @@ -38,16 +37,6 @@ namespace ImageProcessorCore set { - if (value > 360) - { - value -= 360; - } - - if (value < 0) - { - value += 360; - } - this.angle = value; } } @@ -81,41 +70,37 @@ namespace ImageProcessorCore // 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); } } /// 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; + + Point centre = Rectangle.Center(source.Bounds); Matrix3x2 rotation = Point.CreateRotation(centre, -this.angle); - // Since we are not working in parallel we use full height and width - // of the first pass image. + rotation = Point.CreateRotation(new Point(0,0), -this.angle); + + Matrix3x2 tran =Matrix3x2.CreateTranslation(-target.Width/2, -target.Height/2); + rotation = tran* rotation; + Matrix3x2 tran2 = Matrix3x2.CreateTranslation(source.Width / 2, source.Height / 2); + rotation = rotation*tran2; + + Parallel.For( 0, - height, + target.Height, y => { - for (int x = startX; x < endX; x++) + for (int x = 0; x < target.Width; x++) { // Rotate at the centre point Point rotated = Point.Rotate(new Point(x, y), rotation); - if (this.firstPass.Bounds.Contains(rotated.X, rotated.Y)) + if (source.Bounds.Contains(rotated.X, rotated.Y)) { - target[x, y] = this.firstPass[rotated.X, rotated.Y]; + target[x, y] = source[rotated.X, rotated.Y]; } } @@ -126,8 +111,7 @@ namespace ImageProcessorCore /// protected override void AfterApply(ImageBase source, ImageBase target, Rectangle targetRectangle, Rectangle sourceRectangle) { - // Cleanup. - this.firstPass.Dispose(); + } } } \ No newline at end of file From d0f5d53d1fa5acad234f866dd7d69e6f9fe537e0 Mon Sep 17 00:00:00 2001 From: Sverre Rekvin Date: Fri, 1 Jul 2016 23:09:04 +0200 Subject: [PATCH 2/4] First working version Former-commit-id: 9a29900c60ed46679ec35b19764f2716a071cdcc Former-commit-id: 5d27440cde0a1b795322f33df43c1e23a5686158 Former-commit-id: 94ca246cfdf0d6ad960284d4dcd626632d15c300 --- .../Samplers/Processors/RotateProcessor.cs | 26 +++---------------- src/ImageProcessorCore/Samplers/Rotate.cs | 2 +- .../Processors/Samplers/SamplerTests.cs | 2 +- 3 files changed, 6 insertions(+), 24 deletions(-) diff --git a/src/ImageProcessorCore/Samplers/Processors/RotateProcessor.cs b/src/ImageProcessorCore/Samplers/Processors/RotateProcessor.cs index 7822eb7f0..847c68ef4 100644 --- a/src/ImageProcessorCore/Samplers/Processors/RotateProcessor.cs +++ b/src/ImageProcessorCore/Samplers/Processors/RotateProcessor.cs @@ -41,10 +41,6 @@ namespace ImageProcessorCore } } - /// - /// 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. @@ -54,22 +50,12 @@ 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. if (this.Expand) { // First find out how big the target rectangle should be. - Point centre = this.Center == Point.Empty ? Rectangle.Center(sourceRectangle) : this.Center; + Point centre = Rectangle.Center(sourceRectangle); Matrix3x2 rotation = Point.CreateRotation(centre, -this.angle); Rectangle rectangle = ImageMaths.GetBoundingRectangle(sourceRectangle, rotation); - 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); target.SetPixels(rectangle.Width, rectangle.Height, new float[rectangle.Width * rectangle.Height * 4]); } } @@ -78,14 +64,10 @@ namespace ImageProcessorCore protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) { - Point centre = Rectangle.Center(source.Bounds); - Matrix3x2 rotation = Point.CreateRotation(centre, -this.angle); - - rotation = Point.CreateRotation(new Point(0,0), -this.angle); - - Matrix3x2 tran =Matrix3x2.CreateTranslation(-target.Width/2, -target.Height/2); + 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 / 2, source.Height / 2); + Matrix3x2 tran2 = Matrix3x2.CreateTranslation(source.Width / 2f, source.Height / 2f); rotation = rotation*tran2; diff --git a/src/ImageProcessorCore/Samplers/Rotate.cs b/src/ImageProcessorCore/Samplers/Rotate.cs index 49441fbb5..09c49e588 100644 --- a/src/ImageProcessorCore/Samplers/Rotate.cs +++ b/src/ImageProcessorCore/Samplers/Rotate.cs @@ -33,7 +33,7 @@ namespace ImageProcessorCore /// The public static Image Rotate(this Image source, float degrees, Point center, 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/tests/ImageProcessorCore.Tests/Processors/Samplers/SamplerTests.cs b/tests/ImageProcessorCore.Tests/Processors/Samplers/SamplerTests.cs index ac610746e..29ab3be26 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(63, this.ProgressUpdate) + image.Rotate(20, this.ProgressUpdate) .Save(output); } From 82fae083e1ff562faa4484bd178c453aeee2db7a Mon Sep 17 00:00:00 2001 From: Sverre Rekvin Date: Sat, 2 Jul 2016 00:39:30 +0200 Subject: [PATCH 3/4] rotate Former-commit-id: b47614eda6c4bf9f50968bd324f27dcec5f685bb Former-commit-id: 8a1a1b13359ba260906d8439f131d0f07ce81cba Former-commit-id: 9ce6a4e24ec11eb2e0a90f9e12ea103c4dbacd8c --- .../Samplers/Processors/RotateProcessor.cs | 62 +++++-------------- 1 file changed, 14 insertions(+), 48 deletions(-) diff --git a/src/ImageProcessorCore/Samplers/Processors/RotateProcessor.cs b/src/ImageProcessorCore/Samplers/Processors/RotateProcessor.cs index 33c31bf9c..0b6dd1a11 100644 --- a/src/ImageProcessorCore/Samplers/Processors/RotateProcessor.cs +++ b/src/ImageProcessorCore/Samplers/Processors/RotateProcessor.cs @@ -16,7 +16,6 @@ namespace ImageProcessorCore /// /// The image used for storing the first pass pixels. /// - private Image firstPass; /// /// The angle of rotation in degrees. @@ -38,24 +37,10 @@ namespace ImageProcessorCore set { - if (value > 360) - { - value -= 360; - } - - if (value < 0) - { - value += 360; - } - this.angle = value; } } - /// - /// 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. @@ -65,57 +50,39 @@ 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. if (this.Expand) { // First find out how big the target rectangle should be. - Point centre = this.Center == Point.Empty ? Rectangle.Center(sourceRectangle) : this.Center; + Point centre = Rectangle.Center(sourceRectangle); Matrix3x2 rotation = Point.CreateRotation(centre, -this.angle); Rectangle rectangle = ImageMaths.GetBoundingRectangle(sourceRectangle, rotation); - 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); } } /// 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 rotation = Point.CreateRotation(centre, -this.angle); - - // Since we are not working in parallel we use full height and width - // of the first pass image. + + 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; + + Parallel.For( 0, - height, + target.Height, y => { - for (int x = startX; x < endX; x++) + for (int x = 0; x < target.Width; x++) { // Rotate at the centre point Point rotated = Point.Rotate(new Point(x, y), rotation); - if (this.firstPass.Bounds.Contains(rotated.X, rotated.Y)) + if (source.Bounds.Contains(rotated.X, rotated.Y)) { - target[x, y] = this.firstPass[rotated.X, rotated.Y]; + target[x, y] = source[rotated.X, rotated.Y]; } } @@ -126,8 +93,7 @@ namespace ImageProcessorCore /// protected override void AfterApply(ImageBase source, ImageBase target, Rectangle targetRectangle, Rectangle sourceRectangle) { - // Cleanup. - this.firstPass.Dispose(); + } } } \ No newline at end of file From 02427212fc507b8802a13502fcfae3e64ff2066a Mon Sep 17 00:00:00 2001 From: Sverre Rekvin Date: Sat, 2 Jul 2016 01:27:56 +0200 Subject: [PATCH 4/4] Rotate and skew, shorter code fewer if, better math Former-commit-id: 1c43328c71d549a566fe1c3a655f97c9ce9fad05 Former-commit-id: db8c7b880c3f17b85feb7277477eddc50f6dffb5 Former-commit-id: 405ff7e2dccdc33b5cbf53a07208a57528616c6b --- .../Processors/ProcessMatrixHelper.cs | 41 ++++++++++ .../Samplers/Processors/RotateProcessor.cs | 46 +++-------- .../Samplers/Processors/SkewProcessor.cs | 80 ++----------------- .../Processors/Samplers/SamplerTests.cs | 4 +- 4 files changed, 59 insertions(+), 112 deletions(-) create mode 100644 src/ImageProcessorCore/Samplers/Processors/ProcessMatrixHelper.cs 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); }