diff --git a/src/Microsoft.Maui.Graphics.GDI.Winforms/GDICanvasState.cs b/src/Microsoft.Maui.Graphics.GDI.Winforms/GDICanvasState.cs index b83bdfc..bc07dd9 100644 --- a/src/Microsoft.Maui.Graphics.GDI.Winforms/GDICanvasState.cs +++ b/src/Microsoft.Maui.Graphics.GDI.Winforms/GDICanvasState.cs @@ -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); } diff --git a/src/Microsoft.Maui.Graphics/AffineTransform.cs b/src/Microsoft.Maui.Graphics/AffineTransform.cs deleted file mode 100644 index 5b40283..0000000 --- a/src/Microsoft.Maui.Graphics/AffineTransform.cs +++ /dev/null @@ -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)); - } - - /// - /// Multiply two AffineTransform objects - /// - /// the multiplicand - /// the multiplier - /// an AffineTransform object that is a result of t1 multiplied by t2 - 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); - } - - /// - /// Creates a matrix from an SRT. - /// - /// The scale - /// The rotation, in radians - /// the translation - /// A Matrix3x2 - /// - /// This is equivalent to:
- /// - /// m = Matrix3x2.CreateScale(scale)
- /// m *= Matri3x2.CreateRotation(rotation)
- /// m *= Matri3x2.CreateTranslation(translation)
- ///
- ///
- public static Matrix3x2 CreateMatrix3x2(Vector2 scale, float rotation, Vector2 translation) - { - return Matrix3x2Extensions.CreateMatrix3x2(scale, rotation, translation); - } - - - } -} diff --git a/src/Microsoft.Maui.Graphics/CanvasState.cs b/src/Microsoft.Maui.Graphics/CanvasState.cs index 52d0364..0b4e3fd 100644 --- a/src/Microsoft.Maui.Graphics/CanvasState.cs +++ b/src/Microsoft.Maui.Graphics/CanvasState.cs @@ -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 diff --git a/src/Microsoft.Maui.Graphics/Matrix3x2Extensions.cs b/src/Microsoft.Maui.Graphics/Matrix3x2Extensions.cs index 63694bb..b671efa 100644 --- a/src/Microsoft.Maui.Graphics/Matrix3x2Extensions.cs +++ b/src/Microsoft.Maui.Graphics/Matrix3x2Extensions.cs @@ -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) { diff --git a/tests/Microsoft.Maui.Graphics.Tests/AffineTransformTests.cs b/tests/Microsoft.Maui.Graphics.Tests/AffineTransformTests.cs deleted file mode 100644 index 3ec1577..0000000 --- a/tests/Microsoft.Maui.Graphics.Tests/AffineTransformTests.cs +++ /dev/null @@ -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 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); - } - } -} diff --git a/tests/Microsoft.Maui.Graphics.Tests/Matrix3x2ExtensionsTests.cs b/tests/Microsoft.Maui.Graphics.Tests/Matrix3x2ExtensionsTests.cs index ae86bdf..4f82d3b 100644 --- a/tests/Microsoft.Maui.Graphics.Tests/Matrix3x2ExtensionsTests.cs +++ b/tests/Microsoft.Maui.Graphics.Tests/Matrix3x2ExtensionsTests.cs @@ -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); }