diff --git a/src/ImageProcessor/Common/Helpers/ImageMaths.cs b/src/ImageProcessor/Common/Helpers/ImageMaths.cs index 0c748ebf1..fc4c42040 100644 --- a/src/ImageProcessor/Common/Helpers/ImageMaths.cs +++ b/src/ImageProcessor/Common/Helpers/ImageMaths.cs @@ -95,6 +95,44 @@ namespace ImageProcessor return 1.0f; } + /// + /// Returns the given degrees converted to radians. + /// + /// + /// The angle in degrees. + /// + /// + /// The representing the degree as radians. + /// + public static double DegreesToRadians(double angleInDegrees) + { + return angleInDegrees * (PI / 180); + } + + /// + /// Rotates one point around another + /// + /// + /// The point to rotate. + /// The rotation angle in degrees. + /// The centre point of rotation. If not set the point will equal + /// + /// + /// Rotated point + public static Point RotatePoint(Point pointToRotate, double angleInDegrees, Point? centerPoint = null) + { + Point center = centerPoint ?? Point.Empty; + + double angleInRadians = DegreesToRadians(angleInDegrees); + double cosTheta = Math.Cos(angleInRadians); + double sinTheta = Math.Sin(angleInRadians); + return new Point + { + X = (int)((cosTheta * (pointToRotate.X - center.X)) - (sinTheta * (pointToRotate.Y - center.Y)) + center.X), + Y = (int)((sinTheta * (pointToRotate.X - center.X)) + (cosTheta * (pointToRotate.Y - center.Y)) + center.Y) + }; + } + /// /// Ensures that any passed double is correctly rounded to zero /// diff --git a/src/ImageProcessor/Numerics/Rectangle.cs b/src/ImageProcessor/Numerics/Rectangle.cs index 948b233af..2be098b51 100644 --- a/src/ImageProcessor/Numerics/Rectangle.cs +++ b/src/ImageProcessor/Numerics/Rectangle.cs @@ -155,6 +155,16 @@ namespace ImageProcessor && y < this.Y + this.Height; } + /// + /// Returns the center point of the given + /// + /// The rectangle + /// + public static Point Center(Rectangle rectangle) + { + return new Point(rectangle.Left + rectangle.Width / 2, rectangle.Top + rectangle.Height / 2); + } + /// /// Indicates whether this instance and a specified object are equal. /// diff --git a/src/ImageProcessor/Samplers/ImageSampleExtensions.cs b/src/ImageProcessor/Samplers/ImageSampleExtensions.cs index eba1ff3fb..a73eb2b2c 100644 --- a/src/ImageProcessor/Samplers/ImageSampleExtensions.cs +++ b/src/ImageProcessor/Samplers/ImageSampleExtensions.cs @@ -49,7 +49,7 @@ namespace ImageProcessor.Samplers /// The public static Image Resize(this Image source, int width, int height, IResampler sampler, Rectangle sourceRectangle) { - return source.Process(width, height, sourceRectangle, new Rectangle(0, 0, width, height), new Resize(sampler)); + return source.Process(width, height, sourceRectangle, new Rectangle(0, 0, width, height), new Resampler(sampler)); } /// diff --git a/src/ImageProcessor/Samplers/Resize.cs b/src/ImageProcessor/Samplers/Resampler.cs similarity index 71% rename from src/ImageProcessor/Samplers/Resize.cs rename to src/ImageProcessor/Samplers/Resampler.cs index 6ea106724..ae3c00950 100644 --- a/src/ImageProcessor/Samplers/Resize.cs +++ b/src/ImageProcessor/Samplers/Resampler.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // @@ -10,14 +10,19 @@ namespace ImageProcessor.Samplers using System.Threading.Tasks; /// - /// Provides methods that allow the resizing of images using various resampling algorithms. + /// Provides methods that allow the resampling of images using various algorithms. /// - public class Resize : ParallelImageProcessor + public class Resampler : ParallelImageProcessor { /// /// The epsilon for comparing floating point numbers. /// - private const float Epsilon = 0.01f; + private const float Epsilon = 0.0001f; + + /// + /// The angle of rotation. + /// + private double angle; /// /// The horizontal weights. @@ -30,12 +35,12 @@ namespace ImageProcessor.Samplers private Weights[] verticalWeights; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// /// The sampler to perform the resize operation. /// - public Resize(IResampler sampler) + public Resampler(IResampler sampler) { Guard.NotNull(sampler, nameof(sampler)); @@ -47,6 +52,32 @@ namespace ImageProcessor.Samplers /// public IResampler Sampler { get; } + /// + /// Gets or sets the angle of rotation. + /// + public double Angle + { + get + { + return this.angle; + } + + set + { + if (value > 360) + { + value -= 360; + } + + if (value < 0) + { + value += 360; + } + + this.angle = value; + } + } + /// protected override void OnApply(ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) { @@ -61,6 +92,8 @@ namespace ImageProcessor.Samplers int targetBottom = targetRectangle.Bottom; int startX = targetRectangle.X; int endX = targetRectangle.Right; + Point centre = Rectangle.Center(sourceRectangle); + bool rotate = this.angle > 0 && this.angle < 360; Parallel.For( startY, @@ -82,28 +115,38 @@ namespace ImageProcessor.Samplers foreach (Weight yw in verticalValues) { - if (Math.Abs(yw.Value) < Epsilon) - { - continue; - } - int originY = yw.Index; foreach (Weight xw in horizontalValues) { - if (Math.Abs(xw.Value) < Epsilon) - { - continue; - } - int originX = xw.Index; - Color sourceColor = Color.InverseCompand(source[originX, originY]); - if (Math.Abs(sourceColor.A) < Epsilon) + Color sourceColor; + + float weight; + + if (rotate) { - continue; + // Rotating at the centre point + Point rotated = ImageMaths.RotatePoint(new Point(originX, originY), this.angle, centre); + originX = rotated.X; + originY = rotated.Y; + + // TODO: This can't work. We're not normalising properly since weights are skipped. + // Also... This is so slow! + if (sourceRectangle.Contains(originX, originY)) + { + sourceColor = Color.InverseCompand(source[originX, originY]); + weight = (yw.Value / verticalSum) * (xw.Value / horizontalSum); + + destination.R += sourceColor.R * weight; + destination.G += sourceColor.G * weight; + destination.B += sourceColor.B * weight; + destination.A += sourceColor.A * weight; + } } - float weight = (yw.Value / verticalSum) * (xw.Value / horizontalSum); + sourceColor = Color.InverseCompand(source[originX, originY]); + weight = (yw.Value / verticalSum) * (xw.Value / horizontalSum); destination.R += sourceColor.R * weight; destination.G += sourceColor.G * weight; @@ -112,9 +155,12 @@ namespace ImageProcessor.Samplers } } + destination = Color.Compand(destination); + // Ensure are alpha values only reflect possible values to prevent bleed. destination.A = (float)Math.Round(destination.A, 2); - target[x, y] = Color.Compand(destination); + + target[x, y] = destination; } } }); @@ -176,6 +222,7 @@ namespace ImageProcessor.Samplers builder.Add(new Weight(a, w)); } } + result[i].Values = builder.ToImmutable(); result[i].Sum = sum; }); diff --git a/src/ImageProcessor/project.json b/src/ImageProcessor/project.json index dc11613af..57178e751 100644 --- a/src/ImageProcessor/project.json +++ b/src/ImageProcessor/project.json @@ -19,11 +19,11 @@ "System.Runtime.Extensions": "4.0.10", "System.Reflection": "4.0.10", "System.IO": "4.0.10", - "StyleCop.Analyzers": "1.0.0-beta016", "Microsoft.NETCore": "5.0.1-beta-23409", - "Microsoft.NETCore.Platforms": "1.0.1-beta-23409" + "Microsoft.NETCore.Platforms": "1.0.1-beta-23409", + "StyleCop.Analyzers": "1.0.0-beta016" }, "frameworks": { - "dotnet": {} + "dotnet": { } } } \ No newline at end of file diff --git a/src/ImageProcessor/project.lock.json.REMOVED.git-id b/src/ImageProcessor/project.lock.json.REMOVED.git-id index 8fc61db8b..365950bb8 100644 --- a/src/ImageProcessor/project.lock.json.REMOVED.git-id +++ b/src/ImageProcessor/project.lock.json.REMOVED.git-id @@ -1 +1 @@ -0b32043447c786c20468c00c3c0924348b389517 \ No newline at end of file +e9c58eb8b9733d48d157d8bf0852daae4f60dab0 \ No newline at end of file diff --git a/tests/ImageProcessor.Tests/project.lock.json.REMOVED.git-id b/tests/ImageProcessor.Tests/project.lock.json.REMOVED.git-id index e7c0cc81c..a5a60bda7 100644 --- a/tests/ImageProcessor.Tests/project.lock.json.REMOVED.git-id +++ b/tests/ImageProcessor.Tests/project.lock.json.REMOVED.git-id @@ -1 +1 @@ -9ead0f07f8529db4e80eda95477601f6d3eb9583 \ No newline at end of file +11091fe7d5fc9cff9aec6ef60bf1c47e0e01d066 \ No newline at end of file