Skip to content

Commit

Permalink
Implement new GetContextInfo API overloads
Browse files Browse the repository at this point in the history
Implements dotnet#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
  • Loading branch information
JeremyKuhne committed Apr 15, 2021
1 parent db05a29 commit 8f221aa
Show file tree
Hide file tree
Showing 10 changed files with 312 additions and 142 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -595,7 +595,10 @@ 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)]
[Obsolete("Use one of the other overloads.")]
public object GetContextInfo() { throw null; }
public void GetContextInfo(out PointF offset) { throw null; }
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
Expand Up @@ -30,6 +30,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
Original file line number Diff line number Diff line change
Expand Up @@ -581,6 +581,18 @@ public object GetContextInfo()
throw new NotImplementedException();
}

[EditorBrowsable(EditorBrowsableState.Never)]
public void GetContextInfo(out PointF offset)
{
throw new PlatformNotSupportedException();
}

[EditorBrowsable(EditorBrowsableState.Never)]
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,6 +8,7 @@
using System.Drawing.Imaging;
using System.Drawing.Internal;
using System.Globalization;
using System.Numerics;
using System.Runtime.ConstrainedExecution;
using System.Runtime.InteropServices;
using Gdip = System.Drawing.SafeNativeMethods.Gdip;
Expand Down Expand Up @@ -682,40 +683,58 @@ public unsafe void EnumerateMetafile(
/// WARNING: This method is for internal FX support only.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete("Use one of the other overloads.")]
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 +751,39 @@ 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)]
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)]
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));
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -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
{
Expand All @@ -10,58 +10,10 @@ namespace System.Drawing
/// </summary>
internal sealed class GraphicsContext : IDisposable
{
/// <summary>
/// The state that identifies the context.
/// </summary>
private int _contextState;

/// <summary>
/// The context's translate transform.
/// </summary>
private PointF _transformOffset;

/// <summary>
/// The context's clip region.
/// </summary>
private Region? _clipRegion;

/// <summary>
/// The next context up the stack.
/// </summary>
private GraphicsContext? _nextContext;

/// <summary>
/// The previous context down the stack.
/// </summary>
private GraphicsContext? _prevContext;

/// <summary>
/// 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.
/// </summary>
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();
}

/// <summary>
Expand All @@ -78,100 +30,46 @@ public void Dispose()
/// </summary>
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;
}

/// <summary>
/// The state id representing the GraphicsContext.
/// </summary>
public int State
{
get
{
return _contextState;
}
set
{
_contextState = value;
}
}
public int State { get; set; }

/// <summary>
/// The translate transform in the GraphicsContext.
/// </summary>
public PointF TransformOffset
{
get
{
return _transformOffset;
}
}
public Vector2 TransformOffset { get; private set; }

/// <summary>
/// The clipping region the GraphicsContext.
/// The clipping region the GraphicsContext.
/// </summary>
public Region? Clip
{
get
{
return _clipRegion;
}
}
public Region? Clip { get; private set; }

/// <summary>
/// The next GraphicsContext object in the stack.
/// </summary>
public GraphicsContext? Next
{
get
{
return _nextContext;
}
set
{
_nextContext = value;
}
}
public GraphicsContext? Next { get; set; }

/// <summary>
/// The previous GraphicsContext object in the stack.
/// </summary>
public GraphicsContext? Previous
{
get
{
return _prevContext;
}
set
{
_prevContext = value;
}
}
public GraphicsContext? Previous { get; set; }

/// <summary>
/// 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.
/// </summary>
public bool IsCumulative
{
get
{
return _isCumulative;
}
set
{
_isCumulative = value;
}
}
public bool IsCumulative { get; set; }
}
}
Loading

0 comments on commit 8f221aa

Please sign in to comment.