| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442 |
- using System;
- using System.Collections.Generic;
- using Avalonia.Platform;
- using Avalonia.Rendering.SceneGraph;
- using Avalonia.Threading;
- using Avalonia.Utilities;
- using Avalonia.Media.Imaging;
- namespace Avalonia.Media
- {
- public abstract class DrawingContext : IDisposable
- {
- private static ThreadSafeObjectPool<Stack<RestoreState>> StateStackPool { get; } =
- ThreadSafeObjectPool<Stack<RestoreState>>.Default;
- private Stack<RestoreState>? _states;
- internal DrawingContext()
- {
-
- }
- public void Dispose()
- {
- if (_states != null)
- {
- while (_states.Count > 0)
- _states.Pop().Dispose();
- StateStackPool.ReturnAndSetNull(ref _states);
- }
- DisposeCore();
- }
-
- protected abstract void DisposeCore();
-
- /// <summary>
- /// Draws an image.
- /// </summary>
- /// <param name="source">The image.</param>
- /// <param name="rect">The rect in the output to draw to.</param>
- public virtual void DrawImage(IImage source, Rect rect)
- {
- _ = source ?? throw new ArgumentNullException(nameof(source));
- DrawImage(source, new Rect(source.Size), rect);
- }
- /// <summary>
- /// Draws an image.
- /// </summary>
- /// <param name="source">The image.</param>
- /// <param name="sourceRect">The rect in the image to draw.</param>
- /// <param name="destRect">The rect in the output to draw to.</param>
- /// <param name="bitmapInterpolationMode">The bitmap interpolation mode.</param>
- public virtual void DrawImage(IImage source, Rect sourceRect, Rect destRect,
- BitmapInterpolationMode bitmapInterpolationMode = default)
- {
- _ = source ?? throw new ArgumentNullException(nameof(source));
- source.Draw(this, sourceRect, destRect, bitmapInterpolationMode);
- }
-
- /// <summary>
- /// Draws a platform-specific bitmap impl.
- /// </summary>
- /// <param name="source">The bitmap image.</param>
- /// <param name="opacity">The opacity to draw with.</param>
- /// <param name="sourceRect">The rect in the image to draw.</param>
- /// <param name="destRect">The rect in the output to draw to.</param>
- /// <param name="bitmapInterpolationMode">The bitmap interpolation mode.</param>
- internal abstract void DrawBitmap(IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode = BitmapInterpolationMode.Default);
- /// <summary>
- /// Draws a line.
- /// </summary>
- /// <param name="pen">The stroke pen.</param>
- /// <param name="p1">The first point of the line.</param>
- /// <param name="p2">The second point of the line.</param>
- public void DrawLine(IPen pen, Point p1, Point p2)
- {
- if (PenIsVisible(pen))
- DrawLineCore(pen, p1, p2);
- }
- protected abstract void DrawLineCore(IPen pen, Point p1, Point p2);
- /// <summary>
- /// Draws a geometry.
- /// </summary>
- /// <param name="brush">The fill brush.</param>
- /// <param name="pen">The stroke pen.</param>
- /// <param name="geometry">The geometry.</param>
- public void DrawGeometry(IBrush? brush, IPen? pen, Geometry geometry)
- {
- if ((brush != null || PenIsVisible(pen)) && geometry.PlatformImpl != null)
- DrawGeometryCore(brush, pen, geometry.PlatformImpl);
- }
-
- /// <summary>
- /// Draws a geometry.
- /// </summary>
- /// <param name="brush">The fill brush.</param>
- /// <param name="pen">The stroke pen.</param>
- /// <param name="geometry">The geometry.</param>
- public void DrawGeometry(IBrush? brush, IPen? pen, IGeometryImpl geometry)
- {
- if ((brush != null || PenIsVisible(pen)))
- DrawGeometryCore(brush, pen, geometry);
- }
- protected abstract void DrawGeometryCore(IBrush? brush, IPen? pen, IGeometryImpl geometry);
- /// <summary>
- /// Draws a rectangle with the specified Brush and Pen.
- /// </summary>
- /// <param name="brush">The brush used to fill the rectangle, or <c>null</c> for no fill.</param>
- /// <param name="pen">The pen used to stroke the rectangle, or <c>null</c> for no stroke.</param>
- /// <param name="rect">The rectangle bounds.</param>
- /// <param name="radiusX">The radius in the X dimension of the rounded corners.
- /// This value will be clamped to the range of 0 to Width/2
- /// </param>
- /// <param name="radiusY">The radius in the Y dimension of the rounded corners.
- /// This value will be clamped to the range of 0 to Height/2
- /// </param>
- /// <param name="boxShadows">Box shadow effect parameters</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 DrawRectangle(IBrush? brush, IPen? pen, Rect rect,
- double radiusX = 0, double radiusY = 0,
- BoxShadows boxShadows = default)
- {
- if (brush == null && !PenIsVisible(pen) && boxShadows.Count == 0)
- return;
- if (!MathUtilities.IsZero(radiusX))
- {
- radiusX = Math.Min(radiusX, rect.Width / 2);
- }
- if (!MathUtilities.IsZero(radiusY))
- {
- radiusY = Math.Min(radiusY, rect.Height / 2);
- }
-
- DrawRectangleCore(brush, pen, new RoundedRect(rect, radiusX, radiusY), boxShadows);
- }
-
- /// <summary>
- /// Draws a rectangle with the specified Brush and Pen.
- /// </summary>
- /// <param name="brush">The brush used to fill the rectangle, or <c>null</c> for no fill.</param>
- /// <param name="pen">The pen used to stroke the rectangle, or <c>null</c> for no stroke.</param>
- /// <param name="rrect">The rectangle bounds.</param>
- /// <param name="boxShadows">Box shadow effect parameters</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 DrawRectangle(IBrush? brush, IPen? pen, RoundedRect rrect, BoxShadows boxShadows = default)
- {
- if (brush == null && !PenIsVisible(pen) && boxShadows.Count == 0)
- return;
- DrawRectangleCore(brush, pen, rrect, boxShadows);
- }
- protected abstract void DrawRectangleCore(IBrush? brush, IPen? pen, RoundedRect rrect,
- BoxShadows boxShadows = default);
-
- /// <summary>
- /// Draws the outline of a rectangle.
- /// </summary>
- /// <param name="pen">The pen.</param>
- /// <param name="rect">The rectangle bounds.</param>
- /// <param name="cornerRadius">The corner radius.</param>
- public void DrawRectangle(IPen pen, Rect rect, float cornerRadius = 0.0f) =>
- DrawRectangle(null, pen, rect, cornerRadius, cornerRadius);
- /// <summary>
- /// Draws a filled rectangle.
- /// </summary>
- /// <param name="brush">The brush.</param>
- /// <param name="rect">The rectangle bounds.</param>
- /// <param name="cornerRadius">The corner radius.</param>
- public void FillRectangle(IBrush brush, Rect rect, float cornerRadius = 0.0f) =>
- DrawRectangle(brush, null, 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))
- {
- var originX = center.X - radiusX;
- var originY = center.Y - radiusY;
- var width = radiusX * 2;
- var height = radiusY * 2;
- DrawEllipseCore(brush, pen, new Rect(originX, originY, width, height));
- }
- }
-
- /// <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 bounding rect.</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, Rect rect)
- {
- if (brush != null || PenIsVisible(pen))
- DrawEllipseCore(brush, pen, rect);
- }
- protected abstract void DrawEllipseCore(IBrush? brush, IPen? pen, Rect rect);
- /// <summary>
- /// Draws a custom drawing operation
- /// </summary>
- /// <param name="custom">custom operation</param>
- public abstract void Custom(ICustomDrawOperation custom);
- /// <summary>
- /// Draws text.
- /// </summary>
- /// <param name="origin">The upper-left corner of the text.</param>
- /// <param name="text">The text.</param>
- public virtual void DrawText(FormattedText text, Point origin)
- {
- _ = text ?? throw new ArgumentNullException(nameof(text));
-
- text.Draw(this, origin);
- }
- /// <summary>
- /// Draws a glyph run.
- /// </summary>
- /// <param name="foreground">The foreground brush.</param>
- /// <param name="glyphRun">The glyph run.</param>
- public abstract void DrawGlyphRun(IBrush? foreground, GlyphRun glyphRun);
- public record struct PushedState : IDisposable
- {
- private readonly DrawingContext _context;
- private readonly int _level;
- public PushedState(DrawingContext context)
- {
- _context = context;
- _level = _context._states!.Count;
- }
- public void Dispose()
- {
- if(_context?._states == null)
- return;
- if(_context._states.Count != _level)
- throw new InvalidOperationException("Wrong Push/Pop state order");
- _context._states.Pop().Dispose();
- }
- }
-
- private readonly record struct RestoreState : IDisposable
- {
- private readonly DrawingContext _context;
- private readonly PushedStateType _type;
- public enum PushedStateType
- {
- None,
- Transform,
- Opacity,
- Clip,
- GeometryClip,
- OpacityMask,
- BitmapBlendMode
- }
- public RestoreState(DrawingContext context, PushedStateType type)
- {
- _context = context;
- _type = type;
- }
- public void Dispose()
- {
- if (_type == PushedStateType.None)
- return;
- if (_context._states is null)
- throw new ObjectDisposedException(nameof(DrawingContext));
- if (_type == PushedStateType.Transform)
- _context.PopTransformCore();
- else if (_type == PushedStateType.Clip)
- _context.PopClipCore();
- else if (_type == PushedStateType.Opacity)
- _context.PopOpacityCore();
- else if (_type == PushedStateType.GeometryClip)
- _context.PopGeometryClipCore();
- else if (_type == PushedStateType.OpacityMask)
- _context.PopOpacityMaskCore();
- else if (_type == PushedStateType.BitmapBlendMode)
- _context.PopBitmapBlendModeCore();
- }
- }
- /// <summary>
- /// Pushes a clip rectangle.
- /// </summary>
- /// <param name="clip">The clip rectangle.</param>
- /// <returns>A disposable used to undo the clip rectangle.</returns>
- public PushedState PushClip(RoundedRect clip)
- {
- PushClipCore(clip);
- _states ??= StateStackPool.Get();
- _states.Push(new RestoreState(this, RestoreState.PushedStateType.Clip));
- return new PushedState(this);
- }
- protected abstract void PushClipCore(RoundedRect rect);
- /// <summary>
- /// Pushes a clip rectangle.
- /// </summary>
- /// <param name="clip">The clip rectangle.</param>
- /// <returns>A disposable used to undo the clip rectangle.</returns>
- public PushedState PushClip(Rect clip)
- {
- PushClipCore(clip);
- _states ??= StateStackPool.Get();
- _states.Push(new RestoreState(this, RestoreState.PushedStateType.Clip));
- return new PushedState(this);
- }
-
- protected abstract void PushClipCore(Rect rect);
- /// <summary>
- /// Pushes a clip geometry.
- /// </summary>
- /// <param name="clip">The clip geometry.</param>
- /// <returns>A disposable used to undo the clip geometry.</returns>
- public PushedState PushGeometryClip(Geometry clip)
- {
- PushGeometryClipCore(clip);
- _states ??= StateStackPool.Get();
- _states.Push(new RestoreState(this, RestoreState.PushedStateType.GeometryClip));
- return new PushedState(this);
- }
-
- protected abstract void PushGeometryClipCore(Geometry clip);
- /// <summary>
- /// Pushes an opacity value.
- /// </summary>
- /// <param name="opacity">The opacity.</param>
- /// <param name="bounds">The bounds.</param>
- /// <returns>A disposable used to undo the opacity.</returns>
- public PushedState PushOpacity(double opacity, Rect bounds)
- {
- PushOpacityCore(opacity, bounds);
- _states ??= StateStackPool.Get();
- _states.Push(new RestoreState(this, RestoreState.PushedStateType.Opacity));
- return new PushedState(this);
- }
- protected abstract void PushOpacityCore(double opacity, Rect bounds);
- /// <summary>
- /// Pushes an opacity mask.
- /// </summary>
- /// <param name="mask">The opacity mask.</param>
- /// <param name="bounds">
- /// The size of the brush's target area. TODO: Are we sure this is needed?
- /// </param>
- /// <returns>A disposable to undo the opacity mask.</returns>
- public PushedState PushOpacityMask(IBrush mask, Rect bounds)
- {
- PushOpacityMaskCore(mask, bounds);
- _states ??= StateStackPool.Get();
- _states.Push(new RestoreState(this, RestoreState.PushedStateType.OpacityMask));
- return new PushedState(this);
- }
- protected abstract void PushOpacityMaskCore(IBrush mask, Rect bounds);
- public PushedState PushBitmapBlendMode(BitmapBlendingMode blendingMode)
- {
- PushBitmapBlendMode(blendingMode);
- _states ??= StateStackPool.Get();
- _states.Push(new RestoreState(this, RestoreState.PushedStateType.BitmapBlendMode));
- return new PushedState(this);
- }
- protected abstract void PushBitmapBlendModeCore(BitmapBlendingMode blendingMode);
- /// <summary>
- /// Pushes a matrix transformation.
- /// </summary>
- /// <param name="matrix">The matrix</param>
- /// <returns>A disposable used to undo the transformation.</returns>
- public PushedState PushTransform(Matrix matrix)
- {
- PushTransformCore(matrix);
- _states ??= StateStackPool.Get();
- _states.Push(new RestoreState(this, RestoreState.PushedStateType.Transform));
- return new PushedState(this);
- }
- [Obsolete("Use PushTransform")]
- public PushedState PushPreTransform(Matrix matrix) => PushTransform(matrix);
- [Obsolete("Use PushTransform")]
- public PushedState PushPostTransform(Matrix matrix) => PushTransform(matrix);
- [Obsolete("Use PushTransform")]
- public PushedState PushTransformContainer() => PushTransform(Matrix.Identity);
-
-
- protected abstract void PushTransformCore(Matrix matrix);
- protected abstract void PopClipCore();
- protected abstract void PopGeometryClipCore();
- protected abstract void PopOpacityCore();
- protected abstract void PopOpacityMaskCore();
- protected abstract void PopBitmapBlendModeCore();
- protected abstract void PopTransformCore();
-
- private static bool PenIsVisible(IPen? pen)
- {
- return pen?.Brush != null && pen.Thickness > 0;
- }
- }
- }
|