Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement new GetContextInfo API overloads #49186

Merged
merged 12 commits into from
Apr 16, 2021
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)]
JeremyKuhne marked this conversation as resolved.
Show resolved Hide resolved
[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