diff --git a/src/ImageProcessorCore/Common/Helpers/ImageMaths.cs b/src/ImageProcessorCore/Common/Helpers/ImageMaths.cs
index ab4394feb..633f7a992 100644
--- a/src/ImageProcessorCore/Common/Helpers/ImageMaths.cs
+++ b/src/ImageProcessorCore/Common/Helpers/ImageMaths.cs
@@ -13,12 +13,6 @@ namespace ImageProcessorCore
///
internal static class ImageMaths
{
- ///
- /// Represents PI, the ratio of a circle's circumference to its diameter.
- ///
- // ReSharper disable once InconsistentNaming
- public const float PI = 3.1415926535897931f;
-
///
/// Returns how many bits are required to store the specified number of colors.
/// Performs a Log2() on the value.
@@ -41,7 +35,7 @@ namespace ImageProcessorCore
public static float Gaussian(float x, float sigma)
{
const float Numerator = 1.0f;
- float denominator = (float)(Math.Sqrt(2 * PI) * sigma);
+ float denominator = (float)(Math.Sqrt(2 * Math.PI) * sigma);
float exponentNumerator = -x * x;
float exponentDenominator = (float)(2 * Math.Pow(sigma, 2));
@@ -90,9 +84,7 @@ namespace ImageProcessorCore
///
/// Gets the result of a sine cardinal function for the given value.
///
- ///
- /// The value to calculate the result for.
- ///
+ /// The value to calculate the result for.
///
/// The .
///
@@ -102,7 +94,7 @@ namespace ImageProcessorCore
if (Math.Abs(x) > Epsilon)
{
- x *= PI;
+ x *= (float)Math.PI;
return Clean((float)Math.Sin(x) / x);
}
@@ -118,7 +110,7 @@ namespace ImageProcessorCore
///
public static float DegreesToRadians(float degrees)
{
- return degrees * (PI / 180);
+ return degrees * (float)(Math.PI / 180);
}
///
@@ -138,12 +130,16 @@ 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)
+ ///
+ /// Gets the bounding from the given matrix.
+ ///
+ /// The source rectangle.
+ /// The transformation matrix.
+ ///
+ /// The .
+ ///
+ public static Rectangle GetBoundingRectangle(Rectangle rectangle, Matrix3x2 matrix)
{
- 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);
@@ -152,55 +148,16 @@ namespace ImageProcessorCore
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.
- ///
- /// The width of the image.
- /// The height of the image.
- /// The angle of rotation.
- /// The new size of the image
- public static Rectangle GetBoundingRotatedRectangle(int width, int height, float angleInDegrees)
- {
- // Check first clockwise.
- double radians = DegreesToRadians(angleInDegrees);
- double radiansSin = Math.Sin(radians);
- double radiansCos = Math.Cos(radians);
- double width1 = (height * radiansSin) + (width * radiansCos);
- double height1 = (width * radiansSin) + (height * radiansCos);
-
- // Find dimensions in the other direction
- radiansSin = Math.Sin(-radians);
- radiansCos = Math.Cos(-radians);
- double width2 = (height * radiansSin) + (width * radiansCos);
- double height2 = (width * radiansSin) + (height * radiansCos);
-
- // Get the external vertex for the rotation
- Rectangle result = new Rectangle(
- 0,
- 0,
- Convert.ToInt32(Math.Max(Math.Abs(width1), Math.Abs(width2))),
- Convert.ToInt32(Math.Max(Math.Abs(height1), Math.Abs(height2))));
-
- return result;
+ return new Rectangle(0, 0, (int)(max.X - min.X), (int)(max.Y - min.Y));
}
///
/// Finds the bounding rectangle based on the first instance of any color component other
/// than the given one.
///
- ///
- /// The to search within.
- ///
- ///
- /// The color component value to remove.
- ///
- ///
- /// The channel to test against.
- ///
+ /// The to search within.
+ /// The color component value to remove.
+ /// The channel to test against.
///
/// The .
///
diff --git a/src/ImageProcessorCore/Numerics/Point.cs b/src/ImageProcessorCore/Numerics/Point.cs
index f1fe3c5ec..818002f9e 100644
--- a/src/ImageProcessorCore/Numerics/Point.cs
+++ b/src/ImageProcessorCore/Numerics/Point.cs
@@ -2,7 +2,6 @@
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
//
-
namespace ImageProcessorCore
{
using System;
@@ -166,7 +165,7 @@ namespace ImageProcessorCore
/// The rotation
public static Matrix3x2 CreateRotation(Point origin, float degrees)
{
- float radians = (float)ImageMaths.DegreesToRadians(degrees);
+ float radians = ImageMaths.DegreesToRadians(degrees);
return Matrix3x2.CreateRotation(radians, origin.backingVector);
}
@@ -202,8 +201,8 @@ namespace ImageProcessorCore
/// The rotation
public static Matrix3x2 CreateSkew(Point origin, float degreesX, float degreesY)
{
- float radiansX = (float)ImageMaths.DegreesToRadians(degreesX);
- float radiansY = (float)ImageMaths.DegreesToRadians(degreesY);
+ float radiansX = ImageMaths.DegreesToRadians(degreesX);
+ float radiansY = ImageMaths.DegreesToRadians(degreesY);
return Matrix3x2.CreateSkew(radiansX, radiansY, origin.backingVector);
}
diff --git a/src/ImageProcessorCore/Quantizers/Octree/Quantizer.cs b/src/ImageProcessorCore/Quantizers/Octree/Quantizer.cs
index 0db212da5..268772cb8 100644
--- a/src/ImageProcessorCore/Quantizers/Octree/Quantizer.cs
+++ b/src/ImageProcessorCore/Quantizers/Octree/Quantizer.cs
@@ -5,7 +5,6 @@
namespace ImageProcessorCore.Quantizers
{
- using System;
using System.Collections.Generic;
using System.Threading.Tasks;
diff --git a/src/ImageProcessorCore/Samplers/ImageSamplerExtensions.cs b/src/ImageProcessorCore/Samplers/ImageSamplerExtensions.cs
index 45dc3c913..09e059263 100644
--- a/src/ImageProcessorCore/Samplers/ImageSamplerExtensions.cs
+++ b/src/ImageProcessorCore/Samplers/ImageSamplerExtensions.cs
@@ -282,7 +282,7 @@ namespace ImageProcessorCore.Samplers
}
///
- /// Skews an image by the given angles in degrees.
+ /// Skews an image by the given angles in degrees, expanding the image to fit the skewed result.
///
/// The image to skew.
/// The angle in degrees to perform the rotation along the x-axis.
@@ -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, Point.Empty, progressHandler);
+ return Skew(source, degreesX, degreesY, Point.Empty, true, progressHandler);
}
///
@@ -301,11 +301,12 @@ namespace ImageProcessorCore.Samplers
/// 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, ProgressEventHandler progressHandler = null)
+ public static Image Skew(this Image source, float degreesX, float degreesY, Point center, bool expand, ProgressEventHandler progressHandler = null)
{
- Skew processor = new Skew { AngleX = degreesX, AngleY = degreesY, Center = center };
+ Skew processor = new Skew { AngleX = degreesX, AngleY = degreesY, Center = center, Expand = expand };
processor.OnProgress += progressHandler;
try
diff --git a/src/ImageProcessorCore/Samplers/Rotate.cs b/src/ImageProcessorCore/Samplers/Rotate.cs
index a14b3cee1..e8c823723 100644
--- a/src/ImageProcessorCore/Samplers/Rotate.cs
+++ b/src/ImageProcessorCore/Samplers/Rotate.cs
@@ -69,9 +69,10 @@ namespace ImageProcessorCore.Samplers
// 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);
- Rectangle rectangle2 = ImageMaths.GetBoundingRotatedRectangle(sourceRectangle, -this.angle, this.Center);
+ // First find out how big the target rectangle should be.
+ Point centre = this.Center == Point.Empty ? Rectangle.Center(sourceRectangle) : this.Center;
+ Matrix3x2 rotation = Point.CreateRotation(centre, -this.angle);
+ Rectangle rectangle = ImageMaths.GetBoundingRectangle(sourceRectangle, rotation);
ResizeOptions options = new ResizeOptions
{
Size = new Size(rectangle.Width, rectangle.Height),
@@ -96,30 +97,23 @@ namespace ImageProcessorCore.Samplers
///
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 height = 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.CreateRotation(centre, negativeAngle);
+ 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.
+ // Since we are not working in parallel we use full height and width
+ // of the first pass image.
Parallel.For(
0,
- targetHeight,
+ height,
y =>
{
- // Y coordinates of source points
- int originY = y - targetY;
-
for (int x = startX; x < endX; x++)
{
- // X coordinates of source points
- int originX = x - startX;
-
// Rotate at the centre point
- Point rotated = Point.Rotate(new Point(originX, originY), rotation);
+ Point rotated = Point.Rotate(new Point(x, y), rotation);
if (this.firstPass.Bounds.Contains(rotated.X, rotated.Y))
{
target[x, y] = this.firstPass[rotated.X, rotated.Y];
diff --git a/src/ImageProcessorCore/Samplers/Skew.cs b/src/ImageProcessorCore/Samplers/Skew.cs
index 1d88811d1..9248b3016 100644
--- a/src/ImageProcessorCore/Samplers/Skew.cs
+++ b/src/ImageProcessorCore/Samplers/Skew.cs
@@ -13,6 +13,11 @@ namespace ImageProcessorCore.Samplers
///
public class Skew : ImageSampler
{
+ ///
+ /// The image used for storing the first pass pixels.
+ ///
+ private Image firstPass;
+
///
/// The angle of rotation along the x-axis.
///
@@ -23,6 +28,9 @@ namespace ImageProcessorCore.Samplers
///
private float angleY;
+ ///
+ public override int Parallelism { get; set; } = 1;
+
///
/// Gets or sets the angle of rotation along the x-axis in degrees.
///
@@ -80,44 +88,77 @@ namespace ImageProcessorCore.Samplers
///
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; }
+
///
- 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 negativeAngleX = -this.angleX;
- float negativeAngleY = -this.angleY;
- Point centre = this.Center == Point.Empty ? Rectangle.Center(sourceRectangle) : this.Center;
- Matrix3x2 skew = Point.CreateSkew(centre, negativeAngleX, negativeAngleY);
+ // 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;
+ 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 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);
+ }
+ }
+ ///
+ protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY)
+ {
+ int height = this.firstPass.Height;
+ int startX = this.firstPass.Bounds.X;
+ int endX = this.firstPass.Bounds.Right;
+ 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.
Parallel.For(
- startY,
- endY,
+ 0,
+ height,
y =>
{
- if (y >= targetY && y < targetBottom)
+ for (int x = startX; x < endX; x++)
{
- // Y coordinates of source points
- int originY = y - targetY;
-
- 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))
{
- // X coordinates of source points
- int originX = x - startX;
-
- // Skew at the centre point
- Point skewed = Point.Skew(new Point(originX, originY), skew);
- if (sourceRectangle.Contains(skewed.X, skewed.Y))
- {
- target[x, y] = source[skewed.X, skewed.Y];
- }
+ target[x, y] = this.firstPass[skewed.X, skewed.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