DrawingContextImpl.cs 51 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Diagnostics;
  4. using System.Linq;
  5. using System.Threading;
  6. using Avalonia.Media;
  7. using Avalonia.Platform;
  8. using Avalonia.Rendering;
  9. using Avalonia.Rendering.SceneGraph;
  10. using Avalonia.Rendering.Utilities;
  11. using Avalonia.Utilities;
  12. using Avalonia.Media.Imaging;
  13. using Avalonia.Skia.Helpers;
  14. using SkiaSharp;
  15. using ISceneBrush = Avalonia.Media.ISceneBrush;
  16. namespace Avalonia.Skia
  17. {
  18. /// <summary>
  19. /// Skia based drawing context.
  20. /// </summary>
  21. internal class DrawingContextImpl : IDrawingContextImpl, IDrawingContextWithAcrylicLikeSupport
  22. {
  23. private IDisposable?[]? _disposables;
  24. private readonly Vector _dpi;
  25. private readonly Stack<PaintWrapper> _maskStack = new();
  26. private readonly Stack<double> _opacityStack = new();
  27. private readonly Stack<BitmapBlendingMode> _blendingModeStack = new();
  28. private readonly Matrix? _postTransform;
  29. private double _currentOpacity = 1.0f;
  30. private BitmapBlendingMode _currentBlendingMode = BitmapBlendingMode.SourceOver;
  31. private readonly bool _canTextUseLcdRendering;
  32. private Matrix _currentTransform;
  33. private bool _disposed;
  34. private GRContext? _grContext;
  35. public GRContext? GrContext => _grContext;
  36. private readonly ISkiaGpu? _gpu;
  37. private readonly SKPaint _strokePaint = SKPaintCache.Shared.Get();
  38. private readonly SKPaint _fillPaint = SKPaintCache.Shared.Get();
  39. private readonly SKPaint _boxShadowPaint = SKPaintCache.Shared.Get();
  40. private static SKShader? s_acrylicNoiseShader;
  41. private readonly ISkiaGpuRenderSession? _session;
  42. private bool _leased;
  43. private bool _useOpacitySaveLayer;
  44. /// <summary>
  45. /// Context create info.
  46. /// </summary>
  47. public struct CreateInfo
  48. {
  49. /// <summary>
  50. /// Canvas to draw to.
  51. /// </summary>
  52. public SKCanvas? Canvas;
  53. /// <summary>
  54. /// Surface to draw to.
  55. /// </summary>
  56. public SKSurface? Surface;
  57. /// <summary>
  58. /// Dpi of drawings.
  59. /// </summary>
  60. public Vector Dpi;
  61. /// <summary>
  62. /// Render text without Lcd rendering.
  63. /// </summary>
  64. public bool DisableTextLcdRendering;
  65. /// <summary>
  66. /// GPU-accelerated context (optional)
  67. /// </summary>
  68. public GRContext? GrContext;
  69. /// <summary>
  70. /// Skia GPU provider context (optional)
  71. /// </summary>
  72. public ISkiaGpu? Gpu;
  73. public ISkiaGpuRenderSession? CurrentSession;
  74. }
  75. private class SkiaLeaseFeature : ISkiaSharpApiLeaseFeature
  76. {
  77. private readonly DrawingContextImpl _context;
  78. public SkiaLeaseFeature(DrawingContextImpl context)
  79. {
  80. _context = context;
  81. }
  82. public ISkiaSharpApiLease Lease()
  83. {
  84. _context.CheckLease();
  85. return new ApiLease(_context);
  86. }
  87. private class ApiLease : ISkiaSharpApiLease
  88. {
  89. private readonly DrawingContextImpl _context;
  90. private readonly SKMatrix _revertTransform;
  91. private bool _isDisposed;
  92. public ApiLease(DrawingContextImpl context)
  93. {
  94. _revertTransform = context.Canvas.TotalMatrix;
  95. _context = context;
  96. _context._leased = true;
  97. }
  98. public SKCanvas SkCanvas => _context.Canvas;
  99. public GRContext? GrContext => _context.GrContext;
  100. public SKSurface? SkSurface => _context.Surface;
  101. public double CurrentOpacity => _context._currentOpacity;
  102. public void Dispose()
  103. {
  104. if (!_isDisposed)
  105. {
  106. _context.Canvas.SetMatrix(_revertTransform);
  107. _context._leased = false;
  108. _isDisposed = true;
  109. }
  110. }
  111. }
  112. }
  113. /// <summary>
  114. /// Create new drawing context.
  115. /// </summary>
  116. /// <param name="createInfo">Create info.</param>
  117. /// <param name="disposables">Array of elements to dispose after drawing has finished.</param>
  118. public DrawingContextImpl(CreateInfo createInfo, params IDisposable?[]? disposables)
  119. {
  120. Canvas = createInfo.Canvas ?? createInfo.Surface?.Canvas
  121. ?? throw new ArgumentException("Invalid create info - no Canvas provided", nameof(createInfo));
  122. _dpi = createInfo.Dpi;
  123. _disposables = disposables;
  124. _canTextUseLcdRendering = !createInfo.DisableTextLcdRendering;
  125. _grContext = createInfo.GrContext;
  126. _gpu = createInfo.Gpu;
  127. if (_grContext != null)
  128. Monitor.Enter(_grContext);
  129. Surface = createInfo.Surface;
  130. _session = createInfo.CurrentSession;
  131. if (!_dpi.NearlyEquals(SkiaPlatform.DefaultDpi))
  132. {
  133. _postTransform =
  134. Matrix.CreateScale(_dpi.X / SkiaPlatform.DefaultDpi.X, _dpi.Y / SkiaPlatform.DefaultDpi.Y);
  135. }
  136. Transform = Matrix.Identity;
  137. var options = AvaloniaLocator.Current.GetService<SkiaOptions>();
  138. if(options != null)
  139. {
  140. _useOpacitySaveLayer = options.UseOpacitySaveLayer;
  141. }
  142. }
  143. /// <summary>
  144. /// Skia canvas.
  145. /// </summary>
  146. public SKCanvas Canvas { get; }
  147. public SKSurface? Surface { get; }
  148. private void CheckLease()
  149. {
  150. if (_leased)
  151. throw new InvalidOperationException("The underlying graphics API is currently leased");
  152. }
  153. /// <inheritdoc />
  154. public void Clear(Color color)
  155. {
  156. CheckLease();
  157. Canvas.Clear(color.ToSKColor());
  158. }
  159. /// <inheritdoc />
  160. public void DrawBitmap(IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode)
  161. {
  162. CheckLease();
  163. var drawableImage = (IDrawableBitmapImpl)source.Item;
  164. var s = sourceRect.ToSKRect();
  165. var d = destRect.ToSKRect();
  166. var paint = SKPaintCache.Shared.Get();
  167. paint.Color = new SKColor(255, 255, 255, (byte)(255 * opacity * (_useOpacitySaveLayer ? 1 : _currentOpacity)));
  168. paint.FilterQuality = bitmapInterpolationMode.ToSKFilterQuality();
  169. paint.BlendMode = _currentBlendingMode.ToSKBlendMode();
  170. drawableImage.Draw(this, s, d, paint);
  171. SKPaintCache.Shared.ReturnReset(paint);
  172. }
  173. /// <inheritdoc />
  174. public void DrawBitmap(IRef<IBitmapImpl> source, IBrush opacityMask, Rect opacityMaskRect, Rect destRect)
  175. {
  176. CheckLease();
  177. PushOpacityMask(opacityMask, opacityMaskRect);
  178. DrawBitmap(source, 1, new Rect(0, 0, source.Item.PixelSize.Width, source.Item.PixelSize.Height), destRect, BitmapInterpolationMode.Default);
  179. PopOpacityMask();
  180. }
  181. /// <inheritdoc />
  182. public void DrawLine(IPen? pen, Point p1, Point p2)
  183. {
  184. CheckLease();
  185. if (pen is not null
  186. && TryCreatePaint(_strokePaint, pen, new Size(Math.Abs(p2.X - p1.X), Math.Abs(p2.Y - p1.Y))) is { } stroke)
  187. {
  188. using (stroke)
  189. {
  190. Canvas.DrawLine((float)p1.X, (float)p1.Y, (float)p2.X, (float)p2.Y, stroke.Paint);
  191. }
  192. }
  193. }
  194. /// <inheritdoc />
  195. public void DrawGeometry(IBrush? brush, IPen? pen, IGeometryImpl geometry)
  196. {
  197. CheckLease();
  198. var impl = (GeometryImpl) geometry;
  199. var size = geometry.Bounds.Size;
  200. if (brush is not null && impl.FillPath != null)
  201. {
  202. using (var fill = CreatePaint(_fillPaint, brush, size))
  203. {
  204. Canvas.DrawPath(impl.FillPath, fill.Paint);
  205. }
  206. }
  207. if (pen is not null
  208. && impl.StrokePath != null
  209. && TryCreatePaint(_strokePaint, pen, size.Inflate(new Thickness(pen.Thickness / 2))) is { } stroke)
  210. {
  211. using (stroke)
  212. {
  213. Canvas.DrawPath(impl.StrokePath, stroke.Paint);
  214. }
  215. }
  216. }
  217. private struct BoxShadowFilter : IDisposable
  218. {
  219. public readonly SKPaint Paint;
  220. private readonly SKImageFilter? _filter;
  221. public readonly SKClipOperation ClipOperation;
  222. private BoxShadowFilter(SKPaint paint, SKImageFilter? filter, SKClipOperation clipOperation)
  223. {
  224. Paint = paint;
  225. _filter = filter;
  226. ClipOperation = clipOperation;
  227. }
  228. private static float SkBlurRadiusToSigma(double radius) {
  229. if (radius <= 0)
  230. return 0.0f;
  231. return 0.288675f * (float)radius + 0.5f;
  232. }
  233. public static BoxShadowFilter Create(SKPaint paint, BoxShadow shadow, double opacity)
  234. {
  235. var ac = shadow.Color;
  236. var filter = SKImageFilter.CreateBlur(SkBlurRadiusToSigma(shadow.Blur), SkBlurRadiusToSigma(shadow.Blur));
  237. var color = new SKColor(ac.R, ac.G, ac.B, (byte)(ac.A * opacity));
  238. paint.Reset();
  239. paint.IsAntialias = true;
  240. paint.Color = color;
  241. paint.ImageFilter = filter;
  242. var clipOperation = shadow.IsInset ? SKClipOperation.Intersect : SKClipOperation.Difference;
  243. return new BoxShadowFilter(paint, filter, clipOperation);
  244. }
  245. public void Dispose()
  246. {
  247. Paint?.Reset();
  248. _filter?.Dispose();
  249. }
  250. }
  251. private static SKRect AreaCastingShadowInHole(
  252. SKRect hole_rect,
  253. float shadow_blur,
  254. float shadow_spread,
  255. float offsetX, float offsetY)
  256. {
  257. // Adapted from Chromium
  258. var bounds = hole_rect;
  259. bounds.Inflate(shadow_blur, shadow_blur);
  260. if (shadow_spread < 0)
  261. bounds.Inflate(-shadow_spread, -shadow_spread);
  262. var offset_bounds = bounds;
  263. offset_bounds.Offset(-offsetX, -offsetY);
  264. bounds.Union(offset_bounds);
  265. return bounds;
  266. }
  267. /// <inheritdoc />
  268. public void DrawRectangle(IExperimentalAcrylicMaterial? material, RoundedRect rect)
  269. {
  270. if (rect.Rect.Height <= 0 || rect.Rect.Width <= 0)
  271. return;
  272. CheckLease();
  273. var rc = rect.Rect.ToSKRect();
  274. SKRoundRect? skRoundRect = null;
  275. if (rect.IsRounded)
  276. {
  277. skRoundRect = SKRoundRectCache.Shared.Get();
  278. skRoundRect.SetRectRadii(rc,
  279. new[]
  280. {
  281. rect.RadiiTopLeft.ToSKPoint(),
  282. rect.RadiiTopRight.ToSKPoint(),
  283. rect.RadiiBottomRight.ToSKPoint(),
  284. rect.RadiiBottomLeft.ToSKPoint(),
  285. });
  286. }
  287. if (material != null)
  288. {
  289. using (var paint = CreateAcrylicPaint(_fillPaint, material))
  290. {
  291. if (skRoundRect is not null)
  292. {
  293. Canvas.DrawRoundRect(skRoundRect, paint.Paint);
  294. SKRoundRectCache.Shared.Return(skRoundRect);
  295. }
  296. else
  297. {
  298. Canvas.DrawRect(rc, paint.Paint);
  299. }
  300. }
  301. }
  302. }
  303. /// <inheritdoc />
  304. public void DrawRectangle(IBrush? brush, IPen? pen, RoundedRect rect, BoxShadows boxShadows = default)
  305. {
  306. if (rect.Rect.Height <= 0 || rect.Rect.Width <= 0)
  307. return;
  308. CheckLease();
  309. // Arbitrary chosen values
  310. // On OSX Skia breaks OpenGL context when asked to draw, e. g. (0, 0, 623, 6666600) rect
  311. if (rect.Rect.Height > 8192 || rect.Rect.Width > 8192)
  312. boxShadows = default;
  313. var rc = rect.Rect.ToSKRect();
  314. var isRounded = rect.IsRounded;
  315. var needRoundRect = rect.IsRounded || (boxShadows.HasInsetShadows);
  316. SKRoundRect? skRoundRect = null;
  317. if (needRoundRect)
  318. {
  319. skRoundRect = SKRoundRectCache.Shared.GetAndSetRadii(rc, rect);
  320. }
  321. foreach (var boxShadow in boxShadows)
  322. {
  323. if (!boxShadow.IsDefault && !boxShadow.IsInset)
  324. {
  325. using (var shadow = BoxShadowFilter.Create(_boxShadowPaint, boxShadow, _useOpacitySaveLayer ? 1 : _currentOpacity))
  326. {
  327. var spread = (float)boxShadow.Spread;
  328. if (boxShadow.IsInset)
  329. spread = -spread;
  330. Canvas.Save();
  331. if (isRounded)
  332. {
  333. var shadowRect = SKRoundRectCache.Shared.GetAndSetRadii(skRoundRect!.Rect, skRoundRect.Radii);
  334. if (spread != 0)
  335. shadowRect.Inflate(spread, spread);
  336. Canvas.ClipRoundRect(skRoundRect,
  337. shadow.ClipOperation, true);
  338. var oldTransform = Transform;
  339. Transform = oldTransform * Matrix.CreateTranslation(boxShadow.OffsetX, boxShadow.OffsetY);
  340. Canvas.DrawRoundRect(shadowRect, shadow.Paint);
  341. Transform = oldTransform;
  342. SKRoundRectCache.Shared.Return(shadowRect);
  343. }
  344. else
  345. {
  346. var shadowRect = rc;
  347. if (spread != 0)
  348. shadowRect.Inflate(spread, spread);
  349. Canvas.ClipRect(rc, shadow.ClipOperation);
  350. var oldTransform = Transform;
  351. Transform = oldTransform * Matrix.CreateTranslation(boxShadow.OffsetX, boxShadow.OffsetY);
  352. Canvas.DrawRect(shadowRect, shadow.Paint);
  353. Transform = oldTransform;
  354. }
  355. Canvas.Restore();
  356. }
  357. }
  358. }
  359. if (brush != null)
  360. {
  361. using (var fill = CreatePaint(_fillPaint, brush, rect.Rect.Size))
  362. {
  363. if (isRounded)
  364. {
  365. Canvas.DrawRoundRect(skRoundRect, fill.Paint);
  366. }
  367. else
  368. {
  369. Canvas.DrawRect(rc, fill.Paint);
  370. }
  371. }
  372. }
  373. foreach (var boxShadow in boxShadows)
  374. {
  375. if (!boxShadow.IsDefault && boxShadow.IsInset)
  376. {
  377. using (var shadow = BoxShadowFilter.Create(_boxShadowPaint, boxShadow, _useOpacitySaveLayer ? 1 : _currentOpacity))
  378. {
  379. var spread = (float)boxShadow.Spread;
  380. var offsetX = (float)boxShadow.OffsetX;
  381. var offsetY = (float)boxShadow.OffsetY;
  382. var outerRect = AreaCastingShadowInHole(rc, (float)boxShadow.Blur, spread, offsetX, offsetY);
  383. Canvas.Save();
  384. var shadowRect = SKRoundRectCache.Shared.GetAndSetRadii(skRoundRect!.Rect, skRoundRect.Radii);
  385. if (spread != 0)
  386. shadowRect.Deflate(spread, spread);
  387. Canvas.ClipRoundRect(skRoundRect,
  388. shadow.ClipOperation, true);
  389. var oldTransform = Transform;
  390. Transform = oldTransform * Matrix.CreateTranslation(boxShadow.OffsetX, boxShadow.OffsetY);
  391. using (var outerRRect = new SKRoundRect(outerRect))
  392. Canvas.DrawRoundRectDifference(outerRRect, shadowRect, shadow.Paint);
  393. Transform = oldTransform;
  394. Canvas.Restore();
  395. SKRoundRectCache.Shared.Return(shadowRect);
  396. }
  397. }
  398. }
  399. if (pen is not null
  400. && TryCreatePaint(_strokePaint, pen, rect.Rect.Size.Inflate(new Thickness(pen.Thickness / 2))) is { } stroke)
  401. {
  402. using (stroke)
  403. {
  404. if (isRounded)
  405. {
  406. Canvas.DrawRoundRect(skRoundRect, stroke.Paint);
  407. }
  408. else
  409. {
  410. Canvas.DrawRect(rc, stroke.Paint);
  411. }
  412. }
  413. }
  414. if (skRoundRect is not null)
  415. SKRoundRectCache.Shared.Return(skRoundRect);
  416. }
  417. /// <inheritdoc />
  418. public void DrawEllipse(IBrush? brush, IPen? pen, Rect rect)
  419. {
  420. if (rect.Height <= 0 || rect.Width <= 0)
  421. return;
  422. CheckLease();
  423. var rc = rect.ToSKRect();
  424. if (brush != null)
  425. {
  426. using (var fill = CreatePaint(_fillPaint, brush, rect.Size))
  427. {
  428. Canvas.DrawOval(rc, fill.Paint);
  429. }
  430. }
  431. if (pen is not null
  432. && TryCreatePaint(_strokePaint, pen, rect.Size.Inflate(new Thickness(pen.Thickness / 2))) is { } stroke)
  433. {
  434. using (stroke)
  435. {
  436. Canvas.DrawOval(rc, stroke.Paint);
  437. }
  438. }
  439. }
  440. /// <inheritdoc />
  441. public void DrawGlyphRun(IBrush? foreground, IRef<IGlyphRunImpl> glyphRun)
  442. {
  443. CheckLease();
  444. if (foreground is null)
  445. {
  446. return;
  447. }
  448. using (var paintWrapper = CreatePaint(_fillPaint, foreground, glyphRun.Item.Size))
  449. {
  450. var glyphRunImpl = (GlyphRunImpl)glyphRun.Item;
  451. Canvas.DrawText(glyphRunImpl.TextBlob, (float)glyphRun.Item.BaselineOrigin.X,
  452. (float)glyphRun.Item.BaselineOrigin.Y, paintWrapper.Paint);
  453. }
  454. }
  455. /// <inheritdoc />
  456. public IDrawingContextLayerImpl CreateLayer(Size size)
  457. {
  458. CheckLease();
  459. return CreateRenderTarget(size, true);
  460. }
  461. /// <inheritdoc />
  462. public void PushClip(Rect clip)
  463. {
  464. CheckLease();
  465. Canvas.Save();
  466. Canvas.ClipRect(clip.ToSKRect());
  467. }
  468. public void PushClip(RoundedRect clip)
  469. {
  470. CheckLease();
  471. Canvas.Save();
  472. // Get the rounded rectangle
  473. var rc = clip.Rect.ToSKRect();
  474. // Get a round rect from the cache.
  475. var roundRect = SKRoundRectCache.Shared.Get();
  476. roundRect.SetRectRadii(rc,
  477. new[]
  478. {
  479. clip.RadiiTopLeft.ToSKPoint(), clip.RadiiTopRight.ToSKPoint(),
  480. clip.RadiiBottomRight.ToSKPoint(), clip.RadiiBottomLeft.ToSKPoint(),
  481. });
  482. Canvas.ClipRoundRect(roundRect, antialias:true);
  483. // Should not need to reset as SetRectRadii overrides the values.
  484. SKRoundRectCache.Shared.Return(roundRect);
  485. }
  486. /// <inheritdoc />
  487. public void PopClip()
  488. {
  489. CheckLease();
  490. Canvas.Restore();
  491. }
  492. /// <inheritdoc />
  493. public void PushOpacity(double opacity, Rect bounds)
  494. {
  495. CheckLease();
  496. if(_useOpacitySaveLayer)
  497. {
  498. var rect = bounds.ToSKRect();
  499. Canvas.SaveLayer(rect, new SKPaint { ColorF = new SKColorF(0, 0, 0, (float)opacity)});
  500. }
  501. else
  502. {
  503. _opacityStack.Push(_currentOpacity);
  504. _currentOpacity *= opacity;
  505. }
  506. }
  507. /// <inheritdoc />
  508. public void PopOpacity()
  509. {
  510. CheckLease();
  511. if(_useOpacitySaveLayer)
  512. {
  513. Canvas.Restore();
  514. }
  515. else
  516. {
  517. _currentOpacity = _opacityStack.Pop();
  518. }
  519. }
  520. /// <inheritdoc />
  521. public virtual void Dispose()
  522. {
  523. if(_disposed)
  524. return;
  525. CheckLease();
  526. try
  527. {
  528. // Return leased paints.
  529. SKPaintCache.Shared.ReturnReset(_strokePaint);
  530. SKPaintCache.Shared.ReturnReset(_fillPaint);
  531. SKPaintCache.Shared.ReturnReset(_boxShadowPaint);
  532. if (_grContext != null)
  533. {
  534. Monitor.Exit(_grContext);
  535. _grContext = null;
  536. }
  537. if (_disposables != null)
  538. {
  539. foreach (var disposable in _disposables)
  540. disposable?.Dispose();
  541. _disposables = null;
  542. }
  543. }
  544. finally
  545. {
  546. _disposed = true;
  547. }
  548. }
  549. /// <inheritdoc />
  550. public void PushGeometryClip(IGeometryImpl clip)
  551. {
  552. CheckLease();
  553. Canvas.Save();
  554. Canvas.ClipPath(((GeometryImpl)clip).FillPath, SKClipOperation.Intersect, true);
  555. }
  556. /// <inheritdoc />
  557. public void PopGeometryClip()
  558. {
  559. CheckLease();
  560. Canvas.Restore();
  561. }
  562. /// <inheritdoc />
  563. public void PushBitmapBlendMode(BitmapBlendingMode blendingMode)
  564. {
  565. CheckLease();
  566. _blendingModeStack.Push(_currentBlendingMode);
  567. _currentBlendingMode = blendingMode;
  568. }
  569. /// <inheritdoc />
  570. public void PopBitmapBlendMode()
  571. {
  572. CheckLease();
  573. _currentBlendingMode = _blendingModeStack.Pop();
  574. }
  575. public void Custom(ICustomDrawOperation custom)
  576. {
  577. CheckLease();
  578. custom.Render(this);
  579. }
  580. /// <inheritdoc />
  581. public void PushOpacityMask(IBrush mask, Rect bounds)
  582. {
  583. CheckLease();
  584. var paint = SKPaintCache.Shared.Get();
  585. Canvas.SaveLayer(bounds.ToSKRect(), paint);
  586. _maskStack.Push(CreatePaint(paint, mask, bounds.Size));
  587. }
  588. /// <inheritdoc />
  589. public void PopOpacityMask()
  590. {
  591. CheckLease();
  592. var paint = SKPaintCache.Shared.Get();
  593. paint.BlendMode = SKBlendMode.DstIn;
  594. Canvas.SaveLayer(paint);
  595. SKPaintCache.Shared.ReturnReset(paint);
  596. PaintWrapper paintWrapper;
  597. using (paintWrapper = _maskStack.Pop())
  598. {
  599. Canvas.DrawPaint(paintWrapper.Paint);
  600. }
  601. // Return the paint wrapper's paint less the reset since the paint is already reset in the Dispose method above.
  602. SKPaintCache.Shared.Return(paintWrapper.Paint);
  603. Canvas.Restore();
  604. Canvas.Restore();
  605. }
  606. /// <inheritdoc />
  607. public Matrix Transform
  608. {
  609. get { return _currentTransform; }
  610. set
  611. {
  612. CheckLease();
  613. if (_currentTransform == value)
  614. return;
  615. _currentTransform = value;
  616. var transform = value;
  617. if (_postTransform.HasValue)
  618. {
  619. transform *= _postTransform.Value;
  620. }
  621. Canvas.SetMatrix(transform.ToSKMatrix());
  622. }
  623. }
  624. public object? GetFeature(Type t)
  625. {
  626. if (t == typeof(ISkiaSharpApiLeaseFeature))
  627. return new SkiaLeaseFeature(this);
  628. return null;
  629. }
  630. /// <summary>
  631. /// Configure paint wrapper for using gradient brush.
  632. /// </summary>
  633. /// <param name="paintWrapper">Paint wrapper.</param>
  634. /// <param name="targetSize">Target size.</param>
  635. /// <param name="gradientBrush">Gradient brush.</param>
  636. private static void ConfigureGradientBrush(ref PaintWrapper paintWrapper, Size targetSize, IGradientBrush gradientBrush)
  637. {
  638. var tileMode = gradientBrush.SpreadMethod.ToSKShaderTileMode();
  639. var stopColors = gradientBrush.GradientStops.Select(s => s.Color.ToSKColor()).ToArray();
  640. var stopOffsets = gradientBrush.GradientStops.Select(s => (float)s.Offset).ToArray();
  641. switch (gradientBrush)
  642. {
  643. case ILinearGradientBrush linearGradient:
  644. {
  645. var start = linearGradient.StartPoint.ToPixels(targetSize).ToSKPoint();
  646. var end = linearGradient.EndPoint.ToPixels(targetSize).ToSKPoint();
  647. // would be nice to cache these shaders possibly?
  648. if (linearGradient.Transform is null)
  649. {
  650. using (var shader =
  651. SKShader.CreateLinearGradient(start, end, stopColors, stopOffsets, tileMode))
  652. {
  653. paintWrapper.Paint.Shader = shader;
  654. }
  655. }
  656. else
  657. {
  658. var transformOrigin = linearGradient.TransformOrigin.ToPixels(targetSize);
  659. var offset = Matrix.CreateTranslation(transformOrigin);
  660. var transform = (-offset) * linearGradient.Transform.Value * (offset);
  661. using (var shader =
  662. SKShader.CreateLinearGradient(start, end, stopColors, stopOffsets, tileMode, transform.ToSKMatrix()))
  663. {
  664. paintWrapper.Paint.Shader = shader;
  665. }
  666. }
  667. break;
  668. }
  669. case IRadialGradientBrush radialGradient:
  670. {
  671. var center = radialGradient.Center.ToPixels(targetSize).ToSKPoint();
  672. var radius = (float)(radialGradient.Radius * targetSize.Width);
  673. var origin = radialGradient.GradientOrigin.ToPixels(targetSize).ToSKPoint();
  674. if (origin.Equals(center))
  675. {
  676. // when the origin is the same as the center the Skia RadialGradient acts the same as D2D
  677. if (radialGradient.Transform is null)
  678. {
  679. using (var shader =
  680. SKShader.CreateRadialGradient(center, radius, stopColors, stopOffsets, tileMode))
  681. {
  682. paintWrapper.Paint.Shader = shader;
  683. }
  684. }
  685. else
  686. {
  687. var transformOrigin = radialGradient.TransformOrigin.ToPixels(targetSize);
  688. var offset = Matrix.CreateTranslation(transformOrigin);
  689. var transform = (-offset) * radialGradient.Transform.Value * (offset);
  690. using (var shader =
  691. SKShader.CreateRadialGradient(center, radius, stopColors, stopOffsets, tileMode, transform.ToSKMatrix()))
  692. {
  693. paintWrapper.Paint.Shader = shader;
  694. }
  695. }
  696. }
  697. else
  698. {
  699. // when the origin is different to the center use a two point ConicalGradient to match the behaviour of D2D
  700. // reverse the order of the stops to match D2D
  701. var reversedColors = new SKColor[stopColors.Length];
  702. Array.Copy(stopColors, reversedColors, stopColors.Length);
  703. Array.Reverse(reversedColors);
  704. // and then reverse the reference point of the stops
  705. var reversedStops = new float[stopOffsets.Length];
  706. for (var i = 0; i < stopOffsets.Length; i++)
  707. {
  708. reversedStops[i] = stopOffsets[i];
  709. if (reversedStops[i] > 0 && reversedStops[i] < 1)
  710. {
  711. reversedStops[i] = Math.Abs(1 - stopOffsets[i]);
  712. }
  713. }
  714. // compose with a background colour of the final stop to match D2D's behaviour of filling with the final color
  715. if (radialGradient.Transform is null)
  716. {
  717. using (var shader = SKShader.CreateCompose(
  718. SKShader.CreateColor(reversedColors[0]),
  719. SKShader.CreateTwoPointConicalGradient(center, radius, origin, 0, reversedColors, reversedStops, tileMode)
  720. ))
  721. {
  722. paintWrapper.Paint.Shader = shader;
  723. }
  724. }
  725. else
  726. {
  727. var transformOrigin = radialGradient.TransformOrigin.ToPixels(targetSize);
  728. var offset = Matrix.CreateTranslation(transformOrigin);
  729. var transform = (-offset) * radialGradient.Transform.Value * (offset);
  730. using (var shader = SKShader.CreateCompose(
  731. SKShader.CreateColor(reversedColors[0]),
  732. SKShader.CreateTwoPointConicalGradient(center, radius, origin, 0, reversedColors, reversedStops, tileMode, transform.ToSKMatrix())
  733. ))
  734. {
  735. paintWrapper.Paint.Shader = shader;
  736. }
  737. }
  738. }
  739. break;
  740. }
  741. case IConicGradientBrush conicGradient:
  742. {
  743. var center = conicGradient.Center.ToPixels(targetSize).ToSKPoint();
  744. // Skia's default is that angle 0 is from the right hand side of the center point
  745. // but we are matching CSS where the vertical point above the center is 0.
  746. var angle = (float)(conicGradient.Angle - 90);
  747. var rotation = SKMatrix.CreateRotationDegrees(angle, center.X, center.Y);
  748. if (conicGradient.Transform is { })
  749. {
  750. var transformOrigin = conicGradient.TransformOrigin.ToPixels(targetSize);
  751. var offset = Matrix.CreateTranslation(transformOrigin);
  752. var transform = (-offset) * conicGradient.Transform.Value * (offset);
  753. rotation = rotation.PreConcat(transform.ToSKMatrix());
  754. }
  755. using (var shader =
  756. SKShader.CreateSweepGradient(center, stopColors, stopOffsets, rotation))
  757. {
  758. paintWrapper.Paint.Shader = shader;
  759. }
  760. break;
  761. }
  762. }
  763. }
  764. /// <summary>
  765. /// Configure paint wrapper for using tile brush.
  766. /// </summary>
  767. /// <param name="paintWrapper">Paint wrapper.</param>
  768. /// <param name="targetSize">Target size.</param>
  769. /// <param name="tileBrush">Tile brush to use.</param>
  770. /// <param name="tileBrushImage">Tile brush image.</param>
  771. private void ConfigureTileBrush(ref PaintWrapper paintWrapper, Size targetSize, ITileBrush tileBrush, IDrawableBitmapImpl tileBrushImage)
  772. {
  773. var calc = new TileBrushCalculator(tileBrush, tileBrushImage.PixelSize.ToSizeWithDpi(_dpi), targetSize);
  774. var intermediate = CreateRenderTarget(calc.IntermediateSize, false);
  775. paintWrapper.AddDisposable(intermediate);
  776. using (var context = intermediate.CreateDrawingContext())
  777. {
  778. var sourceRect = new Rect(tileBrushImage.PixelSize.ToSizeWithDpi(96));
  779. var targetRect = new Rect(tileBrushImage.PixelSize.ToSizeWithDpi(_dpi));
  780. context.Clear(Colors.Transparent);
  781. context.PushClip(calc.IntermediateClip);
  782. context.Transform = calc.IntermediateTransform;
  783. context.DrawBitmap(
  784. RefCountable.CreateUnownedNotClonable(tileBrushImage),
  785. 1,
  786. sourceRect,
  787. targetRect,
  788. tileBrush.BitmapInterpolationMode);
  789. context.PopClip();
  790. }
  791. var tileTransform =
  792. tileBrush.TileMode != TileMode.None
  793. ? SKMatrix.CreateTranslation(-(float)calc.DestinationRect.X, -(float)calc.DestinationRect.Y)
  794. : SKMatrix.CreateIdentity();
  795. SKShaderTileMode tileX =
  796. tileBrush.TileMode == TileMode.None
  797. ? SKShaderTileMode.Clamp
  798. : tileBrush.TileMode == TileMode.FlipX || tileBrush.TileMode == TileMode.FlipXY
  799. ? SKShaderTileMode.Mirror
  800. : SKShaderTileMode.Repeat;
  801. SKShaderTileMode tileY =
  802. tileBrush.TileMode == TileMode.None
  803. ? SKShaderTileMode.Clamp
  804. : tileBrush.TileMode == TileMode.FlipY || tileBrush.TileMode == TileMode.FlipXY
  805. ? SKShaderTileMode.Mirror
  806. : SKShaderTileMode.Repeat;
  807. var image = intermediate.SnapshotImage();
  808. paintWrapper.AddDisposable(image);
  809. var paintTransform = default(SKMatrix);
  810. SKMatrix.Concat(
  811. ref paintTransform,
  812. tileTransform,
  813. SKMatrix.CreateScale((float)(96.0 / _dpi.X), (float)(96.0 / _dpi.Y)));
  814. if (tileBrush.Transform is { })
  815. {
  816. var origin = tileBrush.TransformOrigin.ToPixels(targetSize);
  817. var offset = Matrix.CreateTranslation(origin);
  818. var transform = (-offset) * tileBrush.Transform.Value * (offset);
  819. paintTransform = paintTransform.PreConcat(transform.ToSKMatrix());
  820. }
  821. using (var shader = image.ToShader(tileX, tileY, paintTransform))
  822. {
  823. paintWrapper.Paint.Shader = shader;
  824. }
  825. }
  826. private void ConfigureSceneBrushContent(ref PaintWrapper paintWrapper, ISceneBrushContent content,
  827. Size targetSize)
  828. {
  829. if(content.UseScalableRasterization)
  830. ConfigureSceneBrushContentWithPicture(ref paintWrapper, content, targetSize);
  831. else
  832. ConfigureSceneBrushContentWithSurface(ref paintWrapper, content, targetSize);
  833. }
  834. private void ConfigureSceneBrushContentWithSurface(ref PaintWrapper paintWrapper, ISceneBrushContent content,
  835. Size targetSize)
  836. {
  837. var rect = content.Rect;
  838. var intermediateSize = rect.Size;
  839. if (intermediateSize.Width >= 1 && intermediateSize.Height >= 1)
  840. {
  841. using var intermediate = CreateRenderTarget(intermediateSize, false);
  842. using (var ctx = intermediate.CreateDrawingContext())
  843. {
  844. ctx.Clear(Colors.Transparent);
  845. content.Render(ctx, rect.TopLeft == default ? null : Matrix.CreateTranslation(-rect.X, -rect.Y));
  846. }
  847. ConfigureTileBrush(ref paintWrapper, targetSize, content.Brush, intermediate);
  848. }
  849. }
  850. private void ConfigureSceneBrushContentWithPicture(ref PaintWrapper paintWrapper, ISceneBrushContent content,
  851. Size targetSize)
  852. {
  853. var rect = content.Rect;
  854. var contentSize = rect.Size;
  855. if (contentSize.Width <= 0 || contentSize.Height <= 0)
  856. {
  857. paintWrapper.Paint.Color = SKColor.Empty;
  858. return;
  859. }
  860. var tileBrush = content.Brush;
  861. var transform = rect.TopLeft == default ? Matrix.Identity : Matrix.CreateTranslation(-rect.X, -rect.Y);
  862. var calc = new TileBrushCalculator(tileBrush, contentSize, targetSize);
  863. transform *= calc.IntermediateTransform;
  864. using var pictureTarget = new PictureRenderTarget(_gpu, _grContext, _dpi);
  865. using (var ctx = pictureTarget.CreateDrawingContext(calc.IntermediateSize))
  866. {
  867. ctx.PushClip(calc.IntermediateClip);
  868. content.Render(ctx, transform);
  869. ctx.PopClip();
  870. }
  871. using var picture = pictureTarget.GetPicture();
  872. var paintTransform =
  873. tileBrush.TileMode != TileMode.None
  874. ? SKMatrix.CreateTranslation(-(float)calc.DestinationRect.X, -(float)calc.DestinationRect.Y)
  875. : SKMatrix.CreateIdentity();
  876. SKShaderTileMode tileX =
  877. tileBrush.TileMode == TileMode.None
  878. ? SKShaderTileMode.Clamp
  879. : tileBrush.TileMode == TileMode.FlipX || tileBrush.TileMode == TileMode.FlipXY
  880. ? SKShaderTileMode.Mirror
  881. : SKShaderTileMode.Repeat;
  882. SKShaderTileMode tileY =
  883. tileBrush.TileMode == TileMode.None
  884. ? SKShaderTileMode.Clamp
  885. : tileBrush.TileMode == TileMode.FlipY || tileBrush.TileMode == TileMode.FlipXY
  886. ? SKShaderTileMode.Mirror
  887. : SKShaderTileMode.Repeat;
  888. paintTransform = SKMatrix.Concat(paintTransform,
  889. SKMatrix.CreateScale((float)(96.0 / _dpi.X), (float)(96.0 / _dpi.Y)));
  890. if (tileBrush.Transform is { })
  891. {
  892. var origin = tileBrush.TransformOrigin.ToPixels(targetSize);
  893. var offset = Matrix.CreateTranslation(origin);
  894. var brushTransform = (-offset) * tileBrush.Transform.Value * (offset);
  895. paintTransform = paintTransform.PreConcat(brushTransform.ToSKMatrix());
  896. }
  897. using (var shader = picture.ToShader(tileX, tileY, paintTransform,
  898. new SKRect(0, 0, picture.CullRect.Width, picture.CullRect.Height)))
  899. {
  900. paintWrapper.Paint.FilterQuality = SKFilterQuality.None;
  901. paintWrapper.Paint.Shader = shader;
  902. }
  903. }
  904. private static SKColorFilter CreateAlphaColorFilter(double opacity)
  905. {
  906. if (opacity > 1)
  907. opacity = 1;
  908. var c = new byte[256];
  909. var a = new byte[256];
  910. for (var i = 0; i < 256; i++)
  911. {
  912. c[i] = (byte)i;
  913. a[i] = (byte)(i * opacity);
  914. }
  915. return SKColorFilter.CreateTable(a, c, c, c);
  916. }
  917. private static byte Blend(byte leftColor, byte leftAlpha, byte rightColor, byte rightAlpha)
  918. {
  919. var ca = leftColor / 255d;
  920. var aa = leftAlpha / 255d;
  921. var cb = rightColor / 255d;
  922. var ab = rightAlpha / 255d;
  923. var r = (ca * aa + cb * ab * (1 - aa)) / (aa + ab * (1 - aa));
  924. return (byte)(r * 255);
  925. }
  926. private static Color Blend(Color left, Color right)
  927. {
  928. var aa = left.A / 255d;
  929. var ab = right.A / 255d;
  930. return new Color(
  931. (byte)((aa + ab * (1 - aa)) * 255),
  932. Blend(left.R, left.A, right.R, right.A),
  933. Blend(left.G, left.A, right.G, right.A),
  934. Blend(left.B, left.A, right.B, right.A)
  935. );
  936. }
  937. internal PaintWrapper CreateAcrylicPaint (SKPaint paint, IExperimentalAcrylicMaterial material)
  938. {
  939. var paintWrapper = new PaintWrapper(paint);
  940. paint.IsAntialias = true;
  941. var tintOpacity =
  942. material.BackgroundSource == AcrylicBackgroundSource.Digger ?
  943. material.TintOpacity : 1;
  944. const double noiseOpcity = 0.0225;
  945. var tintColor = material.TintColor;
  946. var tint = new SKColor(tintColor.R, tintColor.G, tintColor.B, tintColor.A);
  947. if (s_acrylicNoiseShader == null)
  948. {
  949. using (var stream = typeof(DrawingContextImpl).Assembly.GetManifestResourceStream("Avalonia.Skia.Assets.NoiseAsset_256X256_PNG.png"))
  950. using (var bitmap = SKBitmap.Decode(stream))
  951. {
  952. s_acrylicNoiseShader = SKShader.CreateBitmap(bitmap, SKShaderTileMode.Repeat, SKShaderTileMode.Repeat)
  953. .WithColorFilter(CreateAlphaColorFilter(noiseOpcity));
  954. }
  955. }
  956. using (var backdrop = SKShader.CreateColor(new SKColor(material.MaterialColor.R, material.MaterialColor.G, material.MaterialColor.B, material.MaterialColor.A)))
  957. using (var tintShader = SKShader.CreateColor(tint))
  958. using (var effectiveTint = SKShader.CreateCompose(backdrop, tintShader))
  959. using (var compose = SKShader.CreateCompose(effectiveTint, s_acrylicNoiseShader))
  960. {
  961. paint.Shader = compose;
  962. if (material.BackgroundSource == AcrylicBackgroundSource.Digger)
  963. {
  964. paint.BlendMode = SKBlendMode.Src;
  965. }
  966. return paintWrapper;
  967. }
  968. }
  969. /// <summary>
  970. /// Creates paint wrapper for given brush.
  971. /// </summary>
  972. /// <param name="paint">The paint to wrap.</param>
  973. /// <param name="brush">Source brush.</param>
  974. /// <param name="targetSize">Target size.</param>
  975. /// <returns>Paint wrapper for given brush.</returns>
  976. internal PaintWrapper CreatePaint(SKPaint paint, IBrush brush, Size targetSize)
  977. {
  978. var paintWrapper = new PaintWrapper(paint);
  979. paint.IsAntialias = true;
  980. double opacity = brush.Opacity * (_useOpacitySaveLayer ? 1 :_currentOpacity);
  981. if (brush is ISolidColorBrush solid)
  982. {
  983. paint.Color = new SKColor(solid.Color.R, solid.Color.G, solid.Color.B, (byte) (solid.Color.A * opacity));
  984. return paintWrapper;
  985. }
  986. paint.Color = new SKColor(255, 255, 255, (byte) (255 * opacity));
  987. if (brush is IGradientBrush gradient)
  988. {
  989. ConfigureGradientBrush(ref paintWrapper, targetSize, gradient);
  990. return paintWrapper;
  991. }
  992. var tileBrush = brush as ITileBrush;
  993. var tileBrushImage = default(IDrawableBitmapImpl);
  994. if (brush is ISceneBrush sceneBrush)
  995. {
  996. using (var content = sceneBrush.CreateContent())
  997. {
  998. if (content != null)
  999. {
  1000. ConfigureSceneBrushContent(ref paintWrapper, content, targetSize);
  1001. return paintWrapper;
  1002. }
  1003. else
  1004. paint.Color = default;
  1005. }
  1006. }
  1007. else if (brush is ISceneBrushContent sceneBrushContent)
  1008. {
  1009. ConfigureSceneBrushContent(ref paintWrapper, sceneBrushContent, targetSize);
  1010. return paintWrapper;
  1011. }
  1012. else
  1013. {
  1014. tileBrushImage = (tileBrush as IImageBrush)?.Source?.PlatformImpl.Item as IDrawableBitmapImpl;
  1015. }
  1016. if (tileBrush != null && tileBrushImage != null)
  1017. {
  1018. ConfigureTileBrush(ref paintWrapper, targetSize, tileBrush, tileBrushImage);
  1019. }
  1020. else
  1021. {
  1022. paint.Color = new SKColor(255, 255, 255, 0);
  1023. }
  1024. return paintWrapper;
  1025. }
  1026. /// <summary>
  1027. /// Creates paint wrapper for given pen.
  1028. /// </summary>
  1029. /// <param name="paint">The paint to wrap.</param>
  1030. /// <param name="pen">Source pen.</param>
  1031. /// <param name="targetSize">Target size.</param>
  1032. /// <returns></returns>
  1033. private PaintWrapper? TryCreatePaint(SKPaint paint, IPen pen, Size targetSize)
  1034. {
  1035. // In Skia 0 thickness means - use hairline rendering
  1036. // and for us it means - there is nothing rendered.
  1037. if (pen.Brush is not { } brush || pen.Thickness == 0d)
  1038. {
  1039. return null;
  1040. }
  1041. var rv = CreatePaint(paint, brush, targetSize);
  1042. paint.IsStroke = true;
  1043. paint.StrokeWidth = (float) pen.Thickness;
  1044. // Need to modify dashes due to Skia modifying their lengths
  1045. // https://docs.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/graphics/skiasharp/paths/dots
  1046. // TODO: Still something is off, dashes are now present, but don't look the same as D2D ones.
  1047. switch (pen.LineCap)
  1048. {
  1049. case PenLineCap.Round:
  1050. paint.StrokeCap = SKStrokeCap.Round;
  1051. break;
  1052. case PenLineCap.Square:
  1053. paint.StrokeCap = SKStrokeCap.Square;
  1054. break;
  1055. default:
  1056. paint.StrokeCap = SKStrokeCap.Butt;
  1057. break;
  1058. }
  1059. switch (pen.LineJoin)
  1060. {
  1061. case PenLineJoin.Miter:
  1062. paint.StrokeJoin = SKStrokeJoin.Miter;
  1063. break;
  1064. case PenLineJoin.Round:
  1065. paint.StrokeJoin = SKStrokeJoin.Round;
  1066. break;
  1067. default:
  1068. paint.StrokeJoin = SKStrokeJoin.Bevel;
  1069. break;
  1070. }
  1071. paint.StrokeMiter = (float) pen.MiterLimit;
  1072. if (pen.DashStyle?.Dashes != null && pen.DashStyle.Dashes.Count > 0)
  1073. {
  1074. var srcDashes = pen.DashStyle.Dashes;
  1075. var count = srcDashes.Count % 2 == 0 ? srcDashes.Count : srcDashes.Count * 2;
  1076. var dashesArray = new float[count];
  1077. for (var i = 0; i < count; ++i)
  1078. {
  1079. dashesArray[i] = (float) srcDashes[i % srcDashes.Count] * paint.StrokeWidth;
  1080. }
  1081. var offset = (float)(pen.DashStyle.Offset * pen.Thickness);
  1082. var pe = SKPathEffect.CreateDash(dashesArray, offset);
  1083. paint.PathEffect = pe;
  1084. rv.AddDisposable(pe);
  1085. }
  1086. return rv;
  1087. }
  1088. /// <summary>
  1089. /// Create new render target compatible with this drawing context.
  1090. /// </summary>
  1091. /// <param name="size">The size of the render target in DIPs.</param>
  1092. /// <param name="isLayer">Whether the render target is being created for a layer.</param>
  1093. /// <param name="format">Pixel format.</param>
  1094. /// <returns></returns>
  1095. private SurfaceRenderTarget CreateRenderTarget(Size size, bool isLayer, PixelFormat? format = null)
  1096. {
  1097. var pixelSize = PixelSize.FromSizeWithDpi(size, _dpi);
  1098. var createInfo = new SurfaceRenderTarget.CreateInfo
  1099. {
  1100. Width = pixelSize.Width,
  1101. Height = pixelSize.Height,
  1102. Dpi = _dpi,
  1103. Format = format,
  1104. DisableTextLcdRendering = !_canTextUseLcdRendering,
  1105. GrContext = _grContext,
  1106. Gpu = _gpu,
  1107. Session = _session,
  1108. DisableManualFbo = !isLayer,
  1109. };
  1110. return new SurfaceRenderTarget(createInfo);
  1111. }
  1112. /// <summary>
  1113. /// Skia cached paint state.
  1114. /// </summary>
  1115. private readonly struct PaintState : IDisposable
  1116. {
  1117. private readonly SKColor _color;
  1118. private readonly SKShader _shader;
  1119. private readonly SKPaint _paint;
  1120. public PaintState(SKPaint paint, SKColor color, SKShader shader)
  1121. {
  1122. _paint = paint;
  1123. _color = color;
  1124. _shader = shader;
  1125. }
  1126. /// <inheritdoc />
  1127. public void Dispose()
  1128. {
  1129. _paint.Color = _color;
  1130. _paint.Shader = _shader;
  1131. }
  1132. }
  1133. /// <summary>
  1134. /// Skia paint wrapper.
  1135. /// </summary>
  1136. internal struct PaintWrapper : IDisposable
  1137. {
  1138. //We are saving memory allocations there
  1139. public readonly SKPaint Paint;
  1140. private IDisposable? _disposable1;
  1141. private IDisposable? _disposable2;
  1142. private IDisposable? _disposable3;
  1143. public PaintWrapper(SKPaint paint)
  1144. {
  1145. Paint = paint;
  1146. _disposable1 = null;
  1147. _disposable2 = null;
  1148. _disposable3 = null;
  1149. }
  1150. public IDisposable ApplyTo(SKPaint paint)
  1151. {
  1152. var state = new PaintState(paint, paint.Color, paint.Shader);
  1153. paint.Color = Paint.Color;
  1154. paint.Shader = Paint.Shader;
  1155. return state;
  1156. }
  1157. /// <summary>
  1158. /// Add new disposable to a wrapper.
  1159. /// </summary>
  1160. /// <param name="disposable">Disposable to add.</param>
  1161. public void AddDisposable(IDisposable disposable)
  1162. {
  1163. if (_disposable1 == null)
  1164. {
  1165. _disposable1 = disposable;
  1166. }
  1167. else if (_disposable2 == null)
  1168. {
  1169. _disposable2 = disposable;
  1170. }
  1171. else if (_disposable3 == null)
  1172. {
  1173. _disposable3 = disposable;
  1174. }
  1175. else
  1176. {
  1177. Debug.Assert(false);
  1178. // ReSharper disable once HeuristicUnreachableCode
  1179. throw new InvalidOperationException(
  1180. "PaintWrapper disposable object limit reached. You need to add extra struct fields to support more disposables.");
  1181. }
  1182. }
  1183. /// <inheritdoc />
  1184. public void Dispose()
  1185. {
  1186. Paint?.Reset();
  1187. _disposable1?.Dispose();
  1188. _disposable2?.Dispose();
  1189. _disposable3?.Dispose();
  1190. }
  1191. }
  1192. }
  1193. }