DrawingContext.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442
  1. using System;
  2. using System.Collections.Generic;
  3. using Avalonia.Platform;
  4. using Avalonia.Rendering.SceneGraph;
  5. using Avalonia.Threading;
  6. using Avalonia.Utilities;
  7. using Avalonia.Media.Imaging;
  8. namespace Avalonia.Media
  9. {
  10. public abstract class DrawingContext : IDisposable
  11. {
  12. private static ThreadSafeObjectPool<Stack<RestoreState>> StateStackPool { get; } =
  13. ThreadSafeObjectPool<Stack<RestoreState>>.Default;
  14. private Stack<RestoreState>? _states;
  15. internal DrawingContext()
  16. {
  17. }
  18. public void Dispose()
  19. {
  20. if (_states != null)
  21. {
  22. while (_states.Count > 0)
  23. _states.Pop().Dispose();
  24. StateStackPool.ReturnAndSetNull(ref _states);
  25. }
  26. DisposeCore();
  27. }
  28. protected abstract void DisposeCore();
  29. /// <summary>
  30. /// Draws an image.
  31. /// </summary>
  32. /// <param name="source">The image.</param>
  33. /// <param name="rect">The rect in the output to draw to.</param>
  34. public virtual void DrawImage(IImage source, Rect rect)
  35. {
  36. _ = source ?? throw new ArgumentNullException(nameof(source));
  37. DrawImage(source, new Rect(source.Size), rect);
  38. }
  39. /// <summary>
  40. /// Draws an image.
  41. /// </summary>
  42. /// <param name="source">The image.</param>
  43. /// <param name="sourceRect">The rect in the image to draw.</param>
  44. /// <param name="destRect">The rect in the output to draw to.</param>
  45. /// <param name="bitmapInterpolationMode">The bitmap interpolation mode.</param>
  46. public virtual void DrawImage(IImage source, Rect sourceRect, Rect destRect,
  47. BitmapInterpolationMode bitmapInterpolationMode = default)
  48. {
  49. _ = source ?? throw new ArgumentNullException(nameof(source));
  50. source.Draw(this, sourceRect, destRect, bitmapInterpolationMode);
  51. }
  52. /// <summary>
  53. /// Draws a platform-specific bitmap impl.
  54. /// </summary>
  55. /// <param name="source">The bitmap image.</param>
  56. /// <param name="opacity">The opacity to draw with.</param>
  57. /// <param name="sourceRect">The rect in the image to draw.</param>
  58. /// <param name="destRect">The rect in the output to draw to.</param>
  59. /// <param name="bitmapInterpolationMode">The bitmap interpolation mode.</param>
  60. internal abstract void DrawBitmap(IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode = BitmapInterpolationMode.Default);
  61. /// <summary>
  62. /// Draws a line.
  63. /// </summary>
  64. /// <param name="pen">The stroke pen.</param>
  65. /// <param name="p1">The first point of the line.</param>
  66. /// <param name="p2">The second point of the line.</param>
  67. public void DrawLine(IPen pen, Point p1, Point p2)
  68. {
  69. if (PenIsVisible(pen))
  70. DrawLineCore(pen, p1, p2);
  71. }
  72. protected abstract void DrawLineCore(IPen pen, Point p1, Point p2);
  73. /// <summary>
  74. /// Draws a geometry.
  75. /// </summary>
  76. /// <param name="brush">The fill brush.</param>
  77. /// <param name="pen">The stroke pen.</param>
  78. /// <param name="geometry">The geometry.</param>
  79. public void DrawGeometry(IBrush? brush, IPen? pen, Geometry geometry)
  80. {
  81. if ((brush != null || PenIsVisible(pen)) && geometry.PlatformImpl != null)
  82. DrawGeometryCore(brush, pen, geometry.PlatformImpl);
  83. }
  84. /// <summary>
  85. /// Draws a geometry.
  86. /// </summary>
  87. /// <param name="brush">The fill brush.</param>
  88. /// <param name="pen">The stroke pen.</param>
  89. /// <param name="geometry">The geometry.</param>
  90. public void DrawGeometry(IBrush? brush, IPen? pen, IGeometryImpl geometry)
  91. {
  92. if ((brush != null || PenIsVisible(pen)))
  93. DrawGeometryCore(brush, pen, geometry);
  94. }
  95. protected abstract void DrawGeometryCore(IBrush? brush, IPen? pen, IGeometryImpl geometry);
  96. /// <summary>
  97. /// Draws a rectangle with the specified Brush and Pen.
  98. /// </summary>
  99. /// <param name="brush">The brush used to fill the rectangle, or <c>null</c> for no fill.</param>
  100. /// <param name="pen">The pen used to stroke the rectangle, or <c>null</c> for no stroke.</param>
  101. /// <param name="rect">The rectangle bounds.</param>
  102. /// <param name="radiusX">The radius in the X dimension of the rounded corners.
  103. /// This value will be clamped to the range of 0 to Width/2
  104. /// </param>
  105. /// <param name="radiusY">The radius in the Y dimension of the rounded corners.
  106. /// This value will be clamped to the range of 0 to Height/2
  107. /// </param>
  108. /// <param name="boxShadows">Box shadow effect parameters</param>
  109. /// <remarks>
  110. /// The brush and the pen can both be null. If the brush is null, then no fill is performed.
  111. /// 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.
  112. /// </remarks>
  113. public void DrawRectangle(IBrush? brush, IPen? pen, Rect rect,
  114. double radiusX = 0, double radiusY = 0,
  115. BoxShadows boxShadows = default)
  116. {
  117. if (brush == null && !PenIsVisible(pen) && boxShadows.Count == 0)
  118. return;
  119. if (!MathUtilities.IsZero(radiusX))
  120. {
  121. radiusX = Math.Min(radiusX, rect.Width / 2);
  122. }
  123. if (!MathUtilities.IsZero(radiusY))
  124. {
  125. radiusY = Math.Min(radiusY, rect.Height / 2);
  126. }
  127. DrawRectangleCore(brush, pen, new RoundedRect(rect, radiusX, radiusY), boxShadows);
  128. }
  129. /// <summary>
  130. /// Draws a rectangle with the specified Brush and Pen.
  131. /// </summary>
  132. /// <param name="brush">The brush used to fill the rectangle, or <c>null</c> for no fill.</param>
  133. /// <param name="pen">The pen used to stroke the rectangle, or <c>null</c> for no stroke.</param>
  134. /// <param name="rrect">The rectangle bounds.</param>
  135. /// <param name="boxShadows">Box shadow effect parameters</param>
  136. /// <remarks>
  137. /// The brush and the pen can both be null. If the brush is null, then no fill is performed.
  138. /// 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.
  139. /// </remarks>
  140. public void DrawRectangle(IBrush? brush, IPen? pen, RoundedRect rrect, BoxShadows boxShadows = default)
  141. {
  142. if (brush == null && !PenIsVisible(pen) && boxShadows.Count == 0)
  143. return;
  144. DrawRectangleCore(brush, pen, rrect, boxShadows);
  145. }
  146. protected abstract void DrawRectangleCore(IBrush? brush, IPen? pen, RoundedRect rrect,
  147. BoxShadows boxShadows = default);
  148. /// <summary>
  149. /// Draws the outline of a rectangle.
  150. /// </summary>
  151. /// <param name="pen">The pen.</param>
  152. /// <param name="rect">The rectangle bounds.</param>
  153. /// <param name="cornerRadius">The corner radius.</param>
  154. public void DrawRectangle(IPen pen, Rect rect, float cornerRadius = 0.0f) =>
  155. DrawRectangle(null, pen, rect, cornerRadius, cornerRadius);
  156. /// <summary>
  157. /// Draws a filled rectangle.
  158. /// </summary>
  159. /// <param name="brush">The brush.</param>
  160. /// <param name="rect">The rectangle bounds.</param>
  161. /// <param name="cornerRadius">The corner radius.</param>
  162. public void FillRectangle(IBrush brush, Rect rect, float cornerRadius = 0.0f) =>
  163. DrawRectangle(brush, null, rect, cornerRadius, cornerRadius);
  164. /// <summary>
  165. /// Draws an ellipse with the specified Brush and Pen.
  166. /// </summary>
  167. /// <param name="brush">The brush used to fill the ellipse, or <c>null</c> for no fill.</param>
  168. /// <param name="pen">The pen used to stroke the ellipse, or <c>null</c> for no stroke.</param>
  169. /// <param name="center">The location of the center of the ellipse.</param>
  170. /// <param name="radiusX">The horizontal radius of the ellipse.</param>
  171. /// <param name="radiusY">The vertical radius of the ellipse.</param>
  172. /// <remarks>
  173. /// The brush and the pen can both be null. If the brush is null, then no fill is performed.
  174. /// 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.
  175. /// </remarks>
  176. public void DrawEllipse(IBrush? brush, IPen? pen, Point center, double radiusX, double radiusY)
  177. {
  178. if (brush != null || PenIsVisible(pen))
  179. {
  180. var originX = center.X - radiusX;
  181. var originY = center.Y - radiusY;
  182. var width = radiusX * 2;
  183. var height = radiusY * 2;
  184. DrawEllipseCore(brush, pen, new Rect(originX, originY, width, height));
  185. }
  186. }
  187. /// <summary>
  188. /// Draws an ellipse with the specified Brush and Pen.
  189. /// </summary>
  190. /// <param name="brush">The brush used to fill the ellipse, or <c>null</c> for no fill.</param>
  191. /// <param name="pen">The pen used to stroke the ellipse, or <c>null</c> for no stroke.</param>
  192. /// <param name="rect">The bounding rect.</param>
  193. /// <remarks>
  194. /// The brush and the pen can both be null. If the brush is null, then no fill is performed.
  195. /// 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.
  196. /// </remarks>
  197. public void DrawEllipse(IBrush? brush, IPen? pen, Rect rect)
  198. {
  199. if (brush != null || PenIsVisible(pen))
  200. DrawEllipseCore(brush, pen, rect);
  201. }
  202. protected abstract void DrawEllipseCore(IBrush? brush, IPen? pen, Rect rect);
  203. /// <summary>
  204. /// Draws a custom drawing operation
  205. /// </summary>
  206. /// <param name="custom">custom operation</param>
  207. public abstract void Custom(ICustomDrawOperation custom);
  208. /// <summary>
  209. /// Draws text.
  210. /// </summary>
  211. /// <param name="origin">The upper-left corner of the text.</param>
  212. /// <param name="text">The text.</param>
  213. public virtual void DrawText(FormattedText text, Point origin)
  214. {
  215. _ = text ?? throw new ArgumentNullException(nameof(text));
  216. text.Draw(this, origin);
  217. }
  218. /// <summary>
  219. /// Draws a glyph run.
  220. /// </summary>
  221. /// <param name="foreground">The foreground brush.</param>
  222. /// <param name="glyphRun">The glyph run.</param>
  223. public abstract void DrawGlyphRun(IBrush? foreground, GlyphRun glyphRun);
  224. public record struct PushedState : IDisposable
  225. {
  226. private readonly DrawingContext _context;
  227. private readonly int _level;
  228. public PushedState(DrawingContext context)
  229. {
  230. _context = context;
  231. _level = _context._states!.Count;
  232. }
  233. public void Dispose()
  234. {
  235. if(_context?._states == null)
  236. return;
  237. if(_context._states.Count != _level)
  238. throw new InvalidOperationException("Wrong Push/Pop state order");
  239. _context._states.Pop().Dispose();
  240. }
  241. }
  242. private readonly record struct RestoreState : IDisposable
  243. {
  244. private readonly DrawingContext _context;
  245. private readonly PushedStateType _type;
  246. public enum PushedStateType
  247. {
  248. None,
  249. Transform,
  250. Opacity,
  251. Clip,
  252. GeometryClip,
  253. OpacityMask,
  254. BitmapBlendMode
  255. }
  256. public RestoreState(DrawingContext context, PushedStateType type)
  257. {
  258. _context = context;
  259. _type = type;
  260. }
  261. public void Dispose()
  262. {
  263. if (_type == PushedStateType.None)
  264. return;
  265. if (_context._states is null)
  266. throw new ObjectDisposedException(nameof(DrawingContext));
  267. if (_type == PushedStateType.Transform)
  268. _context.PopTransformCore();
  269. else if (_type == PushedStateType.Clip)
  270. _context.PopClipCore();
  271. else if (_type == PushedStateType.Opacity)
  272. _context.PopOpacityCore();
  273. else if (_type == PushedStateType.GeometryClip)
  274. _context.PopGeometryClipCore();
  275. else if (_type == PushedStateType.OpacityMask)
  276. _context.PopOpacityMaskCore();
  277. else if (_type == PushedStateType.BitmapBlendMode)
  278. _context.PopBitmapBlendModeCore();
  279. }
  280. }
  281. /// <summary>
  282. /// Pushes a clip rectangle.
  283. /// </summary>
  284. /// <param name="clip">The clip rectangle.</param>
  285. /// <returns>A disposable used to undo the clip rectangle.</returns>
  286. public PushedState PushClip(RoundedRect clip)
  287. {
  288. PushClipCore(clip);
  289. _states ??= StateStackPool.Get();
  290. _states.Push(new RestoreState(this, RestoreState.PushedStateType.Clip));
  291. return new PushedState(this);
  292. }
  293. protected abstract void PushClipCore(RoundedRect rect);
  294. /// <summary>
  295. /// Pushes a clip rectangle.
  296. /// </summary>
  297. /// <param name="clip">The clip rectangle.</param>
  298. /// <returns>A disposable used to undo the clip rectangle.</returns>
  299. public PushedState PushClip(Rect clip)
  300. {
  301. PushClipCore(clip);
  302. _states ??= StateStackPool.Get();
  303. _states.Push(new RestoreState(this, RestoreState.PushedStateType.Clip));
  304. return new PushedState(this);
  305. }
  306. protected abstract void PushClipCore(Rect rect);
  307. /// <summary>
  308. /// Pushes a clip geometry.
  309. /// </summary>
  310. /// <param name="clip">The clip geometry.</param>
  311. /// <returns>A disposable used to undo the clip geometry.</returns>
  312. public PushedState PushGeometryClip(Geometry clip)
  313. {
  314. PushGeometryClipCore(clip);
  315. _states ??= StateStackPool.Get();
  316. _states.Push(new RestoreState(this, RestoreState.PushedStateType.GeometryClip));
  317. return new PushedState(this);
  318. }
  319. protected abstract void PushGeometryClipCore(Geometry clip);
  320. /// <summary>
  321. /// Pushes an opacity value.
  322. /// </summary>
  323. /// <param name="opacity">The opacity.</param>
  324. /// <param name="bounds">The bounds.</param>
  325. /// <returns>A disposable used to undo the opacity.</returns>
  326. public PushedState PushOpacity(double opacity, Rect bounds)
  327. {
  328. PushOpacityCore(opacity, bounds);
  329. _states ??= StateStackPool.Get();
  330. _states.Push(new RestoreState(this, RestoreState.PushedStateType.Opacity));
  331. return new PushedState(this);
  332. }
  333. protected abstract void PushOpacityCore(double opacity, Rect bounds);
  334. /// <summary>
  335. /// Pushes an opacity mask.
  336. /// </summary>
  337. /// <param name="mask">The opacity mask.</param>
  338. /// <param name="bounds">
  339. /// The size of the brush's target area. TODO: Are we sure this is needed?
  340. /// </param>
  341. /// <returns>A disposable to undo the opacity mask.</returns>
  342. public PushedState PushOpacityMask(IBrush mask, Rect bounds)
  343. {
  344. PushOpacityMaskCore(mask, bounds);
  345. _states ??= StateStackPool.Get();
  346. _states.Push(new RestoreState(this, RestoreState.PushedStateType.OpacityMask));
  347. return new PushedState(this);
  348. }
  349. protected abstract void PushOpacityMaskCore(IBrush mask, Rect bounds);
  350. public PushedState PushBitmapBlendMode(BitmapBlendingMode blendingMode)
  351. {
  352. PushBitmapBlendMode(blendingMode);
  353. _states ??= StateStackPool.Get();
  354. _states.Push(new RestoreState(this, RestoreState.PushedStateType.BitmapBlendMode));
  355. return new PushedState(this);
  356. }
  357. protected abstract void PushBitmapBlendModeCore(BitmapBlendingMode blendingMode);
  358. /// <summary>
  359. /// Pushes a matrix transformation.
  360. /// </summary>
  361. /// <param name="matrix">The matrix</param>
  362. /// <returns>A disposable used to undo the transformation.</returns>
  363. public PushedState PushTransform(Matrix matrix)
  364. {
  365. PushTransformCore(matrix);
  366. _states ??= StateStackPool.Get();
  367. _states.Push(new RestoreState(this, RestoreState.PushedStateType.Transform));
  368. return new PushedState(this);
  369. }
  370. [Obsolete("Use PushTransform")]
  371. public PushedState PushPreTransform(Matrix matrix) => PushTransform(matrix);
  372. [Obsolete("Use PushTransform")]
  373. public PushedState PushPostTransform(Matrix matrix) => PushTransform(matrix);
  374. [Obsolete("Use PushTransform")]
  375. public PushedState PushTransformContainer() => PushTransform(Matrix.Identity);
  376. protected abstract void PushTransformCore(Matrix matrix);
  377. protected abstract void PopClipCore();
  378. protected abstract void PopGeometryClipCore();
  379. protected abstract void PopOpacityCore();
  380. protected abstract void PopOpacityMaskCore();
  381. protected abstract void PopBitmapBlendModeCore();
  382. protected abstract void PopTransformCore();
  383. private static bool PenIsVisible(IPen? pen)
  384. {
  385. return pen?.Brush != null && pen.Thickness > 0;
  386. }
  387. }
  388. }