From 667b4e7729c78208ae1e38a03ea38e4ba3c83acc Mon Sep 17 00:00:00 2001 From: Jeremy Kuhne Date: Wed, 3 Mar 2021 22:44:59 -0800 Subject: [PATCH] Add Matrix3x2 transform methods (#48195) * Add Matrix3x2 transform methods This implements #47940 for System.Drawing.Common. * Move GdipGetWorldTransform into try block. * Fix build issues * Add workaround to nuget issue Co-authored-by: Santiago Fernandez Madero --- .../Microsoft.Extensions.Primitives.csproj | 3 +- .../ref/System.Drawing.Common.cs | 3 + .../ref/System.Drawing.Common.csproj | 2 + .../src/System.Drawing.Common.csproj | 1 + .../src/System/Drawing/Drawing2D/Matrix.cs | 47 +++++++++++ .../src/System/Drawing/Graphics.cs | 50 +++++++++++- .../tests/Drawing2D/MatrixTests.Core.cs | 44 ++++++++++ .../tests/Drawing2D/MatrixTests.cs | 36 +++++---- .../tests/GraphicsTests.Core.cs | 81 +++++++++++++++++++ .../tests/GraphicsTests.cs | 2 +- .../tests/System.Drawing.Common.Tests.csproj | 6 +- .../src/System.Windows.Extensions.csproj | 2 +- 12 files changed, 256 insertions(+), 21 deletions(-) create mode 100644 src/libraries/System.Drawing.Common/tests/Drawing2D/MatrixTests.Core.cs create mode 100644 src/libraries/System.Drawing.Common/tests/GraphicsTests.Core.cs diff --git a/src/libraries/Microsoft.Extensions.Primitives/ref/Microsoft.Extensions.Primitives.csproj b/src/libraries/Microsoft.Extensions.Primitives/ref/Microsoft.Extensions.Primitives.csproj index aee88d8e23493..10861efa67985 100644 --- a/src/libraries/Microsoft.Extensions.Primitives/ref/Microsoft.Extensions.Primitives.csproj +++ b/src/libraries/Microsoft.Extensions.Primitives/ref/Microsoft.Extensions.Primitives.csproj @@ -7,6 +7,7 @@ - + + diff --git a/src/libraries/System.Drawing.Common/ref/System.Drawing.Common.cs b/src/libraries/System.Drawing.Common/ref/System.Drawing.Common.cs index 7cd79b1d02397..157484d8543ff 100644 --- a/src/libraries/System.Drawing.Common/ref/System.Drawing.Common.cs +++ b/src/libraries/System.Drawing.Common/ref/System.Drawing.Common.cs @@ -418,6 +418,7 @@ internal Graphics() { } public int TextContrast { get { throw null; } set { } } public System.Drawing.Text.TextRenderingHint TextRenderingHint { get { throw null; } set { } } public System.Drawing.Drawing2D.Matrix Transform { get { throw null; } set { } } + public System.Numerics.Matrix3x2 TransformElements { get { throw null; } set { } } public System.Drawing.RectangleF VisibleClipBounds { get { throw null; } } public void AddMetafileComment(byte[] data) { } public System.Drawing.Drawing2D.GraphicsContainer BeginContainer() { throw null; } @@ -1653,7 +1654,9 @@ public Matrix() { } public Matrix(System.Drawing.Rectangle rect, System.Drawing.Point[] plgpts) { } public Matrix(System.Drawing.RectangleF rect, System.Drawing.PointF[] plgpts) { } public Matrix(float m11, float m12, float m21, float m22, float dx, float dy) { } + public Matrix(System.Numerics.Matrix3x2 matrix) { } public float[] Elements { get { throw null; } } + public System.Numerics.Matrix3x2 MatrixElements { get { throw null; } set { } } public bool IsIdentity { get { throw null; } } public bool IsInvertible { get { throw null; } } public float OffsetX { get { throw null; } } diff --git a/src/libraries/System.Drawing.Common/ref/System.Drawing.Common.csproj b/src/libraries/System.Drawing.Common/ref/System.Drawing.Common.csproj index d41e02dd07e45..5e56a68614b59 100644 --- a/src/libraries/System.Drawing.Common/ref/System.Drawing.Common.csproj +++ b/src/libraries/System.Drawing.Common/ref/System.Drawing.Common.csproj @@ -14,6 +14,7 @@ + @@ -26,6 +27,7 @@ + diff --git a/src/libraries/System.Drawing.Common/src/System.Drawing.Common.csproj b/src/libraries/System.Drawing.Common/src/System.Drawing.Common.csproj index 48fe7a1dbeb9c..28b06d57e22bd 100644 --- a/src/libraries/System.Drawing.Common/src/System.Drawing.Common.csproj +++ b/src/libraries/System.Drawing.Common/src/System.Drawing.Common.csproj @@ -369,6 +369,7 @@ + diff --git a/src/libraries/System.Drawing.Common/src/System/Drawing/Drawing2D/Matrix.cs b/src/libraries/System.Drawing.Common/src/System/Drawing/Drawing2D/Matrix.cs index bc729e4b17025..a0ed155d9767a 100644 --- a/src/libraries/System.Drawing.Common/src/System/Drawing/Drawing2D/Matrix.cs +++ b/src/libraries/System.Drawing.Common/src/System/Drawing/Drawing2D/Matrix.cs @@ -3,6 +3,7 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using System.Numerics; using System.Runtime.InteropServices; using Gdip = System.Drawing.SafeNativeMethods.Gdip; @@ -24,11 +25,33 @@ public Matrix(float m11, float m12, float m21, float m22, float dx, float dy) NativeMatrix = nativeMatrix; } + /// + /// Construct a utilizing the given . + /// + /// Matrix data to construct from. + public Matrix(Matrix3x2 matrix) : this(CreateNativeHandle(matrix)) + { + } + private Matrix(IntPtr nativeMatrix) { NativeMatrix = nativeMatrix; } + internal static IntPtr CreateNativeHandle(Matrix3x2 matrix) + { + Gdip.CheckStatus(Gdip.GdipCreateMatrix2( + matrix.M11, + matrix.M12, + matrix.M21, + matrix.M22, + matrix.M31, + matrix.M32, + out IntPtr nativeMatrix)); + + return nativeMatrix; + } + public unsafe Matrix(RectangleF rect, PointF[] plgpts) { if (plgpts == null) @@ -93,6 +116,30 @@ public float[] Elements } } + /// + /// Gets/sets the elements for the matrix. + /// + public unsafe Matrix3x2 MatrixElements + { + get + { + Matrix3x2 matrix = default; + Gdip.CheckStatus(Gdip.GdipGetMatrixElements(new HandleRef(this, NativeMatrix), (float*)&matrix)); + return matrix; + } + set + { + Gdip.CheckStatus(Gdip.GdipSetMatrixElements( + new HandleRef(this, NativeMatrix), + value.M11, + value.M12, + value.M21, + value.M22, + value.M31, + value.M32)); + } + } + internal unsafe void GetElements(Span elements) { Debug.Assert(elements.Length >= 6); diff --git a/src/libraries/System.Drawing.Common/src/System/Drawing/Graphics.cs b/src/libraries/System.Drawing.Common/src/System/Drawing/Graphics.cs index 29b6b24749241..ba03870d89468 100644 --- a/src/libraries/System.Drawing.Common/src/System/Drawing/Graphics.cs +++ b/src/libraries/System.Drawing.Common/src/System/Drawing/Graphics.cs @@ -2,10 +2,10 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; using System.Drawing.Drawing2D; using System.Drawing.Imaging; using System.Drawing.Text; +using System.Numerics; using System.Runtime.InteropServices; using Gdip = System.Drawing.SafeNativeMethods.Gdip; @@ -332,6 +332,54 @@ public Matrix Transform } } + /// + /// Gets or sets the world transform elements for this . + /// + /// + /// This is a more performant alternative to that does not need disposal. + /// + public unsafe Matrix3x2 TransformElements + { + get + { + Gdip.CheckStatus(Gdip.GdipCreateMatrix(out IntPtr nativeMatrix)); + + try + { + Gdip.CheckStatus(Gdip.GdipGetWorldTransform( + new HandleRef(this, NativeGraphics), new HandleRef(null, nativeMatrix))); + + Matrix3x2 matrix = default; + Gdip.CheckStatus(Gdip.GdipGetMatrixElements(new HandleRef(null, nativeMatrix), (float*)&matrix)); + return matrix; + } + finally + { + if (nativeMatrix != IntPtr.Zero) + { + Gdip.GdipDeleteMatrix(new HandleRef(null, nativeMatrix)); + } + } + } + set + { + IntPtr nativeMatrix = Matrix.CreateNativeHandle(value); + + try + { + Gdip.CheckStatus(Gdip.GdipSetWorldTransform( + new HandleRef(this, NativeGraphics), new HandleRef(null, nativeMatrix))); + } + finally + { + if (nativeMatrix != IntPtr.Zero) + { + Gdip.GdipDeleteMatrix(new HandleRef(null, nativeMatrix)); + } + } + } + } + public IntPtr GetHdc() { IntPtr hdc = IntPtr.Zero; diff --git a/src/libraries/System.Drawing.Common/tests/Drawing2D/MatrixTests.Core.cs b/src/libraries/System.Drawing.Common/tests/Drawing2D/MatrixTests.Core.cs new file mode 100644 index 0000000000000..5087847fa9923 --- /dev/null +++ b/src/libraries/System.Drawing.Common/tests/Drawing2D/MatrixTests.Core.cs @@ -0,0 +1,44 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Numerics; +using Xunit; + +namespace System.Drawing.Drawing2D.Tests +{ + public partial class MatrixTests + { + [ConditionalTheory(Helpers.IsDrawingSupported)] + [MemberData(nameof(MatrixElements_TestData))] + public void Ctor_Matrix3x2(float m11, float m12, float m21, float m22, float dx, float dy, bool isIdentity, bool isInvertible) + { + Matrix3x2 matrix3X2 = new Matrix3x2(m11, m12, m21, m22, dx, dy); + using (var matrix = new Matrix(matrix3X2)) + { + Assert.Equal(new float[] { m11, m12, m21, m22, dx, dy }, matrix.Elements); + Assert.Equal(matrix3X2, matrix.MatrixElements); + Assert.Equal(isIdentity, matrix.IsIdentity); + Assert.Equal(isInvertible, matrix.IsInvertible); + Assert.Equal(dx, matrix.OffsetX); + Assert.Equal(dy, matrix.OffsetY); + } + } + + [ConditionalTheory(Helpers.IsDrawingSupported)] + [MemberData(nameof(MatrixElements_TestData))] + public void MatrixElements_RoundTrip(float m11, float m12, float m21, float m22, float dx, float dy, bool isIdentity, bool isInvertible) + { + Matrix3x2 matrix3X2 = new Matrix3x2(m11, m12, m21, m22, dx, dy); + using (var matrix = new Matrix()) + { + matrix.MatrixElements = matrix3X2; + Assert.Equal(new float[] { m11, m12, m21, m22, dx, dy }, matrix.Elements); + Assert.Equal(matrix3X2, matrix.MatrixElements); + Assert.Equal(isIdentity, matrix.IsIdentity); + Assert.Equal(isInvertible, matrix.IsInvertible); + Assert.Equal(dx, matrix.OffsetX); + Assert.Equal(dy, matrix.OffsetY); + } + } + } +} diff --git a/src/libraries/System.Drawing.Common/tests/Drawing2D/MatrixTests.cs b/src/libraries/System.Drawing.Common/tests/Drawing2D/MatrixTests.cs index d2cf0b69b582c..34480479f6956 100644 --- a/src/libraries/System.Drawing.Common/tests/Drawing2D/MatrixTests.cs +++ b/src/libraries/System.Drawing.Common/tests/Drawing2D/MatrixTests.cs @@ -30,7 +30,7 @@ namespace System.Drawing.Drawing2D.Tests { - public class MatrixTests + public partial class MatrixTests { private static Matrix CreateDisposedMatrix() { @@ -67,20 +67,7 @@ public void Ctor_FloatingPointBoundsInElements(float f) } [ConditionalTheory(Helpers.IsDrawingSupported)] - [InlineData(1, 0, 0, 1, 0, 0, true, true)] - [InlineData(0, 1, 2, 1, 3, 4, false, true)] - [InlineData(0, 0, 0, 0, 0, 0, false, false)] - [InlineData(1, 2, 3, 4, 5, 6, false, true)] - [InlineData(-1, -2, -3, -4, -5, -6, false, true)] - [InlineData(123, 24, 82, 16, 47, 30, false, false)] - [InlineData(156, 46, 0, 0, 106, 19, false, false)] - [InlineData(146, 66, 158, 104, 42, 150, false, true)] - [InlineData(119, 140, 145, 74, 102, 58, false, true)] - [InlineData(1.1f, 0.1f, -0.1f, 0.9f, 0, 0, false, true)] - [InlineData(1.01f, 0.01f, -0.01f, 0.99f, 0, 0, false, true)] - [InlineData(1.001f, 0.001f, -0.001f, 0.999f, 0, 0, false, true)] - [InlineData(1.0001f, 0.0001f, -0.0001f, 0.9999f, 0, 0, true, true)] - [InlineData(1.0009f, 0.0009f, -0.0009f, 0.99995f, 0, 0, false, true)] + [MemberData(nameof(MatrixElements_TestData))] public void Ctor_Elements(float m11, float m12, float m21, float m22, float dx, float dy, bool isIdentity, bool isInvertible) { using (var matrix = new Matrix(m11, m12, m21, m22, dx, dy)) @@ -93,6 +80,25 @@ public void Ctor_Elements(float m11, float m12, float m21, float m22, float dx, } } + public static TheoryData MatrixElements_TestData + => new TheoryData + { + { 1, 0, 0, 1, 0, 0, true, true }, + { 0, 1, 2, 1, 3, 4, false, true }, + { 0, 0, 0, 0, 0, 0, false, false }, + { 1, 2, 3, 4, 5, 6, false, true }, + { -1, -2, -3, -4, -5, -6, false, true }, + { 123, 24, 82, 16, 47, 30, false, false }, + { 156, 46, 0, 0, 106, 19, false, false }, + { 146, 66, 158, 104, 42, 150, false, true }, + { 119, 140, 145, 74, 102, 58, false, true }, + { 1.1f, 0.1f, -0.1f, 0.9f, 0, 0, false, true }, + { 1.01f, 0.01f, -0.01f, 0.99f, 0, 0, false, true }, + { 1.001f, 0.001f, -0.001f, 0.999f, 0, 0, false, true }, + { 1.0001f, 0.0001f, -0.0001f, 0.9999f, 0, 0, true, true }, + { 1.0009f, 0.0009f, -0.0009f, 0.99995f, 0, 0, false, true } + }; + public static IEnumerable Ctor_Rectangle_Points_TestData() { yield return new object[] { new Rectangle(1, 4, 8, 16), new Point[] { new Point(32, 64), new Point(128, 256), new Point(512, 1024) }, new float[] { 12, 24, 30, 60, -100, -200 }, false, false }; diff --git a/src/libraries/System.Drawing.Common/tests/GraphicsTests.Core.cs b/src/libraries/System.Drawing.Common/tests/GraphicsTests.Core.cs new file mode 100644 index 0000000000000..3a137da17c7e5 --- /dev/null +++ b/src/libraries/System.Drawing.Common/tests/GraphicsTests.Core.cs @@ -0,0 +1,81 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Drawing.Drawing2D; +using System.Numerics; +using Xunit; + +namespace System.Drawing.Tests +{ + public partial class GraphicsTests + { + private static Matrix3x2 s_testMatrix = Matrix3x2.CreateRotation(45) * Matrix3x2.CreateScale(2) * Matrix3x2.CreateTranslation(new Vector2(10, 20)); + + + [ConditionalFact(Helpers.IsDrawingSupported)] + public void TransformElements_SetNonInvertibleMatrix_ThrowsArgumentException() + { + using (var image = new Bitmap(5, 5)) + using (Graphics graphics = Graphics.FromImage(image)) + { + Matrix3x2 matrix = new Matrix3x2(123, 24, 82, 16, 47, 30); + AssertExtensions.Throws(null, () => graphics.TransformElements = matrix); + } + } + + [ActiveIssue("https://github.com/dotnet/runtime/issues/22221", TestPlatforms.AnyUnix)] + [ConditionalFact(Helpers.IsDrawingSupported)] + public void TransformElements_GetSetWhenBusy_ThrowsInvalidOperationException() + { + using (var image = new Bitmap(10, 10)) + using (Graphics graphics = Graphics.FromImage(image)) + { + graphics.GetHdc(); + try + { + Assert.Throws(() => graphics.TransformElements); + Assert.Throws(() => graphics.TransformElements = Matrix3x2.Identity); + } + finally + { + graphics.ReleaseHdc(); + } + } + } + + [ConditionalFact(Helpers.IsDrawingSupported)] + public void TransformElements_GetSetWhenDisposed_ThrowsArgumentException() + { + using (var image = new Bitmap(10, 10)) + { + Graphics graphics = Graphics.FromImage(image); + graphics.Dispose(); + + AssertExtensions.Throws(null, () => graphics.TransformElements); + AssertExtensions.Throws(null, () => graphics.TransformElements = Matrix3x2.Identity); + } + } + + [ConditionalFact(Helpers.IsDrawingSupported)] + public void TransformElements_RoundTrip() + { + using (var image = new Bitmap(10, 10)) + using (Graphics graphics = Graphics.FromImage(image)) + { + graphics.TransformElements = s_testMatrix; + Assert.Equal(s_testMatrix, graphics.TransformElements); + + using (Matrix matrix = graphics.Transform) + { + Assert.Equal(s_testMatrix, matrix.MatrixElements); + } + + using (Matrix matrix = new Matrix()) + { + graphics.Transform = matrix; + Assert.True(graphics.TransformElements.IsIdentity); + } + } + } + } +} diff --git a/src/libraries/System.Drawing.Common/tests/GraphicsTests.cs b/src/libraries/System.Drawing.Common/tests/GraphicsTests.cs index ea823989bceb6..ba8246641c6e2 100644 --- a/src/libraries/System.Drawing.Common/tests/GraphicsTests.cs +++ b/src/libraries/System.Drawing.Common/tests/GraphicsTests.cs @@ -10,7 +10,7 @@ namespace System.Drawing.Tests { - public class GraphicsTests + public partial class GraphicsTests { [ActiveIssue("https://github.com/dotnet/runtime/issues/22221", TestPlatforms.AnyUnix)] [ConditionalFact(Helpers.IsDrawingSupported)] diff --git a/src/libraries/System.Drawing.Common/tests/System.Drawing.Common.Tests.csproj b/src/libraries/System.Drawing.Common/tests/System.Drawing.Common.Tests.csproj index 7e66b067e8d28..9dfd74f0454a2 100644 --- a/src/libraries/System.Drawing.Common/tests/System.Drawing.Common.Tests.csproj +++ b/src/libraries/System.Drawing.Common/tests/System.Drawing.Common.Tests.csproj @@ -1,4 +1,4 @@ - + true true @@ -94,7 +94,7 @@ Link="Common\System\IO\TempFile.cs" /> - + @@ -117,6 +117,8 @@ + + diff --git a/src/libraries/System.Windows.Extensions/src/System.Windows.Extensions.csproj b/src/libraries/System.Windows.Extensions/src/System.Windows.Extensions.csproj index 60b8f6992f965..0a7838d5e99f6 100644 --- a/src/libraries/System.Windows.Extensions/src/System.Windows.Extensions.csproj +++ b/src/libraries/System.Windows.Extensions/src/System.Windows.Extensions.csproj @@ -52,7 +52,7 @@ - +