DrawingContextImpl.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394
  1. // Copyright (c) The Avalonia Project. All rights reserved.
  2. // Licensed under the MIT license. See licence.md file in the project root for full license information.
  3. using System;
  4. using System.Collections;
  5. using System.Collections.Generic;
  6. using Avalonia.Media;
  7. using Avalonia.Platform;
  8. using SharpDX;
  9. using SharpDX.Direct2D1;
  10. using SharpDX.Mathematics.Interop;
  11. using IBitmap = Avalonia.Media.Imaging.IBitmap;
  12. namespace Avalonia.Direct2D1.Media
  13. {
  14. /// <summary>
  15. /// Draws using Direct2D1.
  16. /// </summary>
  17. public class DrawingContextImpl : IDrawingContextImpl, IDisposable
  18. {
  19. /// <summary>
  20. /// The Direct2D1 render target.
  21. /// </summary>
  22. private readonly SharpDX.Direct2D1.RenderTarget _renderTarget;
  23. /// <summary>
  24. /// The DirectWrite factory.
  25. /// </summary>
  26. private SharpDX.DirectWrite.Factory _directWriteFactory;
  27. private SharpDX.DXGI.SwapChain1 _swapChain;
  28. /// <summary>
  29. /// Initializes a new instance of the <see cref="DrawingContextImpl"/> class.
  30. /// </summary>
  31. /// <param name="renderTarget">The render target to draw to.</param>
  32. /// <param name="directWriteFactory">The DirectWrite factory.</param>
  33. /// <param name="swapChain">An optional swap chain associated with this drawing context.</param>
  34. public DrawingContextImpl(
  35. SharpDX.Direct2D1.RenderTarget renderTarget,
  36. SharpDX.DirectWrite.Factory directWriteFactory,
  37. SharpDX.DXGI.SwapChain1 swapChain = null)
  38. {
  39. _renderTarget = renderTarget;
  40. _directWriteFactory = directWriteFactory;
  41. _swapChain = swapChain;
  42. _renderTarget.BeginDraw();
  43. }
  44. /// <summary>
  45. /// Gets the current transform of the drawing context.
  46. /// </summary>
  47. public Matrix Transform
  48. {
  49. get { return _renderTarget.Transform.ToAvalonia(); }
  50. set { _renderTarget.Transform = value.ToDirect2D(); }
  51. }
  52. /// <inheritdoc/>
  53. public void Clear(Color color)
  54. {
  55. _renderTarget.Clear(color.ToDirect2D());
  56. }
  57. /// <summary>
  58. /// Ends a draw operation.
  59. /// </summary>
  60. public void Dispose()
  61. {
  62. foreach (var layer in _layerPool)
  63. layer.Dispose();
  64. try
  65. {
  66. _renderTarget.EndDraw();
  67. _swapChain?.Present(1, SharpDX.DXGI.PresentFlags.None);
  68. }
  69. catch (SharpDXException ex) when((uint)ex.HResult == 0x8899000C) // D2DERR_RECREATE_TARGET
  70. {
  71. throw new RenderTargetCorruptedException(ex);
  72. }
  73. }
  74. /// <summary>
  75. /// Draws a bitmap image.
  76. /// </summary>
  77. /// <param name="source">The bitmap image.</param>
  78. /// <param name="opacity">The opacity to draw with.</param>
  79. /// <param name="sourceRect">The rect in the image to draw.</param>
  80. /// <param name="destRect">The rect in the output to draw to.</param>
  81. public void DrawImage(IBitmapImpl source, double opacity, Rect sourceRect, Rect destRect)
  82. {
  83. var impl = (BitmapImpl)source;
  84. Bitmap d2d = impl.GetDirect2DBitmap(_renderTarget);
  85. _renderTarget.DrawBitmap(
  86. d2d,
  87. destRect.ToSharpDX(),
  88. (float)opacity,
  89. BitmapInterpolationMode.Linear,
  90. sourceRect.ToSharpDX());
  91. }
  92. /// <summary>
  93. /// Draws a line.
  94. /// </summary>
  95. /// <param name="pen">The stroke pen.</param>
  96. /// <param name="p1">The first point of the line.</param>
  97. /// <param name="p2">The second point of the line.</param>
  98. public void DrawLine(Pen pen, Point p1, Point p2)
  99. {
  100. if (pen != null)
  101. {
  102. var size = new Rect(p1, p2).Size;
  103. using (var d2dBrush = CreateBrush(pen.Brush, size))
  104. using (var d2dStroke = pen.ToDirect2DStrokeStyle(_renderTarget))
  105. {
  106. if (d2dBrush.PlatformBrush != null)
  107. {
  108. _renderTarget.DrawLine(
  109. p1.ToSharpDX(),
  110. p2.ToSharpDX(),
  111. d2dBrush.PlatformBrush,
  112. (float)pen.Thickness,
  113. d2dStroke);
  114. }
  115. }
  116. }
  117. }
  118. /// <summary>
  119. /// Draws a geometry.
  120. /// </summary>
  121. /// <param name="brush">The fill brush.</param>
  122. /// <param name="pen">The stroke pen.</param>
  123. /// <param name="geometry">The geometry.</param>
  124. public void DrawGeometry(IBrush brush, Pen pen, IGeometryImpl geometry)
  125. {
  126. if (brush != null)
  127. {
  128. using (var d2dBrush = CreateBrush(brush, geometry.Bounds.Size))
  129. {
  130. if (d2dBrush.PlatformBrush != null)
  131. {
  132. var impl = (GeometryImpl)geometry;
  133. _renderTarget.FillGeometry(impl.Geometry, d2dBrush.PlatformBrush);
  134. }
  135. }
  136. }
  137. if (pen != null)
  138. {
  139. using (var d2dBrush = CreateBrush(pen.Brush, geometry.GetRenderBounds(pen.Thickness).Size))
  140. using (var d2dStroke = pen.ToDirect2DStrokeStyle(_renderTarget))
  141. {
  142. if (d2dBrush.PlatformBrush != null)
  143. {
  144. var impl = (GeometryImpl)geometry;
  145. _renderTarget.DrawGeometry(impl.Geometry, d2dBrush.PlatformBrush, (float)pen.Thickness, d2dStroke);
  146. }
  147. }
  148. }
  149. }
  150. /// <summary>
  151. /// Draws the outline of a rectangle.
  152. /// </summary>
  153. /// <param name="pen">The pen.</param>
  154. /// <param name="rect">The rectangle bounds.</param>
  155. /// <param name="cornerRadius">The corner radius.</param>
  156. public void DrawRectangle(Pen pen, Rect rect, float cornerRadius)
  157. {
  158. using (var brush = CreateBrush(pen.Brush, rect.Size))
  159. using (var d2dStroke = pen.ToDirect2DStrokeStyle(_renderTarget))
  160. {
  161. if (brush.PlatformBrush != null)
  162. {
  163. if (cornerRadius == 0)
  164. {
  165. _renderTarget.DrawRectangle(
  166. rect.ToDirect2D(),
  167. brush.PlatformBrush,
  168. (float)pen.Thickness,
  169. d2dStroke);
  170. }
  171. else
  172. {
  173. _renderTarget.DrawRoundedRectangle(
  174. new RoundedRectangle { Rect = rect.ToDirect2D(), RadiusX = cornerRadius, RadiusY = cornerRadius },
  175. brush.PlatformBrush,
  176. (float)pen.Thickness,
  177. d2dStroke);
  178. }
  179. }
  180. }
  181. }
  182. /// <summary>
  183. /// Draws text.
  184. /// </summary>
  185. /// <param name="foreground">The foreground brush.</param>
  186. /// <param name="origin">The upper-left corner of the text.</param>
  187. /// <param name="text">The text.</param>
  188. public void DrawText(IBrush foreground, Point origin, IFormattedTextImpl text)
  189. {
  190. if (!string.IsNullOrEmpty(text.Text))
  191. {
  192. var impl = (FormattedTextImpl)text;
  193. using (var brush = CreateBrush(foreground, impl.Size))
  194. using (var renderer = new AvaloniaTextRenderer(this, _renderTarget, brush.PlatformBrush))
  195. {
  196. if (brush.PlatformBrush != null)
  197. {
  198. impl.TextLayout.Draw(renderer, (float)origin.X, (float)origin.Y);
  199. }
  200. }
  201. }
  202. }
  203. /// <summary>
  204. /// Draws a filled rectangle.
  205. /// </summary>
  206. /// <param name="brush">The brush.</param>
  207. /// <param name="rect">The rectangle bounds.</param>
  208. /// <param name="cornerRadius">The corner radius.</param>
  209. public void FillRectangle(IBrush brush, Rect rect, float cornerRadius)
  210. {
  211. using (var b = CreateBrush(brush, rect.Size))
  212. {
  213. if (b.PlatformBrush != null)
  214. {
  215. if (cornerRadius == 0)
  216. {
  217. _renderTarget.FillRectangle(rect.ToDirect2D(), b.PlatformBrush);
  218. }
  219. else
  220. {
  221. _renderTarget.FillRoundedRectangle(
  222. new RoundedRectangle
  223. {
  224. Rect = new RawRectangleF(
  225. (float)rect.X,
  226. (float)rect.Y,
  227. (float)rect.Right,
  228. (float)rect.Bottom),
  229. RadiusX = cornerRadius,
  230. RadiusY = cornerRadius
  231. },
  232. b.PlatformBrush);
  233. }
  234. }
  235. }
  236. }
  237. /// <summary>
  238. /// Pushes a clip rectange.
  239. /// </summary>
  240. /// <param name="clip">The clip rectangle.</param>
  241. /// <returns>A disposable used to undo the clip rectangle.</returns>
  242. public void PushClip(Rect clip)
  243. {
  244. _renderTarget.PushAxisAlignedClip(clip.ToSharpDX(), AntialiasMode.PerPrimitive);
  245. }
  246. public void PopClip()
  247. {
  248. _renderTarget.PopAxisAlignedClip();
  249. }
  250. readonly Stack<Layer> _layers = new Stack<Layer>();
  251. private readonly Stack<Layer> _layerPool = new Stack<Layer>();
  252. /// <summary>
  253. /// Pushes an opacity value.
  254. /// </summary>
  255. /// <param name="opacity">The opacity.</param>
  256. /// <returns>A disposable used to undo the opacity.</returns>
  257. public void PushOpacity(double opacity)
  258. {
  259. if (opacity < 1)
  260. {
  261. var parameters = new LayerParameters
  262. {
  263. ContentBounds = PrimitiveExtensions.RectangleInfinite,
  264. MaskTransform = PrimitiveExtensions.Matrix3x2Identity,
  265. Opacity = (float) opacity,
  266. };
  267. var layer = _layerPool.Count != 0 ? _layerPool.Pop() : new Layer(_renderTarget);
  268. _renderTarget.PushLayer(ref parameters, layer);
  269. _layers.Push(layer);
  270. }
  271. else
  272. _layers.Push(null);
  273. }
  274. public void PopOpacity()
  275. {
  276. PopLayer();
  277. }
  278. private void PopLayer()
  279. {
  280. var layer = _layers.Pop();
  281. if (layer != null)
  282. {
  283. _renderTarget.PopLayer();
  284. _layerPool.Push(layer);
  285. }
  286. }
  287. /// <summary>
  288. /// Creates a Direct2D brush wrapper for a Avalonia brush.
  289. /// </summary>
  290. /// <param name="brush">The avalonia brush.</param>
  291. /// <param name="destinationSize">The size of the brush's target area.</param>
  292. /// <returns>The Direct2D brush wrapper.</returns>
  293. public BrushImpl CreateBrush(IBrush brush, Size destinationSize)
  294. {
  295. var solidColorBrush = brush as ISolidColorBrush;
  296. var linearGradientBrush = brush as ILinearGradientBrush;
  297. var radialGradientBrush = brush as IRadialGradientBrush;
  298. var imageBrush = brush as IImageBrush;
  299. var visualBrush = brush as IVisualBrush;
  300. if (solidColorBrush != null)
  301. {
  302. return new SolidColorBrushImpl(solidColorBrush, _renderTarget);
  303. }
  304. else if (linearGradientBrush != null)
  305. {
  306. return new LinearGradientBrushImpl(linearGradientBrush, _renderTarget, destinationSize);
  307. }
  308. else if (radialGradientBrush != null)
  309. {
  310. return new RadialGradientBrushImpl(radialGradientBrush, _renderTarget, destinationSize);
  311. }
  312. else if (imageBrush != null)
  313. {
  314. return new TileBrushImpl(imageBrush, _renderTarget, destinationSize);
  315. }
  316. else if (visualBrush != null)
  317. {
  318. return new TileBrushImpl(visualBrush, _renderTarget, destinationSize);
  319. }
  320. else
  321. {
  322. return new SolidColorBrushImpl(null, _renderTarget);
  323. }
  324. }
  325. public void PushGeometryClip(IGeometryImpl clip)
  326. {
  327. var parameters = new LayerParameters
  328. {
  329. ContentBounds = PrimitiveExtensions.RectangleInfinite,
  330. MaskTransform = PrimitiveExtensions.Matrix3x2Identity,
  331. Opacity = 1,
  332. GeometricMask = ((GeometryImpl)clip).Geometry
  333. };
  334. var layer = _layerPool.Count != 0 ? _layerPool.Pop() : new Layer(_renderTarget);
  335. _renderTarget.PushLayer(ref parameters, layer);
  336. _layers.Push(layer);
  337. }
  338. public void PopGeometryClip()
  339. {
  340. PopLayer();
  341. }
  342. public void PushOpacityMask(IBrush mask, Rect bounds)
  343. {
  344. var parameters = new LayerParameters
  345. {
  346. ContentBounds = PrimitiveExtensions.RectangleInfinite,
  347. MaskTransform = PrimitiveExtensions.Matrix3x2Identity,
  348. Opacity = 1,
  349. OpacityBrush = CreateBrush(mask, bounds.Size).PlatformBrush
  350. };
  351. var layer = _layerPool.Count != 0 ? _layerPool.Pop() : new Layer(_renderTarget);
  352. _renderTarget.PushLayer(ref parameters, layer);
  353. _layers.Push(layer);
  354. }
  355. public void PopOpacityMask()
  356. {
  357. PopLayer();
  358. }
  359. }
  360. }