Browse Source

Better Rotate, almost there with Skew.

Former-commit-id: acc64fb7b84199565b20e48acc02e2bf85f8251b
Former-commit-id: 217dd2c5723f414b60b8f46b03876ce9c873fe44
Former-commit-id: f600d8f9917d15984aa601b8ece53ec63af973e2
pull/1/head
James Jackson-South 10 years ago
parent
commit
8929cb368f
  1. 77
      src/ImageProcessorCore/Common/Helpers/ImageMaths.cs
  2. 7
      src/ImageProcessorCore/Numerics/Point.cs
  3. 1
      src/ImageProcessorCore/Quantizers/Octree/Quantizer.cs
  4. 9
      src/ImageProcessorCore/Samplers/ImageSamplerExtensions.cs
  5. 26
      src/ImageProcessorCore/Samplers/Rotate.cs
  6. 95
      src/ImageProcessorCore/Samplers/Skew.cs

77
src/ImageProcessorCore/Common/Helpers/ImageMaths.cs

@ -13,12 +13,6 @@ namespace ImageProcessorCore
/// </summary>
internal static class ImageMaths
{
/// <summary>
/// Represents PI, the ratio of a circle's circumference to its diameter.
/// </summary>
// ReSharper disable once InconsistentNaming
public const float PI = 3.1415926535897931f;
/// <summary>
/// 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
/// <summary>
/// Gets the result of a sine cardinal function for the given value.
/// </summary>
/// <param name="x">
/// The value to calculate the result for.
/// </param>
/// <param name="x">The value to calculate the result for.</param>
/// <returns>
/// The <see cref="float"/>.
/// </returns>
@ -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
/// </returns>
public static float DegreesToRadians(float degrees)
{
return degrees * (PI / 180);
return degrees * (float)(Math.PI / 180);
}
/// <summary>
@ -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)
/// <summary>
/// Gets the bounding <see cref="Rectangle"/> from the given matrix.
/// </summary>
/// <param name="rectangle">The source rectangle.</param>
/// <param name="matrix">The transformation matrix.</param>
/// <returns>
/// The <see cref="Rectangle"/>.
/// </returns>
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));
}
/// <summary>
/// Calculates the new size after rotation.
/// </summary>
/// <param name="width">The width of the image.</param>
/// <param name="height">The height of the image.</param>
/// <param name="angleInDegrees">The angle of rotation.</param>
/// <returns>The new size of the image</returns>
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));
}
/// <summary>
/// Finds the bounding rectangle based on the first instance of any color component other
/// than the given one.
/// </summary>
/// <param name="bitmap">
/// The <see cref="Image"/> to search within.
/// </param>
/// <param name="componentValue">
/// The color component value to remove.
/// </param>
/// <param name="channel">
/// The <see cref="RgbaComponent"/> channel to test against.
/// </param>
/// <param name="bitmap">The <see cref="Image"/> to search within.</param>
/// <param name="componentValue">The color component value to remove.</param>
/// <param name="channel">The <see cref="RgbaComponent"/> channel to test against.</param>
/// <returns>
/// The <see cref="Rectangle"/>.
/// </returns>

7
src/ImageProcessorCore/Numerics/Point.cs

@ -2,7 +2,6 @@
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessorCore
{
using System;
@ -166,7 +165,7 @@ namespace ImageProcessorCore
/// <returns>The rotation <see cref="Matrix3x2"/></returns>
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
/// <returns>The rotation <see cref="Matrix3x2"/></returns>
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);
}

1
src/ImageProcessorCore/Quantizers/Octree/Quantizer.cs

@ -5,7 +5,6 @@
namespace ImageProcessorCore.Quantizers
{
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

9
src/ImageProcessorCore/Samplers/ImageSamplerExtensions.cs

@ -282,7 +282,7 @@ namespace ImageProcessorCore.Samplers
}
/// <summary>
/// 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.
/// </summary>
/// <param name="source">The image to skew.</param>
/// <param name="degreesX">The angle in degrees to perform the rotation along the x-axis.</param>
@ -291,7 +291,7 @@ namespace ImageProcessorCore.Samplers
/// <returns>The <see cref="Image"/></returns>
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);
}
/// <summary>
@ -301,11 +301,12 @@ namespace ImageProcessorCore.Samplers
/// <param name="degreesX">The angle in degrees to perform the rotation along the x-axis.</param>
/// <param name="degreesY">The angle in degrees to perform the rotation along the y-axis.</param>
/// <param name="center">The center point at which to skew the image.</param>
/// <param name="expand">Whether to expand the image to fit the skewed result.</param>
/// <param name="progressHandler">A delegate which is called as progress is made processing the image.</param>
/// <returns>The <see cref="Image"/></returns>
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

26
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
/// <inheritdoc/>
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];

95
src/ImageProcessorCore/Samplers/Skew.cs

@ -13,6 +13,11 @@ namespace ImageProcessorCore.Samplers
/// </summary>
public class Skew : ImageSampler
{
/// <summary>
/// The image used for storing the first pass pixels.
/// </summary>
private Image firstPass;
/// <summary>
/// The angle of rotation along the x-axis.
/// </summary>
@ -23,6 +28,9 @@ namespace ImageProcessorCore.Samplers
/// </summary>
private float angleY;
/// <inheritdoc/>
public override int Parallelism { get; set; } = 1;
/// <summary>
/// Gets or sets the angle of rotation along the x-axis in degrees.
/// </summary>
@ -80,44 +88,77 @@ namespace ImageProcessorCore.Samplers
/// </summary>
public Point Center { get; set; }
/// <summary>
/// Gets or sets a value indicating whether to expand the canvas to fit the skewed image.
/// </summary>
public bool Expand { get; set; }
/// <inheritdoc/>
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);
}
}
/// <inheritdoc/>
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();
});
}
/// <inheritdoc/>
protected override void AfterApply(ImageBase source, ImageBase target, Rectangle targetRectangle, Rectangle sourceRectangle)
{
// Cleanup.
this.firstPass.Dispose();
}
}
}
Loading…
Cancel
Save