From 7c6ee0766c4c0fcc65f8fa13c376e232a999150b Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 31 May 2016 12:57:12 +1000 Subject: [PATCH 1/4] Clean up after resize. Former-commit-id: ecde7b51da9a00e7b83a6cd3be0f17bc4809ce66 Former-commit-id: 7972863246f8a461e065f0fd53938c734f42df4d Former-commit-id: b266214b444bc204f552ebba19f03a9aca791ac3 --- src/ImageProcessorCore/Samplers/Resize.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/ImageProcessorCore/Samplers/Resize.cs b/src/ImageProcessorCore/Samplers/Resize.cs index a9f528aad..5cbf45f35 100644 --- a/src/ImageProcessorCore/Samplers/Resize.cs +++ b/src/ImageProcessorCore/Samplers/Resize.cs @@ -184,6 +184,9 @@ namespace ImageProcessorCore.Samplers { target.ClonePixels(target.Width, target.Height, source.Pixels); } + + // Clean up + this.firstPass?.Dispose(); } } } \ No newline at end of file From 2c4b92c0de1128a5103c9b9cf0248093d73e0e2b Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 31 May 2016 13:07:38 +1000 Subject: [PATCH 2/4] Enhancements. Add pad (It rhymes :smile_cat:) Expand canvas on rotate. Fix #370 Former-commit-id: a8eb68c244d2aa76b4b7781474ba52ec39627b89 Former-commit-id: 9787dc29aae164752692f611fb18a6be4530a9f2 Former-commit-id: 0a2030e8d3c692394875f8014128715959aaa0bc --- README.md | 9 +- .../Samplers/ImageSamplerExtensions.cs | 33 +++++- src/ImageProcessorCore/Samplers/Rotate.cs | 106 +++++++++++++----- .../Processors/Samplers/SamplerTests.cs | 28 +++++ 4 files changed, 137 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index 130b3d6f0..1432317c1 100644 --- a/README.md +++ b/README.md @@ -72,7 +72,7 @@ git clone https://github.com/JimBobSquarePants/ImageProcessor - [x] Size - [x] Point - [x] Ellipse -- Resampling algorithms. (Optional gamma correction, Performance improvements?) +- Resampling algorithms. (Optional gamma correction, resize modes, Performance improvements?) - [x] Box - [x] Bicubic - [x] Lanczos3 @@ -86,13 +86,18 @@ git clone https://github.com/JimBobSquarePants/ImageProcessor - [x] Spline - [x] Triangle - [x] Welch +- Padding + - [x] Pad + - [x] ResizeMode.Pad + - [x] ResizeMode.BoxPad - Cropping - [x] Rectangular Crop - [ ] Elliptical Crop - [x] Entropy Crop + - [x] ResizeMode.Crop - Rotation/Skew - [x] Flip (90, 270, FlipType etc) - - [x] Rotate by angle and center point. + - [x] Rotate by angle and center point (Expandable canvas). - [x] Skew by x/y angles and center point. - ColorMatrix operations (Uses Matrix4x4) - [x] BlackWhite diff --git a/src/ImageProcessorCore/Samplers/ImageSamplerExtensions.cs b/src/ImageProcessorCore/Samplers/ImageSamplerExtensions.cs index 71c40f6c0..f28723276 100644 --- a/src/ImageProcessorCore/Samplers/ImageSamplerExtensions.cs +++ b/src/ImageProcessorCore/Samplers/ImageSamplerExtensions.cs @@ -42,7 +42,7 @@ namespace ImageProcessorCore.Samplers { Guard.MustBeGreaterThan(width, 0, nameof(width)); Guard.MustBeGreaterThan(height, 0, nameof(height)); - + if (sourceRectangle.Width < width || sourceRectangle.Height < height) { // If the source rectangle is smaller than the target perform a @@ -85,6 +85,26 @@ namespace ImageProcessorCore.Samplers } } + /// + /// Evenly pads an image to fit the new dimensions. + /// + /// The source image to pad. + /// The new width. + /// The new height. + /// A delegate which is called as progress is made processing the image. + /// The . + public static Image Pad(this Image source, int width, int height, ProgressEventHandler progressHandler = null) + { + ResizeOptions options = new ResizeOptions + { + Size = new Size(width, height), + Mode = ResizeMode.BoxPad, + Sampler = new NearestNeighborResampler() + }; + + return Resize(source, options, progressHandler); + } + /// /// Resizes an image in accordance with the given . /// @@ -95,7 +115,7 @@ namespace ImageProcessorCore.Samplers /// Passing zero for one of height or width within the resize options will automatically preserve the aspect ratio of the original image public static Image Resize(this Image source, ResizeOptions options, ProgressEventHandler progressHandler = null) { - // Ensure size is populated acros both dimensions. + // Ensure size is populated across both dimensions. if (options.Size.Width == 0 && options.Size.Height > 0) { options.Size = new Size(source.Width * options.Size.Height / source.Height, options.Size.Height); @@ -203,7 +223,7 @@ namespace ImageProcessorCore.Samplers } /// - /// Rotates an image by the given angle in degrees. + /// Rotates an image by the given angle in degrees, expanding the image to fit the rotated result. /// /// The image to rotate. /// The angle in degrees to perform the rotation. @@ -211,7 +231,7 @@ namespace ImageProcessorCore.Samplers /// The public static Image Rotate(this Image source, float degrees, ProgressEventHandler progressHandler = null) { - return Rotate(source, degrees, Rectangle.Center(source.Bounds), progressHandler); + return Rotate(source, degrees, Point.Empty, true, progressHandler); } /// @@ -220,11 +240,12 @@ namespace ImageProcessorCore.Samplers /// The image to rotate. /// The angle in degrees to perform the rotation. /// The center point at which to skew 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, ProgressEventHandler progressHandler = null) + public static Image Rotate(this Image source, float degrees, Point center, bool expand, ProgressEventHandler progressHandler = null) { - Rotate processor = new Rotate { Angle = degrees, Center = center }; + Rotate processor = new Rotate { Angle = degrees, Center = center, Expand = expand }; processor.OnProgress += progressHandler; try diff --git a/src/ImageProcessorCore/Samplers/Rotate.cs b/src/ImageProcessorCore/Samplers/Rotate.cs index bc10e2fdb..1de1b57af 100644 --- a/src/ImageProcessorCore/Samplers/Rotate.cs +++ b/src/ImageProcessorCore/Samplers/Rotate.cs @@ -3,10 +3,9 @@ // Licensed under the Apache License, Version 2.0. // -using System.Numerics; - namespace ImageProcessorCore.Samplers { + using System.Numerics; using System.Threading.Tasks; /// @@ -15,12 +14,20 @@ namespace ImageProcessorCore.Samplers public class Rotate : ImageSampler { /// - /// The angle of rotation. + /// The image used for storing the first pass pixels. + /// + private Image firstPass; + + /// + /// The angle of rotation in degrees. /// private float angle; + /// + public override int Parallelism { get; set; } = 1; + /// - /// Gets or sets the angle of rotation. + /// Gets or sets the angle of rotation in degrees. /// public float Angle { @@ -50,47 +57,84 @@ namespace ImageProcessorCore.Samplers /// 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; } + /// - protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) + protected override void OnApply(ImageBase source, ImageBase target, Rectangle targetRectangle, Rectangle sourceRectangle) { - int targetY = targetRectangle.Y; - int targetBottom = targetRectangle.Bottom; - int startX = targetRectangle.X; - int endX = targetRectangle.Right; - float negativeAngle = -this.angle; - Point centre = this.Center == Point.Empty ? Rectangle.Center(sourceRectangle) : this.Center; + // 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 the target rectangle should be. + Rectangle rectangle = ImageMaths.GetBoundingRotatedRectangle(source.Width, source.Height, -this.angle); + + ResizeOptions options = new ResizeOptions + { + Size = new Size(rectangle.Width, rectangle.Height), + Mode = ResizeMode.BoxPad, + Sampler = new NearestNeighborResampler() + }; - // Scaling factors - float widthFactor = source.Width / (float)target.Width; - float heightFactor = source.Height / (float)target.Height; + // 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 Resize(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); + } + } - Matrix3x2 rotation = Point.CreateRotatation( centre, negativeAngle ); + /// + protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) + { + int targetY = this.firstPass.Bounds.Y; + int targetHeight = this.firstPass.Height; + int startX = this.firstPass.Bounds.X; + int endX = this.firstPass.Bounds.Right; + float negativeAngle = -this.angle; + Point centre = this.Center == Point.Empty ? Rectangle.Center(this.firstPass.Bounds) : this.Center; + Matrix3x2 rotation = Point.CreateRotatation(centre, negativeAngle); + // Since we are not working in parallel we use full height and width of the first pass image. Parallel.For( - startY, - endY, + 0, + targetHeight, y => { - if (y >= targetY && y < targetBottom) + // Y coordinates of source points + int originY = y - targetY; + + for (int x = startX; x < endX; x++) { - // Y coordinates of source points - int originY = (int)((y - targetY) * heightFactor); + // X coordinates of source points + int originX = x - startX; - for (int x = startX; x < endX; x++) + // Rotate at the centre point + Point rotated = Point.Rotate(new Point(originX, originY), rotation); + if (this.firstPass.Bounds.Contains(rotated.X, rotated.Y)) { - // X coordinates of source points - int originX = (int)((x - startX) * widthFactor); - - // Rotate at the centre point - Point rotated = Point.Rotate(new Point(originX, originY), rotation); - if (sourceRectangle.Contains(rotated.X, rotated.Y)) - { - target[x, y] = source[rotated.X, rotated.Y]; - } + target[x, y] = this.firstPass[rotated.X, rotated.Y]; } - this.OnRowProcessed(); } + + this.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 2516cf62b..845bf0b1b 100644 --- a/tests/ImageProcessorCore.Tests/Processors/Samplers/SamplerTests.cs +++ b/tests/ImageProcessorCore.Tests/Processors/Samplers/SamplerTests.cs @@ -76,6 +76,34 @@ } } + [Fact] + public void ImageShouldPad() + { + if (!Directory.Exists("TestOutput/Pad")) + { + Directory.CreateDirectory("TestOutput/Pad"); + } + + foreach (string file in Files) + { + using (FileStream stream = File.OpenRead(file)) + { + Stopwatch watch = Stopwatch.StartNew(); + + string filename = Path.GetFileName(file); + + using (Image image = new Image(stream)) + using (FileStream output = File.OpenWrite($"TestOutput/Pad/{filename}")) + { + image.Pad(image.Width + 50, image.Height + 50, this.ProgressUpdate) + .Save(output); + } + + Trace.WriteLine($"{watch.ElapsedMilliseconds}ms"); + } + } + } + [Theory] [MemberData("ReSamplers")] public void ImageShouldResize(string name, IResampler sampler) From f11722e460e168675fda38d6eba7b0241c6da1dd Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 31 May 2016 13:44:20 +1000 Subject: [PATCH 3/4] Use qualifier [skip ci] Former-commit-id: a36614c5564961e7b151dbe223182f6ee7649397 Former-commit-id: e0093c75fe7b957d29aaeeccaa82fbb43f9dfacf Former-commit-id: 5c4a6bbb637c99ef5d46ecdb1506a6d334f01853 --- src/ImageProcessorCore/Samplers/RotateFlip.cs | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/src/ImageProcessorCore/Samplers/RotateFlip.cs b/src/ImageProcessorCore/Samplers/RotateFlip.cs index 09162bff0..aec9b9bbf 100644 --- a/src/ImageProcessorCore/Samplers/RotateFlip.cs +++ b/src/ImageProcessorCore/Samplers/RotateFlip.cs @@ -2,7 +2,6 @@ // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // - namespace ImageProcessorCore.Samplers { using System; @@ -43,13 +42,13 @@ namespace ImageProcessorCore.Samplers switch (this.RotateType) { case RotateType.Rotate90: - Rotate90(target, source); + this.Rotate90(target, source); break; case RotateType.Rotate180: - Rotate180(target, source); + this.Rotate180(target, source); break; case RotateType.Rotate270: - Rotate270(target, source); + this.Rotate270(target, source); break; default: target.ClonePixels(target.Width, target.Height, source.Pixels); @@ -60,10 +59,10 @@ namespace ImageProcessorCore.Samplers { // No default needed as we have already set the pixels. case FlipType.Vertical: - FlipX(target); + this.FlipX(target); break; case FlipType.Horizontal: - FlipY(target); + this.FlipY(target); break; } } @@ -79,7 +78,7 @@ namespace ImageProcessorCore.Samplers int height = source.Height; Image temp = new Image(height, width); - Parallel.For(0, height, + Parallel.For(0, height, y => { for (int x = 0; x < width; x++) @@ -90,6 +89,7 @@ namespace ImageProcessorCore.Samplers newY = width - newY - 1; temp[newX, newY] = source[x, y]; } + this.OnRowProcessed(); }); @@ -106,7 +106,7 @@ namespace ImageProcessorCore.Samplers int width = source.Width; int height = source.Height; - Parallel.For(0, height, + Parallel.For(0, height, y => { for (int x = 0; x < width; x++) @@ -115,6 +115,7 @@ namespace ImageProcessorCore.Samplers int newY = height - y - 1; target[newX, newY] = source[x, y]; } + this.OnRowProcessed(); }); } @@ -130,7 +131,7 @@ namespace ImageProcessorCore.Samplers int height = source.Height; Image temp = new Image(height, width); - Parallel.For(0, height, + Parallel.For(0, height, y => { for (int x = 0; x < width; x++) @@ -138,6 +139,7 @@ namespace ImageProcessorCore.Samplers int newX = height - y - 1; temp[newX, x] = source[x, y]; } + this.OnRowProcessed(); }); @@ -157,7 +159,7 @@ namespace ImageProcessorCore.Samplers ImageBase temp = new Image(width, height); temp.ClonePixels(width, height, target.Pixels); - Parallel.For(0, halfHeight, + Parallel.For(0, halfHeight, y => { for (int x = 0; x < width; x++) @@ -166,6 +168,7 @@ namespace ImageProcessorCore.Samplers target[x, y] = temp[x, newY]; target[x, newY] = temp[x, y]; } + this.OnRowProcessed(); }); } @@ -183,7 +186,7 @@ namespace ImageProcessorCore.Samplers ImageBase temp = new Image(width, height); temp.ClonePixels(width, height, target.Pixels); - Parallel.For(0, height, + Parallel.For(0, height, y => { for (int x = 0; x < halfWidth; x++) @@ -192,6 +195,7 @@ namespace ImageProcessorCore.Samplers target[x, y] = temp[newX, y]; target[newX, y] = temp[x, y]; } + this.OnRowProcessed(); }); } From 4b54be51ae089e9d9bf9ddca36a11415471f78b4 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 31 May 2016 18:04:54 +1000 Subject: [PATCH 4/4] Begin smarter rotate/skew maths [skip ci] Former-commit-id: b2ad022ef5c2d8acc22e5a8d4133fc3cb26aac65 Former-commit-id: d0631f99f8aef3b71b44252f0c17ded9a926ec3d Former-commit-id: 5c7ff70d77fe8d5e56cab00db8fa681912fe4006 --- .../Common/Helpers/ImageMaths.cs | 28 ++++++-- src/ImageProcessorCore/Numerics/Point.cs | 67 ++++++++++++------- .../Samplers/ImageSamplerExtensions.cs | 4 +- src/ImageProcessorCore/Samplers/Rotate.cs | 4 +- src/ImageProcessorCore/Samplers/Skew.cs | 13 ++-- .../Processors/Samplers/SamplerTests.cs | 3 +- .../MVC/Properties/launchSettings.json | 19 ++++++ 7 files changed, 97 insertions(+), 41 deletions(-) create mode 100644 tests/TestWebsites/MVC/Properties/launchSettings.json diff --git a/src/ImageProcessorCore/Common/Helpers/ImageMaths.cs b/src/ImageProcessorCore/Common/Helpers/ImageMaths.cs index 662a41f1b..ab4394feb 100644 --- a/src/ImageProcessorCore/Common/Helpers/ImageMaths.cs +++ b/src/ImageProcessorCore/Common/Helpers/ImageMaths.cs @@ -112,15 +112,13 @@ namespace ImageProcessorCore /// /// Returns the given degrees converted to radians. /// - /// - /// The angle in degrees. - /// + /// The angle in degrees. /// - /// The representing the degree as radians. + /// The representing the degree as radians. /// - public static double DegreesToRadians(double angleInDegrees) + public static float DegreesToRadians(float degrees) { - return angleInDegrees * (PI / 180); + return degrees * (PI / 180); } /// @@ -140,6 +138,24 @@ namespace ImageProcessorCore return new Rectangle(topLeft.X, topLeft.Y, bottomRight.X - topLeft.X, bottomRight.Y - topLeft.Y); } + // http://gamedev.stackexchange.com/questions/22840/create-a-rectangle-struct-to-be-rotated-and-have-a-intersects-function + public static Rectangle GetBoundingRotatedRectangle(Rectangle rectangle, float degrees, Point center) + { + float radians = DegreesToRadians(degrees); + Matrix3x2 matrix = Matrix3x2.CreateRotation(radians, center.ToVector2()); + + Vector2 leftTop = Vector2.Transform(new Vector2(rectangle.Left, rectangle.Top), matrix); + Vector2 rightTop = Vector2.Transform(new Vector2(rectangle.Right, rectangle.Top), matrix); + Vector2 leftBottom = Vector2.Transform(new Vector2(rectangle.Left, rectangle.Bottom), matrix); + Vector2 rightBottom = Vector2.Transform(new Vector2(rectangle.Right, rectangle.Bottom), matrix); + + Vector2 min = Vector2.Min(Vector2.Min(leftTop, rightTop), Vector2.Min(leftBottom, rightBottom)); + Vector2 max = Vector2.Max(Vector2.Max(leftTop, rightTop), Vector2.Max(leftBottom, rightBottom)); + + // TODO: minY is wrong - negative + return new Rectangle((int)min.X, (int)min.Y, (int)(max.X - min.X), (int)(max.Y - min.Y)); + } + /// /// Calculates the new size after rotation. /// diff --git a/src/ImageProcessorCore/Numerics/Point.cs b/src/ImageProcessorCore/Numerics/Point.cs index 472a1c773..f1fe3c5ec 100644 --- a/src/ImageProcessorCore/Numerics/Point.cs +++ b/src/ImageProcessorCore/Numerics/Point.cs @@ -159,26 +159,26 @@ namespace ImageProcessorCore } /// - /// Rotates a point around a given a rotation matrix. + /// Creates a rotation matrix for the given point and angle. /// - /// The point to rotate - /// Rotation matrix used - /// - public static Point Rotate( Point point, Matrix3x2 rotation ) + /// The origin point to rotate around + /// Rotation in degrees + /// The rotation + public static Matrix3x2 CreateRotation(Point origin, float degrees) { - return new Point( Vector2.Transform( point.backingVector, rotation ) ); + float radians = (float)ImageMaths.DegreesToRadians(degrees); + return Matrix3x2.CreateRotation(radians, origin.backingVector); } /// - /// Creates a rotation matrix for + /// Rotates a point around a given a rotation matrix. /// - /// The origin point to rotate around - /// Rotation in degrees - /// - public static Matrix3x2 CreateRotatation( Point origin, float degrees ) + /// The point to rotate + /// Rotation matrix used + /// The rotated + public static Point Rotate(Point point, Matrix3x2 rotation) { - float radians = (float)ImageMaths.DegreesToRadians( degrees ); - return Matrix3x2.CreateRotation( radians, origin.backingVector ); + return new Point(Vector2.Transform(point.backingVector, rotation)); } /// @@ -187,26 +187,48 @@ namespace ImageProcessorCore /// The point to rotate /// The center point to rotate around. /// The angle in degrees. - /// + /// The rotated public static Point Rotate(Point point, Point origin, float degrees) { - float radians = (float)ImageMaths.DegreesToRadians(degrees); - return new Point(Vector2.Transform(point.backingVector, Matrix3x2.CreateRotation(radians, origin.backingVector))); + return new Point(Vector2.Transform(point.backingVector, CreateRotation(origin, degrees))); } /// - /// Skews a point around a given origin by the specified angles in degrees. + /// Creates a skew matrix for the given point and angle. + /// + /// The origin point to rotate around + /// The x-angle in degrees. + /// The y-angle in degrees. + /// The rotation + public static Matrix3x2 CreateSkew(Point origin, float degreesX, float degreesY) + { + float radiansX = (float)ImageMaths.DegreesToRadians(degreesX); + float radiansY = (float)ImageMaths.DegreesToRadians(degreesY); + return Matrix3x2.CreateSkew(radiansX, radiansY, origin.backingVector); + } + + /// + /// Skews a point using a given a skew matrix. /// /// The point to rotate + /// Rotation matrix used + /// The rotated + public static Point Skew(Point point, Matrix3x2 skew) + { + return new Point(Vector2.Transform(point.backingVector, skew)); + } + + /// + /// Skews a point around a given origin by the specified angles in degrees. + /// + /// The point to skew. /// The center point to rotate around. /// The x-angle in degrees. /// The y-angle in degrees. - /// + /// The skewed 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))); + return new Point(Vector2.Transform(point.backingVector, CreateSkew(origin, degreesX, degreesY))); } /// @@ -223,8 +245,7 @@ namespace ImageProcessorCore return "Point [ Empty ]"; } - return - $"Point [ X={this.X}, Y={this.Y} ]"; + return $"Point [ X={this.X}, Y={this.Y} ]"; } /// diff --git a/src/ImageProcessorCore/Samplers/ImageSamplerExtensions.cs b/src/ImageProcessorCore/Samplers/ImageSamplerExtensions.cs index f28723276..45dc3c913 100644 --- a/src/ImageProcessorCore/Samplers/ImageSamplerExtensions.cs +++ b/src/ImageProcessorCore/Samplers/ImageSamplerExtensions.cs @@ -239,7 +239,7 @@ namespace ImageProcessorCore.Samplers /// /// The image to rotate. /// The angle in degrees to perform the rotation. - /// The center point at which to skew the image. + /// 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 @@ -291,7 +291,7 @@ namespace ImageProcessorCore.Samplers /// 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); + return Skew(source, degreesX, degreesY, Point.Empty, progressHandler); } /// diff --git a/src/ImageProcessorCore/Samplers/Rotate.cs b/src/ImageProcessorCore/Samplers/Rotate.cs index 1de1b57af..a14b3cee1 100644 --- a/src/ImageProcessorCore/Samplers/Rotate.cs +++ b/src/ImageProcessorCore/Samplers/Rotate.cs @@ -71,7 +71,7 @@ namespace ImageProcessorCore.Samplers { // First find out how the target rectangle should be. Rectangle rectangle = ImageMaths.GetBoundingRotatedRectangle(source.Width, source.Height, -this.angle); - + Rectangle rectangle2 = ImageMaths.GetBoundingRotatedRectangle(sourceRectangle, -this.angle, this.Center); ResizeOptions options = new ResizeOptions { Size = new Size(rectangle.Width, rectangle.Height), @@ -102,7 +102,7 @@ namespace ImageProcessorCore.Samplers int endX = this.firstPass.Bounds.Right; float negativeAngle = -this.angle; Point centre = this.Center == Point.Empty ? Rectangle.Center(this.firstPass.Bounds) : this.Center; - Matrix3x2 rotation = Point.CreateRotatation(centre, negativeAngle); + Matrix3x2 rotation = Point.CreateRotation(centre, negativeAngle); // Since we are not working in parallel we use full height and width of the first pass image. Parallel.For( diff --git a/src/ImageProcessorCore/Samplers/Skew.cs b/src/ImageProcessorCore/Samplers/Skew.cs index 17e42833f..1d88811d1 100644 --- a/src/ImageProcessorCore/Samplers/Skew.cs +++ b/src/ImageProcessorCore/Samplers/Skew.cs @@ -5,6 +5,7 @@ namespace ImageProcessorCore.Samplers { + using System.Numerics; using System.Threading.Tasks; /// @@ -89,10 +90,7 @@ namespace ImageProcessorCore.Samplers 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; + Matrix3x2 skew = Point.CreateSkew(centre, negativeAngleX, negativeAngleY); Parallel.For( startY, @@ -102,20 +100,21 @@ namespace ImageProcessorCore.Samplers if (y >= targetY && y < targetBottom) { // Y coordinates of source points - int originY = (int)((y - targetY) * heightFactor); + int originY = y - targetY; for (int x = startX; x < endX; x++) { // X coordinates of source points - int originX = (int)((x - startX) * widthFactor); + int originX = x - startX; // Skew at the centre point - Point skewed = Point.Skew(new Point(originX, originY), centre, negativeAngleX, negativeAngleY); + Point skewed = Point.Skew(new Point(originX, originY), skew); if (sourceRectangle.Contains(skewed.X, skewed.Y)) { target[x, y] = source[skewed.X, skewed.Y]; } } + this.OnRowProcessed(); } }); diff --git a/tests/ImageProcessorCore.Tests/Processors/Samplers/SamplerTests.cs b/tests/ImageProcessorCore.Tests/Processors/Samplers/SamplerTests.cs index 845bf0b1b..7d57934f0 100644 --- a/tests/ImageProcessorCore.Tests/Processors/Samplers/SamplerTests.cs +++ b/tests/ImageProcessorCore.Tests/Processors/Samplers/SamplerTests.cs @@ -452,6 +452,7 @@ Directory.CreateDirectory("TestOutput/Skew"); } + // Matches live example http://www.w3schools.com/css/tryit.asp?filename=trycss3_transform_skew foreach (string file in Files) { using (FileStream stream = File.OpenRead(file)) @@ -463,7 +464,7 @@ using (Image image = new Image(stream)) using (FileStream output = File.OpenWrite($"TestOutput/Skew/{filename}")) { - image.Skew(45, 45, this.ProgressUpdate) + image.Skew(20, 10, this.ProgressUpdate) .Save(output); } diff --git a/tests/TestWebsites/MVC/Properties/launchSettings.json b/tests/TestWebsites/MVC/Properties/launchSettings.json new file mode 100644 index 000000000..0ce7c413b --- /dev/null +++ b/tests/TestWebsites/MVC/Properties/launchSettings.json @@ -0,0 +1,19 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:55993/", + "sslPort": 0 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} \ No newline at end of file