diff --git a/docs/project/list-of-diagnostics.md b/docs/project/list-of-diagnostics.md index df1f73e6d7b06..0c4093652fd08 100644 --- a/docs/project/list-of-diagnostics.md +++ b/docs/project/list-of-diagnostics.md @@ -27,6 +27,7 @@ Currently the identifiers `SYSLIB0001` through `SYSLIB0999` are carved out for o | __`SYSLIB0012`__ | Assembly.CodeBase and Assembly.EscapedCodeBase are only included for .NET Framework compatibility. Use Assembly.Location instead. | | __`SYSLIB0013`__ | Uri.EscapeUriString can corrupt the Uri string in some cases. Consider using Uri.EscapeDataString for query string components instead. | | __`SYSLIB0015`__ | DisablePrivateReflectionAttribute has no effect in .NET 6.0+ applications. | +| __`SYSLIB0016`__ | Use the Graphics.GetContextInfo overloads that accept arguments for better performance and fewer allocations. | ### Analyzer warnings (`SYSLIB1001` - `SYSLIB1999`) | Diagnostic ID | Description | @@ -54,4 +55,4 @@ Currently the identifiers `SYSLIB0001` through `SYSLIB0999` are carved out for o | __`SYSLIB1021`__ | Can't have the same template with different casing | | __`SYSLIB1022`__ | Can't have malformed format strings (like dangling {, etc) | | __`SYSLIB1023`__ | Generating more than 6 arguments is not supported | -| __`SYSLIB1029`__ | *_Blocked range `SYSLIB1024`-`SYSLIB1029` for logging._* | \ No newline at end of file +| __`SYSLIB1029`__ | *_Blocked range `SYSLIB1024`-`SYSLIB1029` for logging._* | diff --git a/src/libraries/Common/src/System/Obsoletions.cs b/src/libraries/Common/src/System/Obsoletions.cs index a4f6fb66e1f75..52c8042c9bee0 100644 --- a/src/libraries/Common/src/System/Obsoletions.cs +++ b/src/libraries/Common/src/System/Obsoletions.cs @@ -51,5 +51,8 @@ internal static class Obsoletions internal const string DisablePrivateReflectionAttributeMessage = "DisablePrivateReflectionAttribute has no effect in .NET 6.0+ applications."; internal const string DisablePrivateReflectionAttributeDiagId = "SYSLIB0015"; + + internal const string GetContextInfoMessage = "Use the Graphics.GetContextInfo overloads that accept arguments for better performance and fewer allocations."; + internal const string GetContextInfoDiagId = "SYSLIB0016"; } } diff --git a/src/libraries/Common/tests/System/Drawing/Helpers.cs b/src/libraries/Common/tests/System/Drawing/Helpers.cs index 162cf6abcb072..a4567ab687831 100644 --- a/src/libraries/Common/tests/System/Drawing/Helpers.cs +++ b/src/libraries/Common/tests/System/Drawing/Helpers.cs @@ -20,6 +20,7 @@ public static class Helpers public const string GdiPlusIsAvailableNotWindows7 = nameof(Helpers) + "." + nameof(GetGdiPlusIsAvailableNotWindows7); public const string AnyInstalledPrinters = nameof(Helpers) + "." + nameof(IsAnyInstalledPrinters); public const string WindowsRS3OrEarlier = nameof(Helpers) + "." + nameof(IsWindowsRS3OrEarlier); + public const string IsWindows = nameof(Helpers) + "." + nameof(GetIsWindows); public static bool GetIsDrawingSupported() => PlatformDetection.IsDrawingSupported; @@ -53,6 +54,8 @@ public static bool GetIsWindowsOrAtLeastLibgdiplus6() return installedVersion.Major >= 6; } + public static bool GetIsWindows() => PlatformDetection.IsDrawingSupported && PlatformDetection.IsWindows; + public static bool IsNotUnix => PlatformDetection.IsWindows; public static bool IsWindowsRS3OrEarlier => !PlatformDetection.IsWindows10Version1803OrGreater; 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 157484d8543ff..e8531366f99be 100644 --- a/src/libraries/System.Drawing.Common/ref/System.Drawing.Common.cs +++ b/src/libraries/System.Drawing.Common/ref/System.Drawing.Common.cs @@ -595,7 +595,13 @@ public void Flush(System.Drawing.Drawing2D.FlushIntention intention) { } public static System.Drawing.Graphics FromHwndInternal(System.IntPtr hwnd) { throw null; } public static System.Drawing.Graphics FromImage(System.Drawing.Image image) { throw null; } [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] + [System.Runtime.Versioning.SupportedOSPlatformAttribute("windows")] + [System.ObsoleteAttribute("Use the Graphics.GetContextInfo overloads that accept arguments for better performance and fewer allocations.", DiagnosticId = "SYSLIB0016", UrlFormat = "https://aka.ms/dotnet-warnings/{0}")] public object GetContextInfo() { throw null; } + [System.Runtime.Versioning.SupportedOSPlatformAttribute("windows")] + public void GetContextInfo(out PointF offset) { throw null; } + [System.Runtime.Versioning.SupportedOSPlatformAttribute("windows")] + public void GetContextInfo(out PointF offset, out Region? clip) { throw null; } public static System.IntPtr GetHalftonePalette() { throw null; } public System.IntPtr GetHdc() { throw null; } public System.Drawing.Color GetNearestColor(System.Drawing.Color color) { 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 5e56a68614b59..c5678ab357925 100644 --- a/src/libraries/System.Drawing.Common/ref/System.Drawing.Common.csproj +++ b/src/libraries/System.Drawing.Common/ref/System.Drawing.Common.csproj @@ -1,7 +1,8 @@ - + $(NetCoreAppCurrent);netcoreapp3.0 true + true enable 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 153bbc77f97fc..a0e1544cb8be0 100644 --- a/src/libraries/System.Drawing.Common/src/System.Drawing.Common.csproj +++ b/src/libraries/System.Drawing.Common/src/System.Drawing.Common.csproj @@ -7,6 +7,7 @@ true $(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent)-Unix;$(NetCoreAppCurrent);netcoreapp3.0-windows;netcoreapp3.0-Unix;netcoreapp3.0 true + true enable @@ -30,6 +31,7 @@ + @@ -164,6 +166,8 @@ Link="Common\Interop\Windows\Gdi32\Interop.RasterOp.cs" /> + System.Drawing.DefaultComponent.bmp diff --git a/src/libraries/System.Drawing.Common/src/System/Drawing/Graphics.Unix.cs b/src/libraries/System.Drawing.Common/src/System/Drawing/Graphics.Unix.cs index 3da1617e5979b..cba7e4aab0a55 100644 --- a/src/libraries/System.Drawing.Common/src/System/Drawing/Graphics.Unix.cs +++ b/src/libraries/System.Drawing.Common/src/System/Drawing/Graphics.Unix.cs @@ -41,6 +41,7 @@ using System.Runtime.InteropServices; using System.Text; using Gdip = System.Drawing.SafeNativeMethods.Gdip; +using System.Runtime.Versioning; namespace System.Drawing { @@ -576,11 +577,26 @@ public RectangleF VisibleClipBounds } [EditorBrowsable(EditorBrowsableState.Never)] + [SupportedOSPlatform("windows")] public object GetContextInfo() { throw new NotImplementedException(); } + [EditorBrowsable(EditorBrowsableState.Never)] + [SupportedOSPlatform("windows")] + public void GetContextInfo(out PointF offset) + { + throw new PlatformNotSupportedException(); + } + + [EditorBrowsable(EditorBrowsableState.Never)] + [SupportedOSPlatform("windows")] + public void GetContextInfo(out PointF offset, out Region? clip) + { + throw new PlatformNotSupportedException(); + } + private void CheckErrorStatus(int status) { Gdip.CheckStatus(status); diff --git a/src/libraries/System.Drawing.Common/src/System/Drawing/Graphics.Windows.cs b/src/libraries/System.Drawing.Common/src/System/Drawing/Graphics.Windows.cs index f523e1c4e58b5..929ea19c4fad8 100644 --- a/src/libraries/System.Drawing.Common/src/System/Drawing/Graphics.Windows.cs +++ b/src/libraries/System.Drawing.Common/src/System/Drawing/Graphics.Windows.cs @@ -8,8 +8,10 @@ using System.Drawing.Imaging; using System.Drawing.Internal; using System.Globalization; +using System.Numerics; using System.Runtime.ConstrainedExecution; using System.Runtime.InteropServices; +using System.Runtime.Versioning; using Gdip = System.Drawing.SafeNativeMethods.Gdip; namespace System.Drawing @@ -682,40 +684,59 @@ public unsafe void EnumerateMetafile( /// WARNING: This method is for internal FX support only. /// [EditorBrowsable(EditorBrowsableState.Never)] + [Obsolete(Obsoletions.GetContextInfoMessage, DiagnosticId = Obsoletions.GetContextInfoDiagId, UrlFormat = Obsoletions.SharedUrlFormat)] + [SupportedOSPlatform("windows")] public object GetContextInfo() { - Region cumulClip = Clip; // current context clip. - Matrix cumulTransform = Transform; // current context transform. - PointF currentOffset = PointF.Empty; // offset of current context. - PointF totalOffset = PointF.Empty; // absolute coord offset of top context. + GetContextInfo(out Matrix3x2 cumulativeTransform, calculateClip: true, out Region? cumulativeClip); + return new object[] { cumulativeClip ?? new Region(), new Matrix(cumulativeTransform) }; + } - if (!cumulTransform.IsIdentity) - { - currentOffset = cumulTransform.Offset; - } + private void GetContextInfo(out Matrix3x2 cumulativeTransform, bool calculateClip, out Region? cumulativeClip) + { + cumulativeClip = calculateClip ? GetRegionIfNotInfinite() : null; // Current context clip. + cumulativeTransform = TransformElements; // Current context transform. + Vector2 currentOffset = default; // Offset of current context. + Vector2 totalOffset = default; // Absolute coordinate offset of top context. GraphicsContext? context = _previousContext; - while (context != null) + if (!cumulativeTransform.IsIdentity) { - if (!context.TransformOffset.IsEmpty) + currentOffset = cumulativeTransform.Translation; + } + + while (context is not null) + { + if (!context.TransformOffset.IsEmpty()) { - cumulTransform.Translate(context.TransformOffset.X, context.TransformOffset.Y); + cumulativeTransform.Translate(context.TransformOffset); } - if (!currentOffset.IsEmpty) + if (!currentOffset.IsEmpty()) { // The location of the GDI+ clip region is relative to the coordinate origin after any translate transform // has been applied. We need to intersect regions using the same coordinate origin relative to the previous // context. - cumulClip.Translate(currentOffset.X, currentOffset.Y); + + // If we don't have a cumulative clip, we're infinite, and translation on infinite regions is a no-op. + cumulativeClip?.Translate(currentOffset.X, currentOffset.Y); totalOffset.X += currentOffset.X; totalOffset.Y += currentOffset.Y; } - if (context.Clip != null) + // Context only stores clips if they are not infinite. Intersecting a clip with an infinite clip is a no-op. + if (calculateClip && context.Clip is not null) { - cumulClip.Intersect(context.Clip); + // Intersecting an infinite clip with another is just a copy of the second clip. + if (cumulativeClip is null) + { + cumulativeClip = context.Clip; + } + else + { + cumulativeClip.Intersect(context.Clip); + } } currentOffset = context.TransformOffset; @@ -732,14 +753,41 @@ public object GetContextInfo() } while (context.IsCumulative); } - if (!totalOffset.IsEmpty) + if (!totalOffset.IsEmpty()) { // We need now to reset the total transform in the region so when calling Region.GetHRgn(Graphics) // the HRegion is properly offset by GDI+ based on the total offset of the graphics object. - cumulClip.Translate(-totalOffset.X, -totalOffset.Y); + + // If we don't have a cumulative clip, we're infinite, and translation on infinite regions is a no-op. + cumulativeClip?.Translate(-totalOffset.X, -totalOffset.Y); } + } + + /// + /// Gets the cumulative offset. + /// + /// The cumulative offset. + [EditorBrowsable(EditorBrowsableState.Never)] + [SupportedOSPlatform("windows")] + public void GetContextInfo(out PointF offset) + { + GetContextInfo(out Matrix3x2 cumulativeTransform, calculateClip: false, out _); + Vector2 translation = cumulativeTransform.Translation; + offset = new PointF(translation.X, translation.Y); + } - return new object[] { cumulClip, cumulTransform }; + /// + /// Gets the cumulative offset and clip region. + /// + /// The cumulative offset. + /// The cumulative clip region or null if the clip region is infinite. + [EditorBrowsable(EditorBrowsableState.Never)] + [SupportedOSPlatform("windows")] + public void GetContextInfo(out PointF offset, out Region? clip) + { + GetContextInfo(out Matrix3x2 cumulativeTransform, calculateClip: true, out clip); + Vector2 translation = cumulativeTransform.Translation; + offset = new PointF(translation.X, translation.Y); } public RectangleF VisibleClipBounds 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 9ab0ebe423784..4917e8f33d831 100644 --- a/src/libraries/System.Drawing.Common/src/System/Drawing/Graphics.cs +++ b/src/libraries/System.Drawing.Common/src/System/Drawing/Graphics.cs @@ -2465,5 +2465,38 @@ private void IgnoreMetafileErrors(Image image, ref int errorStatus) if (errorStatus != Gdip.Ok && image.RawFormat.Equals(ImageFormat.Emf)) errorStatus = Gdip.Ok; } + + /// + /// Creates a Region class only if the native region is not infinite. + /// + internal Region? GetRegionIfNotInfinite() + { + Gdip.CheckStatus(Gdip.GdipCreateRegion(out IntPtr regionHandle)); + try + { + Gdip.GdipGetClip(new HandleRef(this, NativeGraphics), new HandleRef(null, regionHandle)); + Gdip.CheckStatus(Gdip.GdipIsInfiniteRegion( + new HandleRef(null, regionHandle), + new HandleRef(this, NativeGraphics), + out int isInfinite)); + + if (isInfinite != 0) + { + // Infinite + return null; + } + + Region region = new Region(regionHandle); + regionHandle = IntPtr.Zero; + return region; + } + finally + { + if (regionHandle != IntPtr.Zero) + { + Gdip.GdipDeleteRegion(new HandleRef(null, regionHandle)); + } + } + } } } diff --git a/src/libraries/System.Drawing.Common/src/System/Drawing/GraphicsContext.cs b/src/libraries/System.Drawing.Common/src/System/Drawing/GraphicsContext.cs index 82340aa17020f..c1587d0e383a6 100644 --- a/src/libraries/System.Drawing.Common/src/System/Drawing/GraphicsContext.cs +++ b/src/libraries/System.Drawing.Common/src/System/Drawing/GraphicsContext.cs @@ -1,7 +1,7 @@ // 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; namespace System.Drawing { @@ -10,58 +10,10 @@ namespace System.Drawing /// internal sealed class GraphicsContext : IDisposable { - /// - /// The state that identifies the context. - /// - private int _contextState; - - /// - /// The context's translate transform. - /// - private PointF _transformOffset; - - /// - /// The context's clip region. - /// - private Region? _clipRegion; - - /// - /// The next context up the stack. - /// - private GraphicsContext? _nextContext; - - /// - /// The previous context down the stack. - /// - private GraphicsContext? _prevContext; - - /// - /// Flags that determines whether the context was created for a Graphics.Save() operation. - /// This kind of contexts are cumulative across subsequent Save() calls so the top context - /// info is cumulative. This is not the same for contexts created for a Graphics.BeginContainer() - /// operation, in this case the new context information is reset. See Graphics.BeginContainer() - /// and Graphics.Save() for more information. - /// - private bool _isCumulative; - public GraphicsContext(Graphics g) { - Matrix transform = g.Transform; - if (!transform.IsIdentity) - { - _transformOffset = transform.Offset; - } - transform.Dispose(); - - Region clip = g.Clip; - if (clip.IsInfinite(g)) - { - clip.Dispose(); - } - else - { - _clipRegion = clip; - } + TransformOffset = g.TransformElements.Translation; + Clip = g.GetRegionIfNotInfinite(); } /// @@ -78,100 +30,46 @@ public void Dispose() /// public void Dispose(bool disposing) { - if (_nextContext != null) - { - // Dispose all contexts up the stack since they are relative to this one and its state will be invalid. - _nextContext.Dispose(); - _nextContext = null; - } + // Dispose all contexts up the stack since they are relative to this one and its state will be invalid. + Next?.Dispose(); + Next = null; - if (_clipRegion != null) - { - _clipRegion.Dispose(); - _clipRegion = null; - } + Clip?.Dispose(); + Clip = null; } /// /// The state id representing the GraphicsContext. /// - public int State - { - get - { - return _contextState; - } - set - { - _contextState = value; - } - } + public int State { get; set; } /// /// The translate transform in the GraphicsContext. /// - public PointF TransformOffset - { - get - { - return _transformOffset; - } - } + public Vector2 TransformOffset { get; private set; } /// - /// The clipping region the GraphicsContext. + /// The clipping region the GraphicsContext. /// - public Region? Clip - { - get - { - return _clipRegion; - } - } + public Region? Clip { get; private set; } /// /// The next GraphicsContext object in the stack. /// - public GraphicsContext? Next - { - get - { - return _nextContext; - } - set - { - _nextContext = value; - } - } + public GraphicsContext? Next { get; set; } /// /// The previous GraphicsContext object in the stack. /// - public GraphicsContext? Previous - { - get - { - return _prevContext; - } - set - { - _prevContext = value; - } - } + public GraphicsContext? Previous { get; set; } /// - /// Determines whether this context is cumulative or not. See filed for more info. + /// Flag that determines whether the context was created for a Graphics.Save() operation. + /// This kind of contexts are cumulative across subsequent Save() calls so the top context + /// info is cumulative. This is not the same for contexts created for a Graphics.BeginContainer() + /// operation, in this case the new context information is reset. See Graphics.BeginContainer() + /// and Graphics.Save() for more information. /// - public bool IsCumulative - { - get - { - return _isCumulative; - } - set - { - _isCumulative = value; - } - } + public bool IsCumulative { get; set; } } } diff --git a/src/libraries/System.Drawing.Common/src/System/Drawing/NumericsExtensions.cs b/src/libraries/System.Drawing.Common/src/System/Drawing/NumericsExtensions.cs new file mode 100644 index 0000000000000..1f468afdae269 --- /dev/null +++ b/src/libraries/System.Drawing.Common/src/System/Drawing/NumericsExtensions.cs @@ -0,0 +1,27 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using System.Text; +using System.Threading.Tasks; + +namespace System.Drawing +{ + /// + /// Helpers to allow using System.Numerics types like the System.Drawing equivalents. + /// + internal static class NumericsExtensions + { + internal static void Translate(this ref Matrix3x2 matrix, Vector2 offset) + { + // Replicating what Matrix.Translate(float offsetX, float offsetY) does. + matrix.M31 += (offset.X * matrix.M11) + (offset.Y * matrix.M21); + matrix.M32 += (offset.X * matrix.M12) + (offset.Y * matrix.M22); + } + + internal static bool IsEmpty(this Vector2 vector) => vector.X == 0 && vector.Y == 0; + } +} diff --git a/src/libraries/System.Drawing.Common/src/misc/GDI/WindowsGraphics.cs b/src/libraries/System.Drawing.Common/src/misc/GDI/WindowsGraphics.cs index d6f876ceee5a7..6257a8219199b 100644 --- a/src/libraries/System.Drawing.Common/src/misc/GDI/WindowsGraphics.cs +++ b/src/libraries/System.Drawing.Common/src/misc/GDI/WindowsGraphics.cs @@ -57,42 +57,29 @@ public static WindowsGraphics FromGraphics(Graphics g, ApplyGraphicsProperties p PointF offset = default; - Region? clipRgn = null; - Matrix? worldTransf = null; - - if ((properties & ApplyGraphicsProperties.TranslateTransform) != 0 || (properties & ApplyGraphicsProperties.Clipping) != 0) + if (properties != ApplyGraphicsProperties.None) { - if (g.GetContextInfo() is object[] data && data.Length == 2) + Region? clip = null; + + if (properties.HasFlag(ApplyGraphicsProperties.Clipping)) { - clipRgn = data[0] as Region; - worldTransf = data[1] as Matrix; + g.GetContextInfo(out offset, out clip); } - - if (worldTransf != null) + else { - if ((properties & ApplyGraphicsProperties.TranslateTransform) != 0) - { - offset = worldTransf.Offset; - } - - worldTransf.Dispose(); + g.GetContextInfo(out offset); } - if (clipRgn != null) + if (clip is not null) { - if ((properties & ApplyGraphicsProperties.Clipping) != 0) - { - // We have to create the WindowsRegion and dipose the Region object before locking the Graphics object, - // in case of an unlikely exception before releasing the WindowsRegion, the finalizer will do it for us. - // (no try-finally block since this method is used frequently - perf). - // If the Graphics.Clip has not been set (Region.IsInfinite) we don't need to apply it to the DC. - if (!clipRgn.IsInfinite(g)) - { - wr = WindowsRegion.FromRegion(clipRgn, g); // WindowsRegion will take ownership of the hRegion. - } - } + // We have to create the WindowsRegion and dipose the Region object before locking the Graphics object, + // in case of an unlikely exception before releasing the WindowsRegion, the finalizer will do it for us. + // (no try-finally block since this method is used frequently - perf). + + // If clipping has not been set (Region.IsInfinite) GetContextInfo will return a null Region. - clipRgn.Dispose(); // Disposing the Region object doesn't destroy the hRegion. + wr = WindowsRegion.FromRegion(clip, g); // WindowsRegion will take ownership of the hRegion. + clip.Dispose(); // Disposing the Region object doesn't destroy the hRegion. } } @@ -100,7 +87,7 @@ public static WindowsGraphics FromGraphics(Graphics g, ApplyGraphicsProperties p wg._graphics = g; // Apply transform and clip - if (wr != null) + if (wr is not null) { using (wr) { diff --git a/src/libraries/System.Drawing.Common/tests/Graphics_GetContextTests.Core.cs b/src/libraries/System.Drawing.Common/tests/Graphics_GetContextTests.Core.cs new file mode 100644 index 0000000000000..0e09cef317868 --- /dev/null +++ b/src/libraries/System.Drawing.Common/tests/Graphics_GetContextTests.Core.cs @@ -0,0 +1,149 @@ +// 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 Graphics_GetContextTests + { + [ConditionalFact(Helpers.IsWindows)] + public void GetContextInfo_New_DefaultGraphics() + { + using (var image = new Bitmap(10, 10)) + using (Graphics graphics = Graphics.FromImage(image)) + { + graphics.GetContextInfo(out PointF offset); + Assert.True(offset.IsEmpty); + + graphics.GetContextInfo(out offset, out Region? clip); + Assert.True(offset.IsEmpty); + Assert.Null(clip); + } + } + + [ConditionalFact(Helpers.IsWindows)] + public void GetContextInfo_New_Clipping() + { + using (var image = new Bitmap(10, 10)) + using (Graphics graphics = Graphics.FromImage(image)) + using (Region initialClip = new Region(new Rectangle(1, 2, 9, 10))) + { + graphics.Clip = initialClip; + + graphics.GetContextInfo(out PointF offset); + Assert.True(offset.IsEmpty); + + graphics.GetContextInfo(out offset, out Region? clip); + Assert.True(offset.IsEmpty); + Assert.NotNull(clip); + Assert.Equal(initialClip.GetBounds(graphics), clip.GetBounds(graphics)); + clip.Dispose(); + } + } + + [ConditionalFact(Helpers.IsWindows)] + public void GetContextInfo_New_Transform() + { + using (var image = new Bitmap(10, 10)) + using (Graphics graphics = Graphics.FromImage(image)) + { + graphics.TransformElements = Matrix3x2.CreateTranslation(1, 2); + + graphics.GetContextInfo(out PointF offset); + Assert.Equal(new PointF(1, 2), offset); + + graphics.GetContextInfo(out offset, out Region? clip); + Assert.Null(clip); + Assert.Equal(new PointF(1, 2), offset); + } + } + + [ConditionalFact(Helpers.IsWindows)] + public void GetContextInfo_New_ClipAndTransform() + { + using (var image = new Bitmap(10, 10)) + using (Graphics graphics = Graphics.FromImage(image)) + using (Region initialClip = new Region(new Rectangle(1, 2, 9, 10))) + { + graphics.Clip = initialClip; + graphics.TransformElements = Matrix3x2.CreateTranslation(1, 2); + + graphics.GetContextInfo(out PointF offset); + Assert.Equal(new PointF(1, 2), offset); + + graphics.GetContextInfo(out offset, out Region? clip); + Assert.NotNull(clip); + Assert.Equal(new RectangleF(0, 0, 9, 10), clip.GetBounds(graphics)); + Assert.Equal(new PointF(1, 2), offset); + clip.Dispose(); + } + } + + [ConditionalFact(Helpers.IsWindows)] + public void GetContextInfo_New_TransformAndClip() + { + using (var image = new Bitmap(10, 10)) + using (Graphics graphics = Graphics.FromImage(image)) + using (Region initialClip = new Region(new Rectangle(1, 2, 9, 10))) + { + graphics.TransformElements = Matrix3x2.CreateTranslation(1, 2); + graphics.Clip = initialClip; + + graphics.GetContextInfo(out PointF offset); + Assert.Equal(new PointF(1, 2), offset); + + graphics.GetContextInfo(out offset, out Region? clip); + Assert.NotNull(clip); + Assert.Equal(new RectangleF(1, 2, 9, 10), clip.GetBounds(graphics)); + Assert.Equal(new PointF(1, 2), offset); + clip.Dispose(); + } + } + + [ConditionalFact(Helpers.IsWindows)] + public void GetContextInfo_New_ClipAndTransformSaveState() + { + using (var image = new Bitmap(10, 10)) + using (Graphics graphics = Graphics.FromImage(image)) + using (Region initialClip = new Region(new Rectangle(1, 2, 9, 10))) + { + graphics.Clip = initialClip; + graphics.TransformElements = Matrix3x2.CreateTranslation(1, 2); + + GraphicsState state = graphics.Save(); + + graphics.GetContextInfo(out PointF offset); + Assert.Equal(new PointF(2, 4), offset); + + graphics.GetContextInfo(out offset, out Region? clip); + Assert.NotNull(clip); + Assert.Equal(new RectangleF(0, 0, 8, 8), clip.GetBounds(graphics)); + Assert.Equal(new PointF(2, 4), offset); + clip.Dispose(); + } + } + + [ConditionalFact(Helpers.IsWindows)] + public void GetContextInfo_New_ClipAndTransformSaveAndRestoreState() + { + using (var image = new Bitmap(10, 10)) + using (Graphics graphics = Graphics.FromImage(image)) + { + graphics.SetClip(new Rectangle(1, 2, 9, 10)); + graphics.TransformElements = Matrix3x2.CreateTranslation(1, 2); + + GraphicsState state = graphics.Save(); + graphics.GetContextInfo(out PointF offset, out Region? clip); + graphics.Restore(state); + + Assert.NotNull(clip); + Assert.Equal(new RectangleF(0, 0, 8, 8), clip.GetBounds(graphics)); + Assert.Equal(new PointF(2, 4), offset); + clip.Dispose(); + } + } + } +} diff --git a/src/libraries/System.Drawing.Common/tests/Graphics_GetContextTests.cs b/src/libraries/System.Drawing.Common/tests/Graphics_GetContextTests.cs new file mode 100644 index 0000000000000..deb05c2a03977 --- /dev/null +++ b/src/libraries/System.Drawing.Common/tests/Graphics_GetContextTests.cs @@ -0,0 +1,168 @@ +// 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 Xunit; + +namespace System.Drawing.Tests +{ +#pragma warning disable SYSLIB0016 // Type or member is obsolete + public partial class Graphics_GetContextTests : DrawingTest + { + [ConditionalFact(Helpers.IsWindows)] + public void GetContextInfo_DefaultGraphics() + { + using (var image = new Bitmap(10, 10)) + using (Graphics graphics = Graphics.FromImage(image)) + { + object info = graphics.GetContextInfo(); + Assert.IsType(info); + object[] infoArray = (object[])info; + Assert.Equal(2, infoArray.Length); + Assert.IsType(infoArray[0]); + Assert.IsType(infoArray[1]); + using (Region region = (Region)infoArray[0]) + using (Matrix matrix = (Matrix)infoArray[1]) + { + Assert.True(region.IsInfinite(graphics)); + Assert.True(matrix.IsIdentity); + } + } + } + + [ConditionalFact(Helpers.IsWindows)] + public void GetContextInfo_Clipping() + { + using (var image = new Bitmap(10, 10)) + using (Graphics graphics = Graphics.FromImage(image)) + using (Region initialClip = new Region(new Rectangle(1, 2, 9, 10))) + { + graphics.Clip = initialClip; + + object[] info = (object[])graphics.GetContextInfo(); + using (Region region = (Region)info[0]) + using (Matrix matrix = (Matrix)info[1]) + { + Assert.Equal(initialClip.GetBounds(graphics), region.GetBounds(graphics)); + Assert.True(matrix.IsIdentity); + } + } + } + + [ConditionalFact(Helpers.IsWindows)] + public void GetContextInfo_Transform() + { + using (var image = new Bitmap(10, 10)) + using (Graphics graphics = Graphics.FromImage(image)) + using (Matrix initialTransform = new Matrix()) + { + initialTransform.Translate(1, 2); + graphics.Transform = initialTransform; + + object[] info = (object[])graphics.GetContextInfo(); + using (Region region = (Region)info[0]) + using (Matrix matrix = (Matrix)info[1]) + { + Assert.True(region.IsInfinite(graphics)); + Assert.Equal(initialTransform, matrix); + } + } + } + + [ConditionalFact(Helpers.IsWindows)] + public void GetContextInfo_ClipAndTransform() + { + using (var image = new Bitmap(10, 10)) + using (Graphics graphics = Graphics.FromImage(image)) + using (Matrix initialTransform = new Matrix()) + using (Region initialClip = new Region(new Rectangle(1, 2, 9, 10))) + { + graphics.Clip = initialClip; + initialTransform.Translate(1, 2); + graphics.Transform = initialTransform; + + object[] info = (object[])graphics.GetContextInfo(); + using (Region region = (Region)info[0]) + using (Matrix matrix = (Matrix)info[1]) + { + Assert.Equal(new RectangleF(0, 0, 9, 10), region.GetBounds(graphics)); + Assert.Equal(initialTransform, matrix); + } + } + } + + [ConditionalFact(Helpers.IsWindows)] + public void GetContextInfo_TransformAndClip() + { + using (var image = new Bitmap(10, 10)) + using (Graphics graphics = Graphics.FromImage(image)) + using (Matrix initialTransform = new Matrix()) + using (Region initialClip = new Region(new Rectangle(1, 2, 9, 10))) + { + initialTransform.Translate(1, 2); + graphics.Transform = initialTransform; + graphics.Clip = initialClip; + + object[] info = (object[])graphics.GetContextInfo(); + using (Region region = (Region)info[0]) + using (Matrix matrix = (Matrix)info[1]) + { + Assert.Equal(new RectangleF(1, 2, 9, 10), region.GetBounds(graphics)); + Assert.Equal(initialTransform, matrix); + } + } + } + + [ConditionalFact(Helpers.IsWindows)] + public void GetContextInfo_ClipAndTransformSaveState() + { + using (var image = new Bitmap(10, 10)) + using (Graphics graphics = Graphics.FromImage(image)) + using (Matrix initialTransform = new Matrix()) + using (Region initialClip = new Region(new Rectangle(1, 2, 9, 10))) + { + graphics.Clip = initialClip; + initialTransform.Translate(1, 2); + graphics.Transform = initialTransform; + + GraphicsState state = graphics.Save(); + object[] info = (object[])graphics.GetContextInfo(); + + using (Region region = (Region)info[0]) + using (Matrix matrix = (Matrix)info[1]) + { + initialTransform.Translate(1, 2); + Assert.Equal(new RectangleF(0, 0, 8, 8), region.GetBounds(graphics)); + Assert.Equal(initialTransform, matrix); + } + } + } + + [ConditionalFact(Helpers.IsWindows)] + public void GetContextInfo_ClipAndTransformSaveAndRestoreState() + { + using (var image = new Bitmap(10, 10)) + using (Graphics graphics = Graphics.FromImage(image)) + using (Matrix initialTransform = new Matrix()) + using (Region initialClip = new Region(new Rectangle(1, 2, 9, 10))) + { + graphics.Clip = initialClip; + initialTransform.Translate(1, 2); + graphics.Transform = initialTransform; + + GraphicsState state = graphics.Save(); + object[] info = (object[])graphics.GetContextInfo(); + graphics.Restore(state); + + using (Region region = (Region)info[0]) + using (Matrix matrix = (Matrix)info[1]) + { + initialTransform.Translate(1, 2); + Assert.Equal(new RectangleF(0, 0, 8, 8), region.GetBounds(graphics)); + Assert.Equal(initialTransform, matrix); + } + } + } + } +#pragma warning restore SYSLIB0016 // Type or member is obsolete +} 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 9d0c15dec7797..e8f4d68133f0d 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 @@ -28,6 +28,7 @@ + @@ -87,12 +88,9 @@ - - - + + + @@ -119,6 +117,7 @@ +