Removed no longer needed AffineTransform class.

This commit is contained in:
vpenades 2021-10-14 08:58:21 +02:00
Родитель 47092a2a31
Коммит ae10be192e
6 изменённых файлов: 9 добавлений и 760 удалений

Просмотреть файл

@ -377,7 +377,7 @@ namespace Microsoft.Maui.Graphics.GDI
public void NativeConcatenateTransform(Matrix3x2 transform)
{
_scale *= transform.GetLengthScale();
_scale *= GetLengthScale(transform);
var transformMatrix = new Matrix(transform.M11, transform.M12, transform.M21, transform.M22, transform.M31, transform.M32);
_graphics.MultiplyTransform(transformMatrix);
}

Просмотреть файл

@ -1,520 +0,0 @@
using System;
using System.Diagnostics;
using System.Numerics;
namespace Microsoft.Maui.Graphics
{
[DebuggerDisplay("X={Translation.X} Y={Translation.Y} W={Scaling.Width} H={Scaling.Height} R={RotationDegrees}° ")]
public class AffineTransform
{
private const float Epsilon = 1E-10f;
private float _m11;
private float _m21;
private float _m31;
private float _m12;
private float _m22;
private float _m32;
public AffineTransform()
{
_m11 = _m22 = 1.0f;
_m12 = _m21 = _m31 = _m32 = 0.0f;
}
public AffineTransform(AffineTransform t)
{
_m11 = t._m11;
_m12 = t._m12;
_m21 = t._m21;
_m22 = t._m22;
_m31 = t._m31;
_m32 = t._m32;
}
public AffineTransform(float m11, float m12, float m21, float m22, float m31, float m32)
{
_m11 = m11;
_m12 = m12;
_m21 = m21;
_m22 = m22;
_m31 = m31;
_m32 = m32;
}
public AffineTransform(float[] matrix)
{
_m11 = matrix[0];
_m12 = matrix[1];
_m21 = matrix[2];
_m22 = matrix[3];
if (matrix.Length > 4)
{
_m31 = matrix[4];
_m32 = matrix[5];
}
}
public AffineTransform(in Matrix3x2 matrix)
{
_m11 = matrix.M11;
_m12 = matrix.M12;
_m21 = matrix.M21;
_m22 = matrix.M22;
_m31 = matrix.M31;
_m32 = matrix.M32;
}
public float M11 => _m11;
public float M22 => _m22;
public float M21 => _m21;
public float M12 => _m12;
public float M31 => _m31;
public float M32 => _m32;
public PointF Translation
{
get => new PointF(_m31, _m32);
set { _m31 = value.X; _m32 = value.Y; }
}
public float RotationDegrees
{
get => Geometry.RadiansToDegrees(Rotation);
set => Rotation = Geometry.DegreesToRadians(value);
}
public float Rotation
{
get => (float)Math.Atan2(M12, M11);
set
{
var t = Translation;
var s = Scale;
SetTo(t, value, s);
}
}
public float AverageScale
{
get
{
var s = Scale;
return (Math.Abs(s.Width) + Math.Abs(s.Height)) / 2;
}
}
public SizeF Scale
{
get
{
var sx = _m12 == 0 ? Math.Abs(_m11) : new Vector2(_m11, _m12).Length();
var sy = _m21 == 0 ? Math.Abs(_m22) : new Vector2(_m21, _m22).Length();
if (GetDeterminant() < 0) sy = -sy;
return new SizeF(sx, sy);
}
set
{
var t = Translation;
var r = Rotation;
SetTo(t, r, value);
}
}
public bool IsIdentity => _m11 == 1.0f && _m22 == 1.0f && _m12 == 0.0f && _m21 == 0.0f && _m31 == 0.0f && _m32 == 0.0f;
public void CopyTo(float[] matrix, int offset = 0, int count = 6)
{
count = Math.Min(matrix.Length, count);
matrix[offset + 0] = _m11;
matrix[offset + 1] = _m12;
matrix[offset + 2] = _m21;
matrix[offset + 3] = _m22;
if (count > 4)
{
matrix[offset + 4] = _m31;
matrix[offset + 5] = _m32;
}
}
public float GetDeterminant()
{
return _m11 * _m22 - _m21 * _m12;
}
public void SetTransform(float m11, float m12, float m21, float m22, float m31, float m32)
{
_m11 = m11;
_m12 = m12;
_m21 = m21;
_m22 = m22;
_m31 = m31;
_m32 = m32;
}
public void SetTransform(float[] matrix)
{
_m11 = matrix[0];
_m12 = matrix[1];
_m21 = matrix[2];
_m22 = matrix[3];
if (matrix.Length > 4)
{
_m31 = matrix[4];
_m32 = matrix[5];
}
}
public void SetTransform(in Matrix3x2 matrix)
{
_m11 = matrix.M11;
_m12 = matrix.M12;
_m21 = matrix.M21;
_m22 = matrix.M22;
_m31 = matrix.M31;
_m32 = matrix.M32;
}
public void SetTransform(AffineTransform t)
{
SetTransform(t._m11, t._m12, t._m21, t._m22, t._m31, t._m32);
}
public void SetToIdentity()
{
_m11 = _m22 = 1.0f;
_m12 = _m21 = _m31 = _m32 = 0.0f;
}
public void SetTo(PointF translation, float rotation, SizeF scale)
{
this.SetToTranslation(translation.X, translation.Y);
this.ConcatenateRotation(rotation);
this.ConcatenateScale(scale);
}
public void SetToTranslation(float mx, float my)
{
_m11 = _m22 = 1.0f;
_m21 = _m12 = 0.0f;
_m31 = mx;
_m32 = my;
}
public void SetToScale(float scx, float scy)
{
_m11 = scx;
_m22 = scy;
_m12 = _m21 = _m31 = _m32 = 0.0f;
}
public void SetToShear(float shx, float shy)
{
_m11 = _m22 = 1.0f;
_m31 = _m32 = 0.0f;
_m21 = shx;
_m12 = shy;
}
public void SetToRotation(float radians)
{
float sin = (float) Math.Sin(radians);
float cos = (float) Math.Cos(radians);
if (Math.Abs(cos) < Epsilon)
{
cos = 0.0f;
sin = sin > 0.0f ? 1.0f : -1.0f;
}
else if (Math.Abs(sin) < Epsilon)
{
sin = 0.0f;
cos = cos > 0.0f ? 1.0f : -1.0f;
}
_m11 = _m22 = cos;
_m21 = -sin;
_m12 = sin;
_m31 = _m32 = 0.0f;
}
public void SetToRotation(float radians, float px, float py)
{
SetToRotation(radians);
_m31 = px * (1.0f - _m11) + py * _m12;
_m32 = py * (1.0f - _m11) - px * _m12;
}
public static AffineTransform GetInstance(PointF translation, float rotation, SizeF scale)
{
var t = new AffineTransform();
t.SetTo(translation, rotation, scale);
return t;
}
public static AffineTransform GetTranslateInstance(float mx, float my)
{
var t = new AffineTransform();
t.SetToTranslation(mx, my);
return t;
}
public static AffineTransform GetScaleInstance(float scx, float scY)
{
var t = new AffineTransform();
t.SetToScale(scx, scY);
return t;
}
public static AffineTransform GetShearInstance(float shx, float shy)
{
var m = new AffineTransform();
m.SetToShear(shx, shy);
return m;
}
public static AffineTransform GetRotateInstance(float angle)
{
var t = new AffineTransform();
t.SetToRotation(angle);
return t;
}
public static AffineTransform GetRotateInstance(float angle, float x, float y)
{
var t = new AffineTransform();
t.SetToRotation(angle, x, y);
return t;
}
public void ConcatenateTranslation(PointF point)
{
Concatenate(GetTranslateInstance(point.X, point.Y));
}
public void ConcatenateTranslation(float mx, float my)
{
Concatenate(GetTranslateInstance(mx, my));
}
public void ConcatenateScale(SizeF scale)
{
Concatenate(GetScaleInstance(scale.Width, scale.Height));
}
public void ConcatenateScale(float scx, float scy)
{
Concatenate(GetScaleInstance(scx, scy));
}
public void ConcatenateShear(float shx, float shy)
{
Concatenate(GetShearInstance(shx, shy));
}
public void ConcatenateRotationInDegrees(float degrees)
{
ConcatenateRotation(Geometry.DegreesToRadians(degrees));
}
public void ConcatenateRotationInDegrees(float degrees, float px, float py)
{
ConcatenateRotation(Geometry.DegreesToRadians(degrees), px, py);
}
public void ConcatenateRotation(float radians)
{
Concatenate(GetRotateInstance(radians));
}
public void ConcatenateRotation(float radians, float px, float py)
{
Concatenate(GetRotateInstance(radians, px, py));
}
public void Concatenate(AffineTransform t)
{
SetTransform(Multiply(t, this));
}
public void PreConcatenate(AffineTransform t)
{
SetTransform(Multiply(this, t));
}
/// <summary>
/// Multiply two AffineTransform objects
/// </summary>
/// <param name="t1">the multiplicand</param>
/// <param name="t2">the multiplier</param>
/// <returns>an AffineTransform object that is a result of t1 multiplied by t2</returns>
private AffineTransform Multiply(AffineTransform t1, AffineTransform t2)
{
return new AffineTransform(
t1._m11 * t2._m11 + t1._m12 * t2._m21, // m11
t1._m11 * t2._m12 + t1._m12 * t2._m22, // m21
t1._m21 * t2._m11 + t1._m22 * t2._m21, // m12
t1._m21 * t2._m12 + t1._m22 * t2._m22, // m22
t1._m31 * t2._m11 + t1._m32 * t2._m21 + t2._m31, // m31
t1._m31 * t2._m12 + t1._m32 * t2._m22 + t2._m32); // m32
}
public AffineTransform CreateInverse()
{
float det = GetDeterminant();
if (Math.Abs(det) < Epsilon)
throw new Exception("Determinant is zero");
return new AffineTransform(
_m22 / det,
-_m12 / det,
-_m21 / det,
_m11 / det,
(_m21 * _m32 - _m22 * _m31) / det,
(_m12 * _m31 - _m11 * _m32) / det
);
}
public PointF Transform(PointF src)
{
return Transform(src.X, src.Y);
}
public PointF Transform(float x, float y)
{
return new PointF(x * _m11 + y * _m21 + _m31, x * _m12 + y * _m22 + _m32);
}
public PointF InverseTransform(PointF src)
{
float det = GetDeterminant();
if (Math.Abs(det) < Epsilon)
throw new Exception("Unable to inverse this transform.");
float x = src.X - _m31;
float y = src.Y - _m32;
return new PointF((x * _m22 - y * _m21) / det, (y * _m11 - x * _m12) / det);
}
public void Transform(float[] src, int srcOff, float[] dst, int dstOff, int length)
{
int step = 2;
if (src == dst && srcOff < dstOff && dstOff < srcOff + length * 2)
{
srcOff = srcOff + length * 2 - 2;
dstOff = dstOff + length * 2 - 2;
step = -2;
}
while (--length >= 0)
{
float x = src[srcOff + 0];
float y = src[srcOff + 1];
dst[dstOff + 0] = x * _m11 + y * _m21 + _m31;
dst[dstOff + 1] = x * _m12 + y * _m22 + _m32;
srcOff += step;
dstOff += step;
}
}
public bool IsUnityTransform()
{
return !(HasScale() || HasRotate() || HasTranslate());
}
private bool HasScale()
{
// ReSharper disable CompareOfFloatsByEqualityOperator
// if matrix has no rotation, we can do an exact check:
if (!HasRotate())
{
return _m11 != 1.0 || _m22 != 1.0;
}
// when the transform is rotated, we have to deconstruct
// the scaling and handle the check with precission loss:
var scale = this.Scale;
if (Math.Abs(scale.Width - 1) > Epsilon) return true;
if (Math.Abs(scale.Height - 1) > Epsilon) return true;
return false;
// ReSharper restore CompareOfFloatsByEqualityOperator
}
private bool HasRotate()
{
// ReSharper disable CompareOfFloatsByEqualityOperator
return _m12 != 0.0 || _m21 != 0.0;
// ReSharper restore CompareOfFloatsByEqualityOperator
}
private bool HasTranslate()
{
// ReSharper disable CompareOfFloatsByEqualityOperator
return _m31 != 0.0 || _m32 != 0.0;
// ReSharper restore CompareOfFloatsByEqualityOperator
}
public bool OnlyTranslate()
{
return !HasRotate() && !HasScale();
}
public bool OnlyTranslateOrScale()
{
return !HasRotate();
}
public bool OnlyScale()
{
return !HasRotate() && !HasTranslate();
}
public void Deconstruct(out PointF translation, out float rotation, out SizeF scale)
{
scale = this.Scale;
rotation = this.Rotation;
translation = Translation;
}
public static implicit operator AffineTransform(Matrix3x2 matrix) => new AffineTransform(matrix);
public static explicit operator Matrix3x2(AffineTransform matrix)
{
return new Matrix3x2(matrix._m11, matrix._m12, matrix._m21, matrix._m22, matrix._m31, matrix._m32);
}
/// <summary>
/// Creates a matrix from an SRT.
/// </summary>
/// <param name="scale">The scale</param>
/// <param name="rotation">The rotation, in radians</param>
/// <param name="translation">the translation</param>
/// <returns>A Matrix3x2</returns>
/// <remarks>
/// This is equivalent to:<br/>
/// <c>
/// m = Matrix3x2.CreateScale(scale)<br/>
/// m *= Matri3x2.CreateRotation(rotation)<br/>
/// m *= Matri3x2.CreateTranslation(translation)<br/>
/// </c>
/// </remarks>
public static Matrix3x2 CreateMatrix3x2(Vector2 scale, float rotation, Vector2 translation)
{
return Matrix3x2Extensions.CreateMatrix3x2(scale, rotation, translation);
}
}
}

Просмотреть файл

@ -48,6 +48,8 @@ namespace Microsoft.Maui.Graphics
// let derived classes handle the transform change if needed.
}
protected static float GetLengthScale(Matrix3x2 matrix) => matrix.GetLengthScale();
public virtual void Dispose()
{
// Do nothing right now

Просмотреть файл

@ -3,9 +3,11 @@ using System.Collections.Generic;
using System.Numerics;
using System.Text;
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Microsoft.Maui.Graphics.Tests")]
namespace Microsoft.Maui.Graphics
{
public static class Matrix3x2Extensions
static class Matrix3x2Extensions
{
public static bool IsZero(this in Matrix3x2 matrix)
{

Просмотреть файл

@ -1,235 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Text;
using System.Threading.Tasks;
using Xunit;
namespace Microsoft.Maui.Graphics.Tests
{
public class AffineTransformTests
{
public static IEnumerable<object[]> AffineTransformData()
{
yield return new object[] { 0, 1, 1 };
yield return new object[] { 0, -1, -1 };
yield return new object[] { 1, 2, 3 };
yield return new object[] { -2, 1, 1 };
yield return new object[] { -1, 5, 2 };
yield return new object[] { -3, 1, -2 };
yield return new object[] { 0, -1, 2 };
yield return new object[] { 0, 1, -2 };
yield return new object[] { 3, -1, -2 };
yield return new object[] { 1, -2, -2 };
yield return new object[] { -1, -2, -2 };
}
[Fact]
public void TestEquivalence()
{
// the identity matrix should be equivalent to a matrix
// with negative scale and rotated 180 degrees
var a = new AffineTransform();
var b = new AffineTransform();
b.ConcatenateRotationInDegrees(180);
b.ConcatenateScale(-1, -1);
AssertEqual(a, b, 6);
}
[Fact]
public void TestConcatenationEquivalence()
{
float x = 5;
float y = 7;
float r = 1;
var affine = new AffineTransform();
affine.ConcatenateTranslation(x, y);
affine.ConcatenateRotation(r);
affine.ConcatenateTranslation(-x, -y);
var p1 = Vector2.Transform(Vector2.One, (Matrix3x2)affine);
var matrix = Matrix3x2.Identity;
matrix = Matrix3x2.CreateTranslation(x, y) * matrix;
matrix = Matrix3x2.CreateRotation(r) * matrix;
matrix = Matrix3x2.CreateTranslation(-x, -y) * matrix;
var p2 = Vector2.Transform(Vector2.One, matrix);
Assert.Equal(p1, p2);
}
[Theory]
[MemberData(nameof(AffineTransformData))]
public void TestDeconstruct(float rotation, float scaleX, float scaleY)
{
var translation = new Vector2(5, 9);
var reference = Matrix3x2.Identity;
reference *= Matrix3x2.CreateScale(scaleX, scaleY);
reference *= Matrix3x2.CreateRotation(rotation);
reference *= Matrix3x2.CreateTranslation(translation);
ReferenceDeconstruct(reference, out var refScale, out var refRot, out var refTrans);
AffineTransform at = reference;
at.Deconstruct(out var atTrans, out var atRot, out var atScale);
AssertEqual(refTrans, atTrans, 5);
Assert.Equal(refRot, atRot, 5);
AssertEqual(refScale, atScale, 5);
at = AffineTransform.GetInstance(refTrans, refRot, (SizeF)refScale);
AssertEqual(reference, at, 5);
}
[Theory]
[MemberData(nameof(AffineTransformData))]
public void TestRotationAndScale(float rotation, float scaleX, float scaleY)
{
var translation = new Vector2(5, 9);
var reference = Matrix3x2.Identity;
reference *= Matrix3x2.CreateScale(scaleX, scaleY);
reference *= Matrix3x2.CreateRotation(rotation);
reference *= Matrix3x2.CreateTranslation(translation);
ReferenceDeconstruct(reference, out var refScale, out var refRot, out var refTrans);
// implicit conversion
AffineTransform at = reference;
AssertOrthogonal(at, 7);
AssertEqual(reference, at, 9);
// concatenation
// notice that AffineTransform concatenation order
// is in reverse order compared to Matrix3x2
at = new AffineTransform();
at.ConcatenateTranslation(translation);
at.ConcatenateRotation(rotation);
at.ConcatenateScale(scaleX, scaleY);
AssertOrthogonal(at, 7);
AssertEqual(reference, at, 7);
// concatenation and rotate assign
at = new AffineTransform();
at.ConcatenateTranslation(translation);
at.ConcatenateScale(scaleX, scaleY);
at.Rotation = scaleX >= 0 ? rotation : rotation + (float)Math.PI;
AssertOrthogonal(at, 7);
AssertEqual(reference, at, 4);
// concatenation and scale assign
at = new AffineTransform();
at.ConcatenateTranslation(translation);
at.ConcatenateRotation(rotation);
at.Scale = new SizeF(scaleX, scaleY); // changes scaling without affecting rotation/translation
AssertOrthogonal(at, 7);
AssertEqual(reference, at, 7);
// deconstruct/reconstruct
at = AffineTransform.GetInstance(translation,rotation, new SizeF(scaleX,scaleY) );
at.Deconstruct(out var t, out var r, out var s);
at = AffineTransform.GetInstance(t, r, s);
AssertOrthogonal(at, 7);
AssertEqual(reference, at, 5);
}
[Theory]
[MemberData(nameof(AffineTransformData))]
public void TestTransformPoint(float rotation, float scaleX, float scaleY)
{
var translation = new Vector2(5, 9);
var point = new Vector2(7, -9);
// notice the concatenation order of Matrix3x2 and
// AffineTransform are defined in reverse order.
var reference = Matrix3x2.Identity;
reference *= Matrix3x2.CreateScale(scaleX, scaleY);
reference *= Matrix3x2.CreateRotation(rotation);
reference *= Matrix3x2.CreateTranslation(translation);
var pointx = Vector2.Transform(point, reference);
// create a matrix equivalent to reference.
var at = new AffineTransform();
at.ConcatenateTranslation(translation);
at.ConcatenateRotation(rotation);
at.ConcatenateScale(scaleX, scaleY);
AssertEqual(reference, at, 5);
AssertEqual(pointx, at.Transform(point), 8);
// create in one shot
at = AffineTransform.GetInstance(translation, rotation, new SizeF(scaleX,scaleY));
AssertEqual(reference, at, 5);
AssertEqual(pointx, at.Transform(point), 8);
// de/reconstruct T R S for roundtrip
at.Deconstruct(out var t, out var r, out var s);
at = AffineTransform.GetInstance(t, r, s);
AssertEqual(reference, at, 5);
AssertEqual(pointx, at.Transform(point), 4);
}
private static void AssertEqual(AffineTransform a, AffineTransform b, int precision)
{
Assert.Equal(a.M11, b.M11, precision);
Assert.Equal(a.M21, b.M21, precision);
Assert.Equal(a.M31, b.M31, precision);
Assert.Equal(a.M12, b.M12, precision);
Assert.Equal(a.M22, b.M22, precision);
Assert.Equal(a.M32, b.M32, precision);
}
private static void AssertEqual(PointF a, PointF b, int precision)
{
Assert.Equal(a.X, b.X, precision);
Assert.Equal(a.Y, b.Y, precision);
}
private static void AssertEqual(Vector2 a, SizeF b, int precision)
{
Assert.Equal(a.X, b.Width, precision);
Assert.Equal(a.Y, b.Height, precision);
}
private static void AssertOrthogonal(AffineTransform transform, int precission)
{
var x = Vector2.Normalize(new Vector2(transform.M11, transform.M12));
var y = Vector2.Normalize(new Vector2(transform.M21, transform.M22));
var d = Vector2.Dot(x, y);
if (d < -1) d = -1;
if (d > 1) d = 1;
var skewAngle = (float) ((Math.PI/2) - Math.Acos(d));
Assert.Equal(0, skewAngle, precission);
}
private static void ReferenceDeconstruct(Matrix3x2 m, out Vector2 scale, out float rotation, out Vector2 translation)
{
// Matrix3x2 lacks decompose, but Matrix4x4 has it.
Matrix4x4.Decompose(new Matrix4x4(m), out var s, out Quaternion r, out var t);
scale = new Vector2(s.X, s.Y);
translation = new Vector2(t.X, t.Y);
var hand = Vector3.Transform(Vector3.UnitX, r);
rotation = (float)Math.Atan2(hand.Y, hand.X);
}
}
}

Просмотреть файл

@ -63,7 +63,7 @@ namespace Microsoft.Maui.Graphics.Tests
Assert.Equal(refRot, r, 5);
AssertEqual(refTrans, t, 5);
var m = AffineTransform.CreateMatrix3x2(s, r, t);
var m = Matrix3x2Extensions.CreateMatrix3x2(s, r, t);
AssertEqual(reference, m, 5);
}
@ -111,11 +111,11 @@ namespace Microsoft.Maui.Graphics.Tests
// deconstruct/reconstruct
m1 = AffineTransform.CreateMatrix3x2(new Vector2(scaleX, scaleY), rotation, translation);
m1 = Matrix3x2Extensions.CreateMatrix3x2(new Vector2(scaleX, scaleY), rotation, translation);
var s = m1.GetScale();
var r = m1.GetRotation();
var t = m1.GetTranslation();
m1 = AffineTransform.CreateMatrix3x2(s, r, t);
m1 = Matrix3x2Extensions.CreateMatrix3x2(s, r, t);
AssertOrthogonal(m1, 7);
AssertEqual(reference, m1, 5);
}