Skip to content

Commit

Permalink
Implement new GetContextInfo API overloads (#49186)
Browse files Browse the repository at this point in the history
* Add basic GetContextInfo regression tests.

* Implement new GetContextInfo API overloads

Implements #47880, adding new, more performant overloads for GetContextInfo.

- Add helper for only creating a region if it isn't infinite
- Start an internal extensions class for easier mapping of System.Drawing concepts to System.Numerics types
- Simplify GraphicsContext

* Update WindowsGraphics to use new overloads.

* Skip tests when GDI+ isn't available.

* Add `[SupportedOSPlatform("windows")]`

* Use new Obsolete attribute pattern.

* Use Obsoletions.cs and conditionalize in ref.

* Address feedback

* Conditionalize the obsoletion in the src

* Update suppression in tests

* Apply obsoletions downlevel using an internal ObsoleteAttribute

Co-authored-by: Jeff Handley <jeff.handley@microsoft.com>
Co-authored-by: Santiago Fernandez Madero <safern@microsoft.com>
  • Loading branch information
3 people committed Apr 16, 2021
1 parent 237d863 commit 33033b2
Show file tree
Hide file tree
Showing 15 changed files with 521 additions and 178 deletions.
3 changes: 2 additions & 1 deletion docs/project/list-of-diagnostics.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand Down Expand Up @@ -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._* |
| __`SYSLIB1029`__ | *_Blocked range `SYSLIB1024`-`SYSLIB1029` for logging._* |
3 changes: 3 additions & 0 deletions src/libraries/Common/src/System/Obsoletions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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";
}
}
3 changes: 3 additions & 0 deletions src/libraries/Common/tests/System/Drawing/Helpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>$(NetCoreAppCurrent);netcoreapp3.0</TargetFrameworks>
<ExcludeCurrentNetCoreAppFromPackage>true</ExcludeCurrentNetCoreAppFromPackage>
<IncludeInternalObsoleteAttribute>true</IncludeInternalObsoleteAttribute>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
<IsPartialFacadeAssembly>true</IsPartialFacadeAssembly>
<TargetFrameworks>$(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent)-Unix;$(NetCoreAppCurrent);netcoreapp3.0-windows;netcoreapp3.0-Unix;netcoreapp3.0</TargetFrameworks>
<ExcludeCurrentNetCoreAppFromPackage>true</ExcludeCurrentNetCoreAppFromPackage>
<IncludeInternalObsoleteAttribute>true</IncludeInternalObsoleteAttribute>
<Nullable>enable</Nullable>
</PropertyGroup>
<PropertyGroup>
Expand All @@ -30,6 +31,7 @@
<Compile Include="System\Drawing\ImageConverter.cs" />
<Compile Include="System\Drawing\ImageFormatConverter.cs" />
<Compile Include="System\Drawing\ImageType.cs" />
<Compile Include="System\Drawing\NumericsExtensions.cs" />
<Compile Include="System\Drawing\Pen.cs" />
<Compile Include="System\Drawing\Pens.cs" />
<Compile Include="System\Drawing\RotateFlipType.cs" />
Expand Down Expand Up @@ -164,6 +166,8 @@
Link="Common\Interop\Windows\Gdi32\Interop.RasterOp.cs" />
<Compile Include="$(CommonPath)System\Text\ValueStringBuilder.cs"
Link="Common\System\Text\ValueStringBuilder.cs" />
<Compile Include="$(CommonPath)System\Obsoletions.cs"
Link="Common\System\Obsoletions.cs" />
<EmbeddedResource Include="Resources\System\Drawing\DefaultComponent.bmp">
<LogicalName>System.Drawing.DefaultComponent.bmp</LogicalName>
</EmbeddedResource>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
using System.Runtime.InteropServices;
using System.Text;
using Gdip = System.Drawing.SafeNativeMethods.Gdip;
using System.Runtime.Versioning;

namespace System.Drawing
{
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -682,40 +684,59 @@ public unsafe void EnumerateMetafile(
/// WARNING: This method is for internal FX support only.
/// </summary>
[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;
Expand All @@ -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);
}
}

/// <summary>
/// Gets the cumulative offset.
/// </summary>
/// <param name="offset">The cumulative offset.</param>
[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 };
/// <summary>
/// Gets the cumulative offset and clip region.
/// </summary>
/// <param name="offset">The cumulative offset.</param>
/// <param name="clip">The cumulative clip region or null if the clip region is infinite.</param>
[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
Expand Down
33 changes: 33 additions & 0 deletions src/libraries/System.Drawing.Common/src/System/Drawing/Graphics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2465,5 +2465,38 @@ private void IgnoreMetafileErrors(Image image, ref int errorStatus)
if (errorStatus != Gdip.Ok && image.RawFormat.Equals(ImageFormat.Emf))
errorStatus = Gdip.Ok;
}

/// <summary>
/// Creates a Region class only if the native region is not infinite.
/// </summary>
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));
}
}
}
}
}
Loading

0 comments on commit 33033b2

Please sign in to comment.