Browse Source

Add support for drawing ellipses directly via DrawingContext

pull/7031/head
Dariusz Komosinski 4 years ago
parent
commit
b0317f46a5
  1. 4
      src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs
  2. 28
      src/Avalonia.Visuals/Media/DrawingContext.cs
  3. 12
      src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs
  4. 14
      src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs
  5. 118
      src/Avalonia.Visuals/Rendering/SceneGraph/EllipseNode.cs
  6. 3
      src/Avalonia.Visuals/Rendering/SceneGraph/RectangleNode.cs
  7. 28
      src/Skia/Avalonia.Skia/DrawingContextImpl.cs
  8. 39
      src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
  9. 4
      tests/Avalonia.Benchmarks/NullDrawingContextImpl.cs

4
src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs

@ -447,6 +447,10 @@ namespace Avalonia.Headless
}
public void DrawEllipse(IBrush brush, IPen pen, Rect rect)
{
}
public void DrawGlyphRun(IBrush foreground, GlyphRun glyphRun)
{

28
src/Avalonia.Visuals/Media/DrawingContext.cs

@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using Avalonia.Media.Imaging;
using Avalonia.Platform;
using Avalonia.Rendering.SceneGraph;
using Avalonia.Threading;
@ -190,6 +189,33 @@ namespace Avalonia.Media
DrawRectangle(null, pen, rect, cornerRadius, cornerRadius);
}
/// <summary>
/// Draws an ellipse with the specified Brush and Pen.
/// </summary>
/// <param name="brush">The brush used to fill the ellipse, or <c>null</c> for no fill.</param>
/// <param name="pen">The pen used to stroke the ellipse, or <c>null</c> for no stroke.</param>
/// <param name="center">The location of the center of the ellipse.</param>
/// <param name="radiusX">The horizontal radius of the ellipse.</param>
/// <param name="radiusY">The vertical radius of the ellipse.</param>
/// <remarks>
/// The brush and the pen can both be null. If the brush is null, then no fill is performed.
/// If the pen is null, then no stoke is performed. If both the pen and the brush are null, then the drawing is not visible.
/// </remarks>
public void DrawEllipse(IBrush brush, IPen pen, Point center, double radiusX, double radiusY)
{
if (brush == null && !PenIsVisible(pen))
{
return;
}
var originX = center.X - radiusX;
var originY = center.Y - radiusY;
var width = radiusX * 2;
var height = radiusY * 2;
PlatformImpl.DrawEllipse(brush, pen, new Rect(originX, originY, width, height));
}
/// <summary>
/// Draws a custom drawing operation
/// </summary>

12
src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs

@ -71,6 +71,18 @@ namespace Avalonia.Platform
void DrawRectangle(IBrush brush, IPen pen, RoundedRect rect,
BoxShadows boxShadows = default);
/// <summary>
/// Draws an ellipse with the specified Brush and Pen.
/// </summary>
/// <param name="brush">The brush used to fill the ellipse, or <c>null</c> for no fill.</param>
/// <param name="pen">The pen used to stroke the ellipse, or <c>null</c> for no stroke.</param>
/// <param name="rect">The ellipse bounds.</param>
/// <remarks>
/// The brush and the pen can both be null. If the brush is null, then no fill is performed.
/// If the pen is null, then no stoke is performed. If both the pen and the brush are null, then the drawing is not visible.
/// </remarks>
void DrawEllipse(IBrush brush, IPen pen, Rect rect);
/// <summary>
/// Draws text.
/// </summary>

14
src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs

@ -179,6 +179,20 @@ namespace Avalonia.Rendering.SceneGraph
}
}
public void DrawEllipse(IBrush brush, IPen pen, Rect rect)
{
var next = NextDrawAs<EllipseNode>();
if (next == null || !next.Item.Equals(Transform, brush, pen, rect))
{
Add(new EllipseNode(Transform, brush, pen, rect, CreateChildScene(brush)));
}
else
{
++_drawOperationindex;
}
}
public void Custom(ICustomDrawOperation custom)
{
var next = NextDrawAs<CustomDrawOperation>();

118
src/Avalonia.Visuals/Rendering/SceneGraph/EllipseNode.cs

@ -0,0 +1,118 @@
using System;
using System.Collections.Generic;
using Avalonia.Media;
using Avalonia.Media.Immutable;
using Avalonia.Platform;
using Avalonia.VisualTree;
namespace Avalonia.Rendering.SceneGraph
{
/// <summary>
/// A node in the scene graph which represents an ellipse draw.
/// </summary>
internal class EllipseNode : BrushDrawOperation
{
public EllipseNode(
Matrix transform,
IBrush brush,
IPen pen,
Rect rect,
IDictionary<IVisual, Scene> childScenes = null)
: base(rect.Inflate(pen?.Thickness ?? 0), transform)
{
Transform = transform;
Brush = brush?.ToImmutable();
Pen = pen?.ToImmutable();
Rect = rect;
ChildScenes = childScenes;
}
/// <summary>
/// Gets the fill brush.
/// </summary>
public IBrush Brush { get; }
/// <summary>
/// Gets the stroke pen.
/// </summary>
public ImmutablePen Pen { get; }
/// <summary>
/// Gets the transform with which the node will be drawn.
/// </summary>
public Matrix Transform { get; }
/// <summary>
/// Gets the rect of the ellipse to draw.
/// </summary>
public Rect Rect { get; }
public override IDictionary<IVisual, Scene> ChildScenes { get; }
public bool Equals(Matrix transform, IBrush brush, IPen pen, Rect rect)
{
return transform == Transform &&
Equals(brush, Brush) &&
Equals(Pen, pen) &&
rect.Equals(Rect);
}
public override void Render(IDrawingContextImpl context)
{
context.DrawEllipse(Brush, Pen, Rect);
}
public override bool HitTest(Point p)
{
if (!Transform.TryInvert(out Matrix inverted))
{
return false;
}
p *= inverted;
var center = Rect.Center;
var strokeThickness = Pen?.Thickness ?? 0;
var rx = Rect.Width / 2 + strokeThickness / 2;
var ry = Rect.Height / 2 + strokeThickness / 2;
var dx = p.X - center.X;
var dy = p.Y - center.Y;
if (Math.Abs(dx) > rx || Math.Abs(dy) > ry)
{
return false;
}
if (Brush != null)
{
return Contains(rx, ry);
}
else if (strokeThickness > 0)
{
bool inStroke = Contains(rx, ry);
rx = Rect.Width / 2 - strokeThickness / 2;
ry = Rect.Height / 2 - strokeThickness / 2;
bool inInner = Contains(rx, ry);
return inStroke && !inInner;
}
bool Contains(double radiusX, double radiusY)
{
var rx2 = radiusX * radiusX;
var ry2 = radiusY * radiusY;
var distance = ry2 * dx * dx + rx2 * dy * dy;
return distance < rx2 * ry2;
}
return false;
}
}
}

3
src/Avalonia.Visuals/Rendering/SceneGraph/RectangleNode.cs

@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using Avalonia.Media;
using Avalonia.Media.Immutable;
using Avalonia.Platform;

28
src/Skia/Avalonia.Skia/DrawingContextImpl.cs

@ -414,6 +414,34 @@ namespace Avalonia.Skia
}
}
/// <inheritdoc />
public void DrawEllipse(IBrush brush, IPen pen, Rect rect)
{
if (rect.Height <= 0 || rect.Width <= 0)
return;
var rc = rect.ToSKRect();
if (brush != null)
{
using (var paint = CreatePaint(_fillPaint, brush, rect.Size))
{
Canvas.DrawOval(rc, paint.Paint);
}
}
if (pen?.Brush != null)
{
using (var paint = CreatePaint(_strokePaint, pen, rect.Size))
{
if (paint.Paint is object)
{
Canvas.DrawOval(rc, paint.Paint);
}
}
}
}
/// <inheritdoc />
public void DrawText(IBrush foreground, Point origin, IFormattedTextImpl text)
{

39
src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs

@ -337,6 +337,45 @@ namespace Avalonia.Direct2D1.Media
}
}
/// <inheritdoc />
public void DrawEllipse(IBrush brush, IPen pen, Rect rect)
{
var rc = rect.ToDirect2D();
if (brush != null)
{
using (var b = CreateBrush(brush, rect.Size))
{
if (b.PlatformBrush != null)
{
_deviceContext.FillEllipse(new Ellipse
{
Point = rect.Center.ToSharpDX(),
RadiusX = (float)(rect.Width / 2),
RadiusY = (float)(rect.Height / 2)
}, b.PlatformBrush);
}
}
}
if (pen?.Brush != null)
{
using (var wrapper = CreateBrush(pen.Brush, rect.Size))
using (var d2dStroke = pen.ToDirect2DStrokeStyle(_deviceContext))
{
if (wrapper.PlatformBrush != null)
{
_deviceContext.DrawEllipse(new Ellipse
{
Point = rect.Center.ToSharpDX(),
RadiusX = (float)(rect.Width / 2),
RadiusY = (float)(rect.Height / 2)
}, wrapper.PlatformBrush, (float)pen.Thickness, d2dStroke);
}
}
}
}
/// <summary>
/// Draws text.
/// </summary>

4
tests/Avalonia.Benchmarks/NullDrawingContextImpl.cs

@ -39,6 +39,10 @@ namespace Avalonia.Benchmarks
{
}
public void DrawEllipse(IBrush brush, IPen pen, Rect rect)
{
}
public void DrawText(IBrush foreground, Point origin, IFormattedTextImpl text)
{
}

Loading…
Cancel
Save