Browse Source

Clean up primitives

Former-commit-id: 5cf1ec2ba4cf646e94524fb425951760724b3eae
Former-commit-id: cc0f2177a28de32d6df34b0d23bfcba3ec898c0c
Former-commit-id: 8f2227320a0891bc824fb7256b97d536f381a76a
af/merge-core
James Jackson-South 10 years ago
parent
commit
756f5b6a93
  1. 8
      src/ImageProcessor/Colors/Color.cs
  2. 2
      src/ImageProcessor/Colors/Colorspaces/Bgra32.cs
  3. 2
      src/ImageProcessor/Colors/Colorspaces/CieLab.cs
  4. 2
      src/ImageProcessor/Colors/Colorspaces/CieXyz.cs
  5. 2
      src/ImageProcessor/Colors/Colorspaces/Cmyk.cs
  6. 2
      src/ImageProcessor/Colors/Colorspaces/Hsl.cs
  7. 2
      src/ImageProcessor/Colors/Colorspaces/Hsv.cs
  8. 2
      src/ImageProcessor/Colors/Colorspaces/YCbCr.cs
  9. 27
      src/ImageProcessor/Common/Helpers/ImageMaths.cs
  10. 2
      src/ImageProcessor/Filters/Glow.cs
  11. 2
      src/ImageProcessor/Filters/Vignette.cs
  12. 60
      src/ImageProcessor/Numerics/Ellipse.cs
  13. 155
      src/ImageProcessor/Numerics/Point.cs
  14. 188
      src/ImageProcessor/Numerics/Rectangle.cs
  15. 219
      src/ImageProcessor/Numerics/RotatedRectangle.cs
  16. 150
      src/ImageProcessor/Numerics/Size.cs
  17. 25
      src/ImageProcessor/Samplers/Resampler.cs
  18. 22
      tests/ImageProcessor.Tests/Processors/Samplers/SamplerTests.cs

8
src/ImageProcessor/Colors/Color.cs

@ -109,9 +109,7 @@ namespace ImageProcessor
/// <summary>
/// Initializes a new instance of the <see cref="Color"/> struct.
/// </summary>
/// <param name="vector">
/// The vector.
/// </param>
/// <param name="vector">The vector.</param>
public Color(Vector4 vector)
{
this.backingVector = vector;
@ -208,7 +206,7 @@ namespace ImageProcessor
/// Gets a value indicating whether this <see cref="Color"/> is empty.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public bool IsEmpty => this.backingVector.Equals(default(Vector4));
public bool IsEmpty => this.Equals(Empty);
/// <summary>
/// Gets this color with the component values clamped from 0 to 1.
@ -308,7 +306,7 @@ namespace ImageProcessor
}
/// <summary>
/// Compares two <see cref="Hsv"/> objects for inequality.
/// Compares two <see cref="Color"/> objects for inequality.
/// </summary>
/// <param name="left">
/// The <see cref="Color"/> on the left side of the operand.

2
src/ImageProcessor/Colors/Colorspaces/Bgra32.cs

@ -80,7 +80,7 @@ namespace ImageProcessor
/// Gets a value indicating whether this <see cref="Bgra32"/> is empty.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public bool IsEmpty => this.backingVector.Equals(default(Vector4));
public bool IsEmpty => this.Equals(Empty);
/// <summary>
/// Allows the implicit conversion of an instance of <see cref="Color"/> to a

2
src/ImageProcessor/Colors/Colorspaces/CieLab.cs

@ -66,7 +66,7 @@ namespace ImageProcessor
/// Gets a value indicating whether this <see cref="CieLab"/> is empty.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public bool IsEmpty => this.backingVector.Equals(default(Vector3));
public bool IsEmpty => this.Equals(Empty);
/// <summary>
/// Allows the implicit conversion of an instance of <see cref="Color"/> to a

2
src/ImageProcessor/Colors/Colorspaces/CieXyz.cs

@ -67,7 +67,7 @@ namespace ImageProcessor
/// Gets a value indicating whether this <see cref="CieXyz"/> is empty.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public bool IsEmpty => this.backingVector.Equals(default(Vector3));
public bool IsEmpty => this.Equals(Empty);
/// <summary>
/// Allows the implicit conversion of an instance of <see cref="Color"/> to a

2
src/ImageProcessor/Colors/Colorspaces/Cmyk.cs

@ -73,7 +73,7 @@ namespace ImageProcessor
/// Gets a value indicating whether this <see cref="Cmyk"/> is empty.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public bool IsEmpty => this.backingVector.Equals(default(Vector4));
public bool IsEmpty => this.Equals(Empty);
/// <summary>
/// Allows the implicit conversion of an instance of <see cref="Color"/> to a

2
src/ImageProcessor/Colors/Colorspaces/Hsl.cs

@ -64,7 +64,7 @@ namespace ImageProcessor
/// Gets a value indicating whether this <see cref="Hsl"/> is empty.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public bool IsEmpty => this.backingVector.Equals(default(Vector3));
public bool IsEmpty => this.Equals(Empty);
/// <summary>
/// Allows the implicit conversion of an instance of <see cref="Color"/> to a

2
src/ImageProcessor/Colors/Colorspaces/Hsv.cs

@ -64,7 +64,7 @@ namespace ImageProcessor
/// Gets a value indicating whether this <see cref="Hsv"/> is empty.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public bool IsEmpty => this.backingVector.Equals(default(Vector3));
public bool IsEmpty => this.Equals(Empty);
/// <summary>
/// Allows the implicit conversion of an instance of <see cref="Color"/> to a

2
src/ImageProcessor/Colors/Colorspaces/YCbCr.cs

@ -67,7 +67,7 @@ namespace ImageProcessor
/// Gets a value indicating whether this <see cref="YCbCr"/> is empty.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public bool IsEmpty => this.backingVector.Equals(default(Vector3));
public bool IsEmpty => this.Equals(Empty);
/// <summary>
/// Allows the implicit conversion of an instance of <see cref="Color"/> to a

27
src/ImageProcessor/Common/Helpers/ImageMaths.cs

@ -110,33 +110,6 @@ namespace ImageProcessor
return angleInDegrees * (PI / 180);
}
/// <summary>
/// Rotates one point around another
/// <see href="http://stackoverflow.com/a/13695630/82333"/>
/// </summary>
/// <param name="point">The point to rotate.</param>
/// <param name="origin">The origin point of rotation.</param>
/// <param name="degrees">The rotation angle in degrees.</param>
/// <returns><see cref="Vector2"/></returns>
public static Vector2 RotatePoint(Vector2 point, Vector2 origin, float degrees)
{
double radians = DegreesToRadians(degrees);
double cosTheta = Math.Cos(radians);
double sinTheta = Math.Sin(radians);
Vector2 translatedPoint = new Vector2
{
X = (float)(origin.X
+ (point.X - origin.X) * cosTheta
- (point.Y - origin.Y) * sinTheta),
Y = (float)(origin.Y
+ (point.Y - origin.Y) * cosTheta
+ (point.X - origin.X) * sinTheta)
};
return translatedPoint;
}
/// <summary>
/// Gets the bounding <see cref="Rectangle"/> from the given points.
/// </summary>

2
src/ImageProcessor/Filters/Glow.cs

@ -35,7 +35,7 @@ namespace ImageProcessor.Filters
int startX = sourceRectangle.X;
int endX = sourceRectangle.Right;
Color color = this.Color;
Vector2 centre = Rectangle.Center(targetRectangle);
Vector2 centre = Rectangle.Center(targetRectangle).ToVector2();
float rX = this.RadiusX > 0 ? this.RadiusX : targetRectangle.Width / 2f;
float rY = this.RadiusY > 0 ? this.RadiusY : targetRectangle.Height / 2f;
float maxDistance = (float)Math.Sqrt(rX * rX + rY * rY);

2
src/ImageProcessor/Filters/Vignette.cs

@ -35,7 +35,7 @@ namespace ImageProcessor.Filters
int startX = sourceRectangle.X;
int endX = sourceRectangle.Right;
Color color = this.Color;
Vector2 centre = Rectangle.Center(targetRectangle);
Vector2 centre = Rectangle.Center(targetRectangle).ToVector2();
float rX = this.RadiusX > 0 ? this.RadiusX : targetRectangle.Width / 2f;
float rY = this.RadiusY > 0 ? this.RadiusY : targetRectangle.Height / 2f;
float maxDistance = (float)Math.Sqrt(rX * rX + rY * rY);

60
src/ImageProcessor/Numerics/Ellipse.cs

@ -16,11 +16,6 @@ namespace ImageProcessor
/// </summary>
private Point center;
/// <summary>
/// The epsilon for comparing floating point numbers.
/// </summary>
private const float Epsilon = 0.0001f;
/// <summary>
/// Represents a <see cref="Ellipse"/> that has X and Y values set to zero.
/// </summary>
@ -47,9 +42,7 @@ namespace ImageProcessor
/// Gets a value indicating whether this <see cref="Ellipse"/> is empty.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public bool IsEmpty => this.center.IsEmpty
&& Math.Abs(this.RadiusX) < Epsilon
&& Math.Abs(this.RadiusY) < Epsilon;
public bool IsEmpty => this.Equals(Empty);
/// <summary>
/// Compares two <see cref="Ellipse"/> objects for equality.
@ -109,34 +102,16 @@ namespace ImageProcessor
return false;
}
//This is a more general form of the circle equation
// TODO: SIMD?
// This is a more general form of the circle equation
// X^2/a^2 + Y^2/b^2 <= 1
Point normalized = new Point(x - this.center.X, y - this.center.Y);
int nX = normalized.X;
int nY = normalized.Y;
//return (double)(nX * nX) / (this.RadiusX * this.RadiusX)
// + (double)(nY * nY) / (this.RadiusY * this.RadiusY)
// <= 1.0;
return ((double)(nX * nX) / (this.RadiusX * this.RadiusX))
+ ((double)(nY * nY) / (this.RadiusY * this.RadiusY))
<= 1.0;
}
/// <inheritdoc/>
public override bool Equals(object obj)
{
if (!(obj is Ellipse))
{
return false;
}
Ellipse other = (Ellipse)obj;
return other.center == this.center
&& Math.Abs(other.RadiusX - this.RadiusX) < Epsilon
&& Math.Abs(other.RadiusY - this.RadiusY) < Epsilon;
return (double)(nX * nX) / (this.RadiusX * this.RadiusX)
+ (double)(nY * nY) / (this.RadiusY * this.RadiusY)
<= 1.0;
}
/// <inheritdoc/>
@ -157,30 +132,41 @@ namespace ImageProcessor
$"Ellipse [ RadiusX={this.RadiusX}, RadiusY={this.RadiusX}, Centre={this.center.X},{this.center.Y} ]";
}
/// <inheritdoc/>
public override bool Equals(object obj)
{
if (obj is Ellipse)
{
return this.Equals((Ellipse)obj);
}
return false;
}
/// <inheritdoc/>
public bool Equals(Ellipse other)
{
return this.center.Equals(other.center)
&& this.RadiusX.Equals(other.RadiusX)
&& this.RadiusX.Equals(other.RadiusY);
&& this.RadiusY.Equals(other.RadiusY);
}
/// <summary>
/// Returns the hash code for this instance.
/// </summary>
/// <param name="point">
/// <param name="ellipse">
/// The instance of <see cref="Point"/> to return the hash code for.
/// </param>
/// <returns>
/// A 32-bit signed integer that is the hash code for this instance.
/// </returns>
private int GetHashCode(Ellipse point)
private int GetHashCode(Ellipse ellipse)
{
unchecked
{
int hashCode = point.center.GetHashCode();
hashCode = (hashCode * 397) ^ point.RadiusX.GetHashCode();
hashCode = (hashCode * 397) ^ point.RadiusY.GetHashCode();
int hashCode = ellipse.center.GetHashCode();
hashCode = (hashCode * 397) ^ ellipse.RadiusX.GetHashCode();
hashCode = (hashCode * 397) ^ ellipse.RadiusY.GetHashCode();
return hashCode;
}
}

155
src/ImageProcessor/Numerics/Point.cs

@ -7,6 +7,7 @@ namespace ImageProcessor
{
using System;
using System.ComponentModel;
using System.Numerics;
/// <summary>
/// Represents an ordered pair of integer x- and y-coordinates that defines a point in
@ -23,37 +24,99 @@ namespace ImageProcessor
/// </summary>
public static readonly Point Empty = default(Point);
/// <summary>
/// The backing vector for SIMD support.
/// </summary>
private Vector2 backingVector;
/// <summary>
/// Initializes a new instance of the <see cref="Point"/> struct.
/// </summary>
/// <param name="x">The horizontal position of the point.</param>
/// <param name="y">The vertical position of the point.</param>
public Point(int x, int y)
: this()
{
this.backingVector = new Vector2(x, y);
}
/// <summary>
/// Initializes a new instance of the <see cref="Point"/> struct.
/// </summary>
/// <param name="vector">
/// The vector representing the width and height.
/// </param>
public Point(Vector2 vector)
{
this.X = x;
this.Y = y;
this.backingVector = new Vector2(vector.X, vector.Y);
}
/// <summary>
/// The x-coordinate of this <see cref="Point"/>.
/// </summary>
public int X { get; set; }
public int X
{
get
{
return (int)this.backingVector.X;
}
set
{
this.backingVector.X = value;
}
}
/// <summary>
/// The y-coordinate of this <see cref="Point"/>.
/// </summary>
public int Y { get; set; }
public int Y
{
get
{
return (int)this.backingVector.Y;
}
set
{
this.backingVector.Y = value;
}
}
/// <summary>
/// Gets a value indicating whether this <see cref="Point"/> is empty.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public bool IsEmpty => this.X == 0 && this.Y == 0;
public bool IsEmpty => this.Equals(Empty);
/// <summary>
/// Computes the sum of adding two points.
/// </summary>
/// <param name="left">The point on the left hand of the operand.</param>
/// <param name="right">The point on the right hand of the operand.</param>
/// <returns>
/// The <see cref="Point"/>
/// </returns>
public static Point operator +(Point left, Point right)
{
return new Point(left.backingVector + right.backingVector);
}
/// <summary>
/// Computes the difference left by subtracting one point from another.
/// </summary>
/// <param name="left">The point on the left hand of the operand.</param>
/// <param name="right">The point on the right hand of the operand.</param>
/// <returns>
/// The <see cref="Point"/>
/// </returns>
public static Point operator -(Point left, Point right)
{
return new Point(left.backingVector - right.backingVector);
}
/// <summary>
/// Compares two <see cref="Point"/> objects. The result specifies whether the values
/// of the <see cref="Point.X"/> or <see cref="Point.Y"/> properties of the two
/// <see cref="Point"/> objects are equal.
/// Compares two <see cref="Point"/> objects for equality.
/// </summary>
/// <param name="left">
/// The <see cref="Point"/> on the left side of the operand.
@ -70,9 +133,7 @@ namespace ImageProcessor
}
/// <summary>
/// Compares two <see cref="Point"/> objects. The result specifies whether the values
/// of the <see cref="Point.X"/> or <see cref="Point.Y"/> properties of the two
/// <see cref="Point"/> objects are unequal.
/// Compares two <see cref="Point"/> objects for inequality.
/// </summary>
/// <param name="left">
/// The <see cref="Point"/> on the left side of the operand.
@ -89,44 +150,34 @@ namespace ImageProcessor
}
/// <summary>
/// Indicates whether this instance and a specified object are equal.
/// Gets a <see cref="Vector2"/> representation for this <see cref="Point"/>.
/// </summary>
/// <param name="obj">
/// The object to compare with the current instance.
/// </param>
/// <returns>
/// true if <paramref name="obj"/> and this instance are the same type and represent the
/// same value; otherwise, false.
/// </returns>
public override bool Equals(object obj)
/// <returns>A <see cref="Vector2"/> representation for this object.</returns>
public Vector2 ToVector2()
{
if (!(obj is Point))
{
return false;
}
Point other = (Point)obj;
return other.X == this.X && other.Y == this.Y;
return new Vector2(this.X, this.Y);
}
/// <summary>
/// Returns the hash code for this instance.
/// Rotates a point around a given origin by the specified angle in degrees.
/// </summary>
/// <returns>
/// A 32-bit signed integer that is the hash code for this instance.
/// </returns>
/// <param name="point">The point to rotate</param>
/// <param name="origin">The center point to rotate around.</param>
/// <param name="degrees">The angle in degrees.</param>
/// <returns></returns>
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)));
}
/// <inheritdoc/>
public override int GetHashCode()
{
return this.GetHashCode(this);
}
/// <summary>
/// Returns the fully qualified type name of this instance.
/// </summary>
/// <returns>
/// A <see cref="T:System.String"/> containing a fully qualified type name.
/// </returns>
/// <inheritdoc/>
public override string ToString()
{
if (this.IsEmpty)
@ -138,16 +189,21 @@ namespace ImageProcessor
$"Point [ X={this.X}, Y={this.Y} ]";
}
/// <summary>
/// Indicates whether the current object is equal to another object of the same type.
/// </summary>
/// <returns>
/// True if the current object is equal to the <paramref name="other"/> parameter; otherwise, false.
/// </returns>
/// <param name="other">An object to compare with this object.</param>
/// <inheritdoc/>
public override bool Equals(object obj)
{
if (obj is Point)
{
return this.Equals((Point)obj);
}
return false;
}
/// <inheritdoc/>
public bool Equals(Point other)
{
return this.X.Equals(other.X) && this.Y.Equals(other.Y);
return this.backingVector.Equals(other.backingVector);
}
/// <summary>
@ -161,12 +217,7 @@ namespace ImageProcessor
/// </returns>
private int GetHashCode(Point point)
{
unchecked
{
int hashCode = point.X.GetHashCode();
hashCode = (hashCode * 397) ^ point.Y.GetHashCode();
return hashCode;
}
return point.backingVector.GetHashCode();
}
}
}

188
src/ImageProcessor/Numerics/Rectangle.cs

@ -23,6 +23,11 @@ namespace ImageProcessor
/// </summary>
public static readonly Rectangle Empty = default(Rectangle);
/// <summary>
/// The backing vector for SIMD support.
/// </summary>
private Vector4 backingVector;
/// <summary>
/// Initializes a new instance of the <see cref="Rectangle"/> struct.
/// </summary>
@ -32,10 +37,7 @@ namespace ImageProcessor
/// <param name="height">The height of the rectangle.</param>
public Rectangle(int x, int y, int width, int height)
{
this.X = x;
this.Y = y;
this.Width = width;
this.Height = height;
this.backingVector = new Vector4(x, y, width, height);
}
/// <summary>
@ -49,37 +51,87 @@ namespace ImageProcessor
/// </param>
public Rectangle(Point point, Size size)
{
this.X = point.X;
this.Y = point.Y;
this.Width = size.Width;
this.Height = size.Height;
this.backingVector = new Vector4(point.X, point.Y, size.Width, size.Height);
}
/// <summary>
/// Initializes a new instance of the <see cref="Rectangle"/> struct.
/// </summary>
/// <param name="vector">The vector.</param>
public Rectangle(Vector4 vector)
{
this.backingVector = vector;
}
/// <summary>
/// The x-coordinate of this <see cref="Rectangle"/>.
/// </summary>
public int X { get; set; }
public int X
{
get
{
return (int)this.backingVector.X;
}
set
{
this.backingVector.X = value;
}
}
/// <summary>
/// The y-coordinate of this <see cref="Rectangle"/>.
/// </summary>
public int Y { get; set; }
public int Y
{
get
{
return (int)this.backingVector.Y;
}
set
{
this.backingVector.Y = value;
}
}
/// <summary>
/// The width of this <see cref="Rectangle"/>.
/// </summary>
public int Width { get; set; }
public int Width
{
get
{
return (int)this.backingVector.W;
}
set
{
this.backingVector.W = value;
}
}
/// <summary>
/// The height of this <see cref="Rectangle"/>.
/// </summary>
public int Height { get; set; }
public int Height
{
get
{
return (int)this.backingVector.Z;
}
set
{
this.backingVector.Z = value;
}
}
/// <summary>
/// Gets a value indicating whether this <see cref="Rectangle"/> is empty.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public bool IsEmpty => this.X == 0 && this.Y == 0 && this.Width == 0 && this.Height == 0;
public bool IsEmpty => this.Equals(Empty);
/// <summary>
/// Gets the y-coordinate of the top edge of this <see cref="Rectangle"/>.
@ -102,10 +154,33 @@ namespace ImageProcessor
public int Left => this.X;
/// <summary>
/// Compares two <see cref="Rectangle"/> objects. The result specifies whether the values
/// of the <see cref="Rectangle.X"/>, <see cref="Rectangle.Y"/>, <see cref="Rectangle.Width"/>,
/// and the <see cref="Rectangle.Height"/>properties of the two
/// <see cref="Rectangle"/> objects are equal.
/// Computes the sum of adding two rectangles.
/// </summary>
/// <param name="left">The rectangle on the left hand of the operand.</param>
/// <param name="right">The rectangle on the right hand of the operand.</param>
/// <returns>
/// The <see cref="Rectangle"/>
/// </returns>
public static Rectangle operator +(Rectangle left, Rectangle right)
{
return new Rectangle(left.backingVector + right.backingVector);
}
/// <summary>
/// Computes the difference left by subtracting one rectangle from another.
/// </summary>
/// <param name="left">The rectangle on the left hand of the operand.</param>
/// <param name="right">The rectangle on the right hand of the operand.</param>
/// <returns>
/// The <see cref="Rectangle"/>
/// </returns>
public static Rectangle operator -(Rectangle left, Rectangle right)
{
return new Rectangle(left.backingVector - right.backingVector);
}
/// <summary>
/// Compares two <see cref="Rectangle"/> objects for equality.
/// </summary>
/// <param name="left">
/// The <see cref="Rectangle"/> on the left side of the operand.
@ -122,10 +197,7 @@ namespace ImageProcessor
}
/// <summary>
/// Compares two <see cref="Rectangle"/> objects. The result specifies whether the values
/// of the <see cref="Rectangle.X"/>, <see cref="Rectangle.Y"/>, <see cref="Rectangle.Width"/>,
/// and the <see cref="Rectangle.Height"/>properties of the two
/// <see cref="Rectangle"/> objects are unequal.
/// Compares two <see cref="Rectangle"/> objects for inequality.
/// </summary>
/// <param name="left">
/// The <see cref="Rectangle"/> on the left side of the operand.
@ -150,6 +222,7 @@ namespace ImageProcessor
/// <returns>The <see cref="bool"/></returns>
public bool Contains(int x, int y)
{
// TODO: SIMD?
return this.X <= x
&& x < this.Right
&& this.Y <= y
@ -160,49 +233,19 @@ namespace ImageProcessor
/// Returns the center point of the given <see cref="Rectangle"/>
/// </summary>
/// <param name="rectangle">The rectangle</param>
/// <returns><see cref="Vector2"/></returns>
public static Vector2 Center(Rectangle rectangle)
{
return new Vector2(rectangle.Left + rectangle.Width / 2, rectangle.Top + rectangle.Height / 2);
}
/// <summary>
/// Indicates whether this instance and a specified object are equal.
/// </summary>
/// <returns>
/// True if <paramref name="obj"/> and this instance are the same type and represent the same value; otherwise, false.
/// </returns>
/// <param name="obj">The object to compare with the current instance. </param>
public override bool Equals(object obj)
/// <returns><see cref="Point"/></returns>
public static Point Center(Rectangle rectangle)
{
if (!(obj is Rectangle))
{
return false;
}
Rectangle other = (Rectangle)obj;
return other.X == this.X && other.Y == this.Y
&& other.Width == this.Width && other.Height == this.Height;
return new Point(rectangle.Left + rectangle.Width / 2, rectangle.Top + rectangle.Height / 2);
}
/// <summary>
/// Returns the hash code for this instance.
/// </summary>
/// <returns>
/// A 32-bit signed integer that is the hash code for this instance.
/// </returns>
/// <inheritdoc/>
public override int GetHashCode()
{
return this.GetHashCode(this);
}
/// <summary>
/// Returns the fully qualified type name of this instance.
/// </summary>
/// <returns>
/// A <see cref="T:System.String"/> containing a fully qualified type name.
/// </returns>
/// <inheritdoc/>
public override string ToString()
{
if (this.IsEmpty)
@ -214,19 +257,21 @@ namespace ImageProcessor
$"Rectangle [ X={this.X}, Y={this.Y}, Width={this.Width}, Height={this.Height} ]";
}
/// <summary>
/// Indicates whether the current object is equal to another object of the same type.
/// </summary>
/// <returns>
/// True if the current object is equal to the <paramref name="other"/> parameter; otherwise, false.
/// </returns>
/// <param name="other">An object to compare with this object.</param>
/// <inheritdoc/>
public override bool Equals(object obj)
{
if (obj is Rectangle)
{
return this.Equals((Rectangle)obj);
}
return false;
}
/// <inheritdoc/>
public bool Equals(Rectangle other)
{
return this.X.Equals(other.X)
&& this.Y.Equals(other.Y)
&& this.Width.Equals(other.Width)
&& this.Height.Equals(other.Height);
return this.backingVector.Equals(other.backingVector);
}
/// <summary>
@ -240,14 +285,7 @@ namespace ImageProcessor
/// </returns>
private int GetHashCode(Rectangle rectangle)
{
unchecked
{
int hashCode = rectangle.X.GetHashCode();
hashCode = (hashCode * 397) ^ rectangle.Y.GetHashCode();
hashCode = (hashCode * 397) ^ rectangle.Width.GetHashCode();
hashCode = (hashCode * 397) ^ rectangle.Height.GetHashCode();
return hashCode;
}
return rectangle.backingVector.GetHashCode();
}
}
}

219
src/ImageProcessor/Numerics/RotatedRectangle.cs

@ -1,219 +0,0 @@
namespace ImageProcessor
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
/// <summary>
/// A rotated rectangle. Adapted from the excellent sample. TODO: Refactor into a struct.
/// <see href="http://www.xnadevelopment.com/tutorials/rotatedrectanglecollisions/rotatedrectanglecollisions.shtml"/>
/// </summary>
public class RotatedRectangle
{
public Rectangle CollisionRectangle;
public float Rotation;
public Vector2 Origin;
public RotatedRectangle(Rectangle theRectangle, float theInitialRotation)
{
CollisionRectangle = theRectangle;
Rotation = theInitialRotation;
//Calculate the Rectangles origin. We assume the center of the Rectangle will
//be the point that we will be rotating around and we use that for the origin
Origin = new Vector2((int)theRectangle.Width / 2f, (int)theRectangle.Height / 2f);
}
/// <summary>
/// Used for changing the X and Y position of the RotatedRectangle
/// </summary>
/// <param name="theXPositionAdjustment"></param>
/// <param name="theYPositionAdjustment"></param>
public void ChangePosition(int theXPositionAdjustment, int theYPositionAdjustment)
{
CollisionRectangle.X += theXPositionAdjustment;
CollisionRectangle.Y += theYPositionAdjustment;
}
/// <summary>
/// Determines if the specfied point is contained within the rectangular region defined by
/// this <see cref="RotatedRectangle"/>.
/// </summary>
/// <param name="x">The x-coordinate of the given point.</param>
/// <param name="y">The y-coordinate of the given point.</param>
/// <returns>The <see cref="bool"/></returns>
public bool Contains(int x, int y)
{
Rectangle rectangle = new Rectangle(x, y, 1, 1);
return this.Intersects(new RotatedRectangle(rectangle, 0.0f));
}
/// <summary>
/// This intersects method can be used to check a standard XNA framework Rectangle
/// object and see if it collides with a Rotated Rectangle object
/// </summary>
/// <param name="rectangle"></param>
/// <returns></returns>
public bool Intersects(Rectangle rectangle)
{
return this.Intersects(new RotatedRectangle(rectangle, 0.0f));
}
/// <summary>
/// Check to see if two Rotated Rectangls have collided
/// </summary>
/// <param name="theRectangle"></param>
/// <returns></returns>
public bool Intersects(RotatedRectangle theRectangle)
{
//Calculate the Axis we will use to determine if a collision has occurred
//Since the objects are rectangles, we only have to generate 4 Axis (2 for
//each rectangle) since we know the other 2 on a rectangle are parallel.
List<Vector2> aRectangleAxis = new List<Vector2>
{
this.UpperRightCorner() - this.UpperLeftCorner(),
this.UpperRightCorner() - this.LowerRightCorner(),
theRectangle.UpperLeftCorner() - theRectangle.LowerLeftCorner(),
theRectangle.UpperLeftCorner() - theRectangle.UpperRightCorner()
};
//Cycle through all of the Axis we need to check. If a collision does not occur
//on ALL of the Axis, then a collision is NOT occurring. We can then exit out
//immediately and notify the calling function that no collision was detected. If
//a collision DOES occur on ALL of the Axis, then there is a collision occurring
//between the rotated rectangles. We know this to be true by the Seperating Axis Theorem
return aRectangleAxis.All(aAxis => this.IsAxisCollision(theRectangle, aAxis));
}
/// <summary>
/// Determines if a collision has occurred on an Axis of one of the
/// planes parallel to the Rectangle
/// </summary>
/// <param name="theRectangle"></param>
/// <param name="aAxis"></param>
/// <returns></returns>
private bool IsAxisCollision(RotatedRectangle theRectangle, Vector2 aAxis)
{
//Project the corners of the Rectangle we are checking on to the Axis and
//get a scalar value of that project we can then use for comparison
List<int> aRectangleAScalars = new List<int>
{
this.GenerateScalar(theRectangle.UpperLeftCorner(), aAxis),
this.GenerateScalar(theRectangle.UpperRightCorner(), aAxis),
this.GenerateScalar(theRectangle.LowerLeftCorner(), aAxis),
this.GenerateScalar(theRectangle.LowerRightCorner(), aAxis)
};
//Project the corners of the current Rectangle on to the Axis and
//get a scalar value of that project we can then use for comparison
List<int> aRectangleBScalars = new List<int>
{
this.GenerateScalar(this.UpperLeftCorner(), aAxis),
this.GenerateScalar(this.UpperRightCorner(), aAxis),
this.GenerateScalar(this.LowerLeftCorner(), aAxis),
this.GenerateScalar(this.LowerRightCorner(), aAxis)
};
//Get the Maximum and Minium Scalar values for each of the Rectangles
int aRectangleAMinimum = aRectangleAScalars.Min();
int aRectangleAMaximum = aRectangleAScalars.Max();
int aRectangleBMinimum = aRectangleBScalars.Min();
int aRectangleBMaximum = aRectangleBScalars.Max();
//If we have overlaps between the Rectangles (i.e. Min of B is less than Max of A)
//then we are detecting a collision between the rectangles on this Axis
if (aRectangleBMinimum <= aRectangleAMaximum && aRectangleBMaximum >= aRectangleAMaximum)
{
return true;
}
else if (aRectangleAMinimum <= aRectangleBMaximum && aRectangleAMaximum >= aRectangleBMaximum)
{
return true;
}
return false;
}
/// <summary>
/// Generates a scalar value that can be used to compare where corners of
/// a rectangle have been projected onto a particular axis.
/// </summary>
/// <param name="corner"></param>
/// <param name="axis"></param>
/// <returns></returns>
private int GenerateScalar(Vector2 corner, Vector2 axis)
{
// Using the formula for Vector projection. Take the corner being passed in
// and project it onto the given Axis
float numerator = Vector2.Dot(corner, axis); //(theRectangleCorner.X * theAxis.X) + (theRectangleCorner.Y * theAxis.Y);
float denominator = Vector2.Dot(axis, axis); //(theAxis.X * theAxis.X) + (theAxis.Y * theAxis.Y);
float aDivisionResult = numerator / denominator;
Vector2 projected = new Vector2(aDivisionResult * axis.X, aDivisionResult * axis.Y);
// Now that we have our projected Vector, calculate a scalar of that projection
// that can be used to more easily do comparisons
float scalar = Vector2.Dot(axis, projected);
return (int)scalar;
}
/// <summary>
/// Rotate a point from a given location and adjust using the Origin we
/// are rotating around
/// </summary>
/// <param name="thePoint"></param>
/// <param name="theOrigin"></param>
/// <param name="theRotation"></param>
/// <returns></returns>
private Vector2 RotatePoint(Vector2 thePoint, Vector2 theOrigin, float theRotation)
{
Vector2 aTranslatedPoint = new Vector2
{
X = (float)(theOrigin.X
+ (thePoint.X - theOrigin.X) * Math.Cos(theRotation)
- (thePoint.Y - theOrigin.Y) * Math.Sin(theRotation)),
Y = (float)(theOrigin.Y
+ (thePoint.Y - theOrigin.Y) * Math.Cos(theRotation)
+ (thePoint.X - theOrigin.X) * Math.Sin(theRotation))
};
return aTranslatedPoint;
}
public Vector2 UpperLeftCorner()
{
Vector2 aUpperLeft = new Vector2(this.CollisionRectangle.Left, this.CollisionRectangle.Top);
aUpperLeft = this.RotatePoint(aUpperLeft, aUpperLeft + this.Origin, this.Rotation);
return aUpperLeft;
}
public Vector2 UpperRightCorner()
{
Vector2 aUpperRight = new Vector2(this.CollisionRectangle.Right, this.CollisionRectangle.Top);
aUpperRight = this.RotatePoint(aUpperRight, aUpperRight + new Vector2(-this.Origin.X, this.Origin.Y), this.Rotation);
return aUpperRight;
}
public Vector2 LowerLeftCorner()
{
Vector2 aLowerLeft = new Vector2(this.CollisionRectangle.Left, this.CollisionRectangle.Bottom);
aLowerLeft = this.RotatePoint(aLowerLeft, aLowerLeft + new Vector2(this.Origin.X, -this.Origin.Y), this.Rotation);
return aLowerLeft;
}
public Vector2 LowerRightCorner()
{
Vector2 aLowerRight = new Vector2(this.CollisionRectangle.Right, this.CollisionRectangle.Bottom);
aLowerRight = this.RotatePoint(aLowerRight, aLowerRight + new Vector2(-this.Origin.X, -this.Origin.Y), this.Rotation);
return aLowerRight;
}
public int X => this.CollisionRectangle.X;
public int Y => this.CollisionRectangle.Y;
public int Width => this.CollisionRectangle.Width;
public int Height => this.CollisionRectangle.Height;
}
}

150
src/ImageProcessor/Numerics/Size.cs

@ -7,6 +7,7 @@ namespace ImageProcessor
{
using System;
using System.ComponentModel;
using System.Numerics;
/// <summary>
/// Stores an ordered pair of integers, which specify a height and width.
@ -22,41 +23,98 @@ namespace ImageProcessor
/// </summary>
public static readonly Size Empty = default(Size);
/// <summary>
/// The backing vector for SIMD support.
/// </summary>
private Vector2 backingVector;
/// <summary>
/// Initializes a new instance of the <see cref="Size"/> struct.
/// </summary>
/// <param name="width">
/// The width of the size.
/// </param>
/// <param name="height">
/// The height of the size.
/// </param>
/// <param name="width">The width of the size.</param>
/// <param name="height">The height of the size.</param>
public Size(int width, int height)
{
this.Width = width;
this.Height = height;
this.backingVector = new Vector2(width, height);
}
/// <summary>
/// Initializes a new instance of the <see cref="Size"/> struct.
/// </summary>
/// <param name="vector">
/// The vector representing the width and height.
/// </param>
public Size(Vector2 vector)
{
this.backingVector = new Vector2(vector.X, vector.Y);
}
/// <summary>
/// The width of this <see cref="Size"/>.
/// </summary>
public int Width { get; set; }
public int Width
{
get
{
return (int)this.backingVector.X;
}
set
{
this.backingVector.X = value;
}
}
/// <summary>
/// The height of this <see cref="Size"/>.
/// </summary>
public int Height { get; set; }
public int Height
{
get
{
return (int)this.backingVector.Y;
}
set
{
this.backingVector.Y = value;
}
}
/// <summary>
/// Gets a value indicating whether this <see cref="Size"/> is empty.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public bool IsEmpty => this.Width == 0 && this.Height == 0;
public bool IsEmpty => this.Equals(Empty);
/// <summary>
/// Computes the sum of adding two sizes.
/// </summary>
/// <param name="left">The size on the left hand of the operand.</param>
/// <param name="right">The size on the right hand of the operand.</param>
/// <returns>
/// The <see cref="Size"/>
/// </returns>
public static Size operator +(Size left, Size right)
{
return new Size(left.backingVector + right.backingVector);
}
/// <summary>
/// Computes the difference left by subtracting one size from another.
/// </summary>
/// <param name="left">The size on the left hand of the operand.</param>
/// <param name="right">The size on the right hand of the operand.</param>
/// <returns>
/// The <see cref="Size"/>
/// </returns>
public static Size operator -(Size left, Size right)
{
return new Size(left.backingVector - right.backingVector);
}
/// <summary>
/// Compares two <see cref="Size"/> objects. The result specifies whether the values
/// <see cref="Size.Width"/> and the <see cref="Size.Height"/>properties of the two
/// <see cref="Size"/> objects are equal.
/// Compares two <see cref="Size"/> objects for equality.
/// </summary>
/// <param name="left">
/// The <see cref="Size"/> on the left side of the operand.
@ -73,9 +131,7 @@ namespace ImageProcessor
}
/// <summary>
/// Compares two <see cref="Size"/> objects. The result specifies whether the values
/// <see cref="Size.Width"/> and the <see cref="Size.Height"/>properties of the two
/// <see cref="Size"/> objects are unequal.
/// Compares two <see cref="Size"/> objects for inequality.
/// </summary>
/// <param name="left">
/// The <see cref="Size"/> on the left side of the operand.
@ -92,41 +148,21 @@ namespace ImageProcessor
}
/// <summary>
/// Indicates whether this instance and a specified object are equal.
/// Gets a <see cref="Vector2"/> representation for this <see cref="Size"/>.
/// </summary>
/// <returns>
/// True if <paramref name="obj"/> and this instance are the same type and represent the same value; otherwise, false.
/// </returns>
/// <param name="obj">The object to compare with the current instance. </param>
public override bool Equals(object obj)
/// <returns>A <see cref="Vector2"/> representation for this object.</returns>
public Vector2 ToVector2()
{
if (!(obj is Size))
{
return false;
}
Size other = (Size)obj;
return other.Width == this.Width && other.Height == this.Height;
return new Vector2(this.Width, this.Height);
}
/// <summary>
/// Returns the hash code for this instance.
/// </summary>
/// <returns>
/// A 32-bit signed integer that is the hash code for this instance.
/// </returns>
/// <inheritdoc/>
public override int GetHashCode()
{
return this.GetHashCode(this);
}
/// <summary>
/// Returns the fully qualified type name of this instance.
/// </summary>
/// <returns>
/// A <see cref="T:System.String"/> containing a fully qualified type name.
/// </returns>
/// <inheritdoc/>
public override string ToString()
{
if (this.IsEmpty)
@ -138,16 +174,21 @@ namespace ImageProcessor
$"Size [ Width={this.Width}, Height={this.Height} ]";
}
/// <summary>
/// Indicates whether the current object is equal to another object of the same type.
/// </summary>
/// <returns>
/// True if the current object is equal to the <paramref name="other"/> parameter; otherwise, false.
/// </returns>
/// <param name="other">An object to compare with this object.</param>
/// <inheritdoc/>
public override bool Equals(object obj)
{
if (obj is Size)
{
return this.Equals((Size)obj);
}
return false;
}
/// <inheritdoc/>
public bool Equals(Size other)
{
return this.Width.Equals(other.Width) && this.Height.Equals(other.Height);
return this.backingVector.Equals(other.backingVector);
}
/// <summary>
@ -161,12 +202,7 @@ namespace ImageProcessor
/// </returns>
private int GetHashCode(Size size)
{
unchecked
{
int hashCode = size.Width.GetHashCode();
hashCode = (hashCode * 397) ^ size.Height.GetHashCode();
return hashCode;
}
return size.backingVector.GetHashCode();
}
}
}

25
src/ImageProcessor/Samplers/Resampler.cs

@ -230,7 +230,7 @@ namespace ImageProcessor.Samplers
int startX = targetRectangle.X;
int endX = targetRectangle.Right;
float negativeAngle = -this.angle;
Vector2 centre = Rectangle.Center(sourceRectangle);
Point centre = Rectangle.Center(sourceRectangle);
if (this.Sampler is NearestNeighborResampler)
{
@ -254,13 +254,10 @@ namespace ImageProcessor.Samplers
int originX = (int)((x - startX) * widthFactor);
// Rotate at the centre point
Vector2 rotated = ImageMaths.RotatePoint(new Vector2(originX, originY), centre, negativeAngle);
int rotatedX = (int)rotated.X;
int rotatedY = (int)rotated.Y;
if (sourceRectangle.Contains(rotatedX, rotatedY))
Point rotated = Point.Rotate(new Point(originX, originY), centre, negativeAngle);
if (sourceRectangle.Contains(rotated.X, rotated.Y))
{
target[x, y] = source[rotatedX, rotatedY];
target[x, y] = source[rotated.X, rotated.Y];
}
}
}
@ -296,13 +293,15 @@ namespace ImageProcessor.Samplers
int originX = xw.Index;
// Rotate at the centre point
Vector2 rotated = ImageMaths.RotatePoint(new Vector2(originX, originY), centre, negativeAngle);
int rotatedX = (int)rotated.X;
int rotatedY = (int)rotated.Y;
Point rotated = Point.Rotate(new Point(originX, originY), centre, negativeAngle);
if (sourceRectangle.Contains(rotated.X, rotated.Y))
{
target[x, y] = source[rotated.X, rotated.Y];
}
if (sourceRectangle.Contains(rotatedX, rotatedY))
if (sourceRectangle.Contains(rotated.X, rotated.Y))
{
Color sourceColor = Color.Expand(source[rotatedX, rotatedY]);
Color sourceColor = Color.Expand(source[rotated.X, rotated.Y]);
float weight = yw.Value * xw.Value;
destination.R += sourceColor.R * weight;
destination.G += sourceColor.G * weight;
@ -435,4 +434,4 @@ namespace ImageProcessor.Samplers
public float Sum { get; set; }
}
}
}
}

22
tests/ImageProcessor.Tests/Processors/Samplers/SamplerTests.cs

@ -13,20 +13,20 @@
public static readonly TheoryData<string, IResampler> Samplers =
new TheoryData<string, IResampler>
{
{ "Bicubic", new BicubicResampler() },
{ "Triangle", new TriangleResampler() },
{ "Box", new BoxResampler() },
{ "Lanczos3", new Lanczos3Resampler() },
//{ "Bicubic", new BicubicResampler() },
//{ "Triangle", new TriangleResampler() },
//{ "Box", new BoxResampler() },
//{ "Lanczos3", new Lanczos3Resampler() },
//{ "Lanczos5", new Lanczos5Resampler() },
//{ "Lanczos8", new Lanczos8Resampler() },
{ "MitchellNetravali", new MitchellNetravaliResampler() },
//{ "MitchellNetravali", new MitchellNetravaliResampler() },
{ "NearestNeighbor", new NearestNeighborResampler() },
{ "Hermite", new HermiteResampler() },
{ "Spline", new SplineResampler() },
{ "Robidoux", new RobidouxResampler() },
{ "RobidouxSharp", new RobidouxSharpResampler() },
{ "RobidouxSoft", new RobidouxSoftResampler() },
{ "Welch", new WelchResampler() }
//{ "Hermite", new HermiteResampler() },
//{ "Spline", new SplineResampler() },
//{ "Robidoux", new RobidouxResampler() },
//{ "RobidouxSharp", new RobidouxSharpResampler() },
//{ "RobidouxSoft", new RobidouxSoftResampler() },
//{ "Welch", new WelchResampler() }
};
public static readonly TheoryData<RotateType, FlipType> RotateFlips = new TheoryData<RotateType, FlipType>

Loading…
Cancel
Save