VectSharp  2.2.1
A light library for C# vector graphics
SKMultiLayerRenderCanvas.cs
1 /*
2  VectSharp - A light library for C# vector graphics.
3  Copyright (C) 2020-2022 Giorgio Bianchini
4 
5  This program is free software: you can redistribute it and/or modify
6  it under the terms of the GNU Lesser General Public License as published by
7  the Free Software Foundation, version 3.
8 
9  This program is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  GNU Lesser General Public License for more details.
13 
14  You should have received a copy of the GNU Lesser General Public License
15  along with this program. If not, see <https://www.gnu.org/licenses/>.
16 */
17 
18 using Avalonia;
19 using Avalonia.Media;
20 using Avalonia.Media.Imaging;
21 using Avalonia.Platform;
22 using Avalonia.Rendering.SceneGraph;
23 using Avalonia.Skia;
24 using SkiaSharp;
25 using System;
26 using System.Collections.Generic;
27 using System.Linq;
28 using System.Linq.Expressions;
29 using System.Reflection;
30 using System.Threading;
31 
32 namespace VectSharp.Canvas
33 {
34  internal interface ISKRenderCanvas
35  {
36  void InvalidateDirty();
37  void InvalidateZIndex();
38  }
39 
40  /// <summary>
41  /// Represents a multi-threaded, triple-buffered canvas on which the image is drawn using SkiaSharp.
42  /// </summary>
43  public class SKMultiLayerRenderCanvas : Avalonia.Controls.Canvas, IDisposable, ISKRenderCanvas
44  {
45  /// <summary>
46  /// The width of the page that is rendered on this canvas.
47  /// </summary>
48  public double PageWidth { get; set; }
49 
50  /// <summary>
51  /// The height of the page that is rendered on this canvas.
52  /// </summary>
53  public double PageHeight { get; set; }
54  private SKColor BackgroundColour;
55 
56  /// <summary>
57  /// The list of render actions, each element in this list is itself a list, containing the actions that correspond to a layer in the image.
58  /// </summary>
59  public List<List<SKRenderAction>> RenderActions;
60 
61  /// <summary>
62  /// The list of transforms associated with each layer.
63  /// </summary>
64  public List<SKRenderAction> LayerTransforms;
65 
66  /// <summary>
67  /// If the image to draw is not already cached, this method is called with an argument containing the number of milliseconds since the image was last rendered.
68  /// The method can return a Bitmap that will be drawn on the canvas in order to let users know that the image is being rendered in background.
69  /// </summary>
70  public Func<long, Bitmap> Spinner { get; set; } = null;
71 
72  private readonly System.Diagnostics.Stopwatch LastFrameStopwatch = new System.Diagnostics.Stopwatch();
73  static readonly Func<IDrawingContextImpl, IVisualNode> GetNode;
74 
76  {
77  Type DeferredDrawingContextImpl = Assembly.GetAssembly(typeof(Avalonia.Rendering.SceneGraph.Scene)).GetType("Avalonia.Rendering.SceneGraph.DeferredDrawingContextImpl");
78 
79  FieldInfo NodeFieldInfo = DeferredDrawingContextImpl.GetField("_node", BindingFlags.NonPublic | BindingFlags.Instance);
80 
81  ParameterExpression ownerParameter = Expression.Parameter(typeof(IDrawingContextImpl));
82 
83  MemberExpression exp = Expression.Field(Expression.Convert(ownerParameter, DeferredDrawingContextImpl), NodeFieldInfo);
84  GetNode = Expression.Lambda<Func<IDrawingContextImpl, IVisualNode>>(exp, ownerParameter).Compile();
85  }
86 
87  private Dictionary<string, (SKBitmap, bool)> Images;
88  private List<List<SKRenderAction>> TaggedRenderActions;
89 
90  /// <summary>
91  /// Create a new SKMultiLayerRenderCanvas from a <see cref="Document"/>, where each page represents a layer.
92  /// </summary>
93  /// <param name="document">The document containing the layers as <see cref="Page"/>s.</param>
94  /// <param name="layerTransforms">A list of transforms associated with each layer. This list should contain the same number of elements as the number of pages in <paramref name="document"/>. This is useful to manipulate the position of each layer individually. If this is null, an identity transform is applied to each layer.</param>
95  /// <param name="backgroundColour">The background colour of the canvas.</param>
96  /// <param name="width">The width of the canvas and the pages it contains.</param>
97  /// <param name="height">The height of the canvas and the pages it contains.</param>
98  public SKMultiLayerRenderCanvas(Document document, Colour backgroundColour, double width, double height, List<SKRenderAction> layerTransforms = null) : this(document.Pages, backgroundColour, width, height, layerTransforms) { }
99 
100  /// <summary>
101  /// Create a new SKMultiLayerRenderCanvas from a collection of <see cref="Page"/>s, each representing a layer.
102  /// </summary>
103  /// <param name="layers">The contents of the canvas. Each element in this list represents a layer.</param>
104  /// <param name="layerTransforms">A list of transforms associated with each layer. This list should contain the same number of elements as <paramref name="layers"/>. This is useful to manipulate the position of each layer individually. If this is null, an identity transform is applied to each layer.</param>
105  /// <param name="backgroundColour">The background colour of the canvas.</param>
106  /// <param name="width">The width of the canvas and the pages it contains.</param>
107  /// <param name="height">The height of the canvas and the pages it contains.</param>
108  public SKMultiLayerRenderCanvas(IEnumerable<Page> layers, Colour backgroundColour, double width, double height, List<SKRenderAction> layerTransforms = null)
109  {
110  List<SKRenderContext> contents = (from el in layers select el.CopyToSKRenderContext()).ToList();
111  List<SKRenderAction> contentTransforms;
112 
113  if (layerTransforms== null)
114  {
115  contentTransforms = (from el in Enumerable.Range(0, contents.Count) select SKRenderAction.TransformAction(SKMatrix.Identity)).ToList();
116  }
117  else
118  {
119  contentTransforms = layerTransforms;
120  }
121 
122  UpdateWith(contents, contentTransforms, backgroundColour, width, height);
123 
124  this.Width = width;
125  this.Height = height;
126 
127  this.PointerPressed += this.PointerPressedAction;
128  this.PointerReleased += this.PointerReleasedAction;
129  this.PointerMoved += this.PointerMoveAction;
130  this.PointerLeave += this.PointerLeaveAction;
131 
132  LastFrameStopwatch.Start();
133  }
134 
135  /// <summary>
136  /// Create a new SKMultiLayerRenderCanvas from a list of SKRenderContexts, each representing a layer.
137  /// </summary>
138  /// <param name="contents">The contents of the canvas. Each element in this list represents a layer. A Page can be converded to a SKRenderContext through the CopyToSKRenderContext method.</param>
139  /// <param name="contentTransforms">A list of transforms associated with each layer. This list should contain the same number of elements as <paramref name="contents"/>. This is useful to manipulate the position of each layer individually.</param>
140  /// <param name="backgroundColour">The background colour of the canvas.</param>
141  /// <param name="width">The width of the canvas and the page it contains.</param>
142  /// <param name="height">The height of the canvas and the page it contains.</param>
143  public SKMultiLayerRenderCanvas(List<SKRenderContext> contents, List<SKRenderAction> contentTransforms, Colour backgroundColour, double width, double height)
144  {
145  UpdateWith(contents, contentTransforms, backgroundColour, width, height);
146 
147  this.Width = width;
148  this.Height = height;
149 
150  this.PointerPressed += this.PointerPressedAction;
151  this.PointerReleased += this.PointerReleasedAction;
152  this.PointerMoved += this.PointerMoveAction;
153  this.PointerLeave += this.PointerLeaveAction;
154 
155  LastFrameStopwatch.Start();
156  }
157 
158  /// <summary>
159  /// Replace the contents of the SKMultiLayerRenderCanvas with the specified layers.
160  /// </summary>
161  /// <param name="contents">The contents of the canvas. Each element in this list represents a layer. A Page can be converded to a SKRenderContext through the CopyToSKRenderContext method.</param>
162  /// <param name="contentTransforms">A list of transforms associated with each layer. This list should contain the same number of elements as <paramref name="contents"/>. This is useful to manipulate the position of each layer individually.</param>
163  /// <param name="backgroundColour">The background colour of the canvas.</param>
164  /// <param name="width">The width of the canvas and the page it contains.</param>
165  /// <param name="height">The height of the canvas and the page it contains.</param>
166  public void UpdateWith(List<SKRenderContext> contents, List<SKRenderAction> contentTransforms, Colour backgroundColour, double width, double height)
167  {
168  lock (RenderLock)
169  {
170  if (this.RenderActions == null)
171  {
172  this.RenderActions = new List<List<SKRenderAction>>();
173  }
174  else
175  {
176  this.RenderActions.Clear();
177  }
178 
179  if (this.LayerTransforms == null)
180  {
181  this.LayerTransforms = new List<SKRenderAction>();
182  }
183  else
184  {
185  this.LayerTransforms.Clear();
186  }
187 
188  this.PageWidth = width;
189  this.PageHeight = height;
190  this.BackgroundColour = backgroundColour.ToSKColor();
191 
192  if (this.Images == null)
193  {
194  Images = new Dictionary<string, (SKBitmap, bool)>();
195  }
196  else
197  {
198  Images.Clear();
199  }
200 
201  for (int i = 0; i < contents.Count; i++)
202  {
203  this.LayerTransforms.Add(contentTransforms[i]);
204  this.RenderActions.Add(contents[i].SKRenderActions);
205 
206  foreach (KeyValuePair<string, (SKBitmap, bool)> kvp in contents[i].Images)
207  {
208  this.Images[kvp.Key] = kvp.Value;
209  }
210  }
211 
212  if (this.TaggedRenderActions == null)
213  {
214  this.TaggedRenderActions = new List<List<SKRenderAction>>();
215  }
216  else
217  {
218  this.TaggedRenderActions.Clear();
219  }
220 
221 
222  for (int i = this.RenderActions.Count - 1; i >= 0; i--)
223  {
224  TaggedRenderActions.Add(new List<SKRenderAction>());
225 
226  for (int j = this.RenderActions[i].Count - 1; j >= 0; j--)
227  {
228  RenderActions[i][j].InternalParent = this;
229  if (!string.IsNullOrEmpty(this.RenderActions[i][j].Tag))
230  {
231  TaggedRenderActions[this.RenderActions.Count - 1 - i].Add(this.RenderActions[i][j]);
232  }
233  }
234  }
235  }
236  }
237 
238  /// <summary>
239  /// Replace a single layer with the specified content.
240  /// </summary>
241  /// <param name="layer">The index of the layer to replace.</param>
242  /// <param name="newContent">The new contents of the layer. A Page can be converded to a SKRenderContext through the CopyToSKRenderContext method.</param>
243  /// <param name="newTransform">The new transform for the layer.</param>
244  public void UpdateLayer(int layer, SKRenderContext newContent, SKRenderAction newTransform)
245  {
246  lock (RenderLock)
247  {
248  this.LayerTransforms[layer] = newTransform;
249  this.RenderActions[layer] = newContent.SKRenderActions;
250 
251  foreach (KeyValuePair<string, (SKBitmap, bool)> kvp in newContent.Images)
252  {
253  this.Images[kvp.Key] = kvp.Value;
254  }
255 
256  TaggedRenderActions[this.RenderActions.Count - 1 - layer].Clear();
257 
258  for (int j = this.RenderActions[layer].Count - 1; j >= 0; j--)
259  {
260  RenderActions[layer][j].InternalParent = this;
261  if (!string.IsNullOrEmpty(this.RenderActions[layer][j].Tag))
262  {
263  TaggedRenderActions[this.RenderActions.Count - 1 - layer].Add(this.RenderActions[layer][j]);
264  }
265  }
266  }
267 
268  this.InvalidateDirty();
269  }
270 
271  /// <summary>
272  /// Add a new layer to the image.
273  /// </summary>
274  /// <param name="newContent">The contents of the new layer. A Page can be converded to a SKRenderContext through the CopyToSKRenderContext method.</param>
275  /// <param name="newTransform">The transform for the new layer.</param>
276  public void AddLayer(SKRenderContext newContent, SKRenderAction newTransform)
277  {
278  lock (RenderLock)
279  {
280  this.LayerTransforms.Add(newTransform);
281  this.RenderActions.Add(newContent.SKRenderActions);
282 
283  foreach (KeyValuePair<string, (SKBitmap, bool)> kvp in newContent.Images)
284  {
285  this.Images[kvp.Key] = kvp.Value;
286  }
287 
288  TaggedRenderActions.Insert(0, new List<SKRenderAction>());
289 
290  for (int j = this.RenderActions[this.RenderActions.Count - 1].Count - 1; j >= 0; j--)
291  {
292  RenderActions[this.RenderActions.Count - 1][j].InternalParent = this;
293  if (!string.IsNullOrEmpty(this.RenderActions[this.RenderActions.Count - 1][j].Tag))
294  {
295  TaggedRenderActions[0].Add(this.RenderActions[this.RenderActions.Count - 1][j]);
296  }
297  }
298  }
299 
300  this.InvalidateDirty();
301  }
302 
303  /// <summary>
304  /// Insert a new layer at the specified index.
305  /// </summary>
306  /// <param name="index">The position at which the new layer will be inserted.</param>
307  /// <param name="newContent">The contents of the new layer.</param>
308  /// <param name="newTransform">The transform for the new layer.</param>
309  public void InsertLayer(int index, SKRenderContext newContent, SKRenderAction newTransform)
310  {
311  lock (RenderLock)
312  {
313  this.LayerTransforms.Insert(index, newTransform);
314  this.RenderActions.Insert(index, newContent.SKRenderActions);
315 
316  foreach (KeyValuePair<string, (SKBitmap, bool)> kvp in newContent.Images)
317  {
318  this.Images[kvp.Key] = kvp.Value;
319  }
320 
321  TaggedRenderActions.Insert(this.RenderActions.Count - 1 - index, new List<SKRenderAction>());
322 
323  for (int j = this.RenderActions[index].Count - 1; j >= 0; j--)
324  {
325  RenderActions[index][j].InternalParent = this;
326  if (!string.IsNullOrEmpty(this.RenderActions[index][j].Tag))
327  {
328  TaggedRenderActions[this.RenderActions.Count - 1 - index].Add(this.RenderActions[index][j]);
329  }
330  }
331  }
332 
333  this.InvalidateDirty();
334  }
335 
336  /// <summary>
337  /// Remove the specified layer from the image.
338  /// </summary>
339  /// <param name="layer">The index of the layer to remove.</param>
340  public void RemoveLayer(int layer)
341  {
342  lock (RenderLock)
343  {
344  this.LayerTransforms[layer].Dispose();
345  this.LayerTransforms.RemoveAt(layer);
346 
347  for (int i = 0; i < this.RenderActions[layer].Count; i++)
348  {
349  this.RenderActions[layer][i].Dispose();
350  }
351  this.RenderActions.RemoveAt(layer);
352 
353  TaggedRenderActions.RemoveAt(this.TaggedRenderActions.Count - 1 - layer);
354  }
355 
356  this.InvalidateDirty();
357  }
358 
359  /// <summary>
360  /// Switch the position of the two specified layers.
361  /// </summary>
362  /// <param name="layer1">The index of the first layer to switch.</param>
363  /// <param name="layer2">The index of the second layer to switch.</param>
364  public void SwitchLayers(int layer1, int layer2)
365  {
366  lock (RenderLock)
367  {
368  var temp = this.LayerTransforms[layer1];
369  this.LayerTransforms[layer1] = this.LayerTransforms[layer2];
370  this.LayerTransforms[layer2] = temp;
371 
372  var temp2 = this.RenderActions[layer1];
373  this.RenderActions[layer1] = this.RenderActions[layer2];
374  this.RenderActions[layer2] = temp2;
375 
376  var temp3 = this.TaggedRenderActions[this.RenderActions.Count - 1 - layer1];
377  this.TaggedRenderActions[this.RenderActions.Count - 1 - layer1] = this.TaggedRenderActions[this.RenderActions.Count - 1 - layer2];
378  this.TaggedRenderActions[this.RenderActions.Count - 1 - layer2] = temp3;
379  }
380 
381  this.InvalidateDirty();
382  }
383 
384  /// <summary>
385  /// Move the specified layer to the specified position, shifting all other layers as necessary.
386  /// </summary>
387  /// <param name="oldIndex">The current index of the layer to move.</param>
388  /// <param name="newIndex">The final index of the layer. Layers after this will be shifted by 1 in order to accommodate the moved layer.</param>
389  public void MoveLayer(int oldIndex, int newIndex)
390  {
391  lock (RenderLock)
392  {
393  var temp = this.LayerTransforms[oldIndex];
394  this.LayerTransforms.RemoveAt(oldIndex);
395  this.LayerTransforms.Insert(newIndex, temp);
396 
397  var temp2 = this.RenderActions[oldIndex];
398  this.RenderActions.RemoveAt(oldIndex);
399  this.RenderActions.Insert(newIndex, temp2);
400 
401  var temp3 = this.TaggedRenderActions[this.RenderActions.Count - 1 - oldIndex];
402  this.TaggedRenderActions.RemoveAt(this.RenderActions.Count - 1 - oldIndex);
403  this.TaggedRenderActions.Insert(this.RenderActions.Count - 1 - newIndex, temp3);
404  }
405 
406  this.InvalidateDirty();
407  }
408 
409 
410  private SKRenderAction CurrentPressedAction = null;
411  private void PointerPressedAction(object sender, Avalonia.Input.PointerPressedEventArgs e)
412  {
413  SKPoint position = e.GetPosition(this).ToSKPoint();
414 
415  for (int i = 0; i < TaggedRenderActions.Count; i++)
416  {
417  bool found = false;
418 
419  for (int j = 0; j < TaggedRenderActions[i].Count; j++)
420  {
421  if (TaggedRenderActions[i][j].LastRenderedGlobalHitTestPath?.Contains(position.X, position.Y) == true)
422  {
423 
424  if (TaggedRenderActions[i][j].ActionType == SKRenderAction.ActionTypes.Path)
425  {
426  CurrentPressedAction = TaggedRenderActions[i][j];
427  TaggedRenderActions[i][j].FirePointerPressed(e);
428  found = true;
429  break;
430  }
431  else if (TaggedRenderActions[i][j].ActionType == SKRenderAction.ActionTypes.Text)
432  {
433  CurrentPressedAction = TaggedRenderActions[i][j];
434  TaggedRenderActions[i][j].FirePointerPressed(e);
435  found = true;
436  break;
437  }
438  else if (TaggedRenderActions[i][j].ActionType == SKRenderAction.ActionTypes.RasterImage)
439  {
440  CurrentPressedAction = TaggedRenderActions[i][j];
441  TaggedRenderActions[i][j].FirePointerPressed(e);
442  found = true;
443  break;
444  }
445  }
446  }
447 
448  if (found)
449  {
450  break;
451  }
452  }
453  }
454 
455  private void PointerReleasedAction(object sender, Avalonia.Input.PointerReleasedEventArgs e)
456  {
457  if (CurrentPressedAction != null)
458  {
459  if (!CurrentPressedAction.Disposed)
460  {
461  CurrentPressedAction.FirePointerReleased(e);
462  }
463  CurrentPressedAction = null;
464  }
465  else
466  {
467  SKPoint position = e.GetPosition(this).ToSKPoint();
468 
469  for (int i = 0; i < TaggedRenderActions.Count; i++)
470  {
471  bool found = false;
472 
473  for (int j = 0; j < TaggedRenderActions[i].Count; j++)
474  {
475  if (TaggedRenderActions[i][j].LastRenderedGlobalHitTestPath?.Contains(position.X, position.Y) == true)
476  {
477  if (TaggedRenderActions[i][j].ActionType == SKRenderAction.ActionTypes.Path)
478  {
479  TaggedRenderActions[i][j].FirePointerReleased(e);
480  found = true;
481  break;
482  }
483  else if (TaggedRenderActions[i][j].ActionType == SKRenderAction.ActionTypes.Text)
484  {
485  TaggedRenderActions[i][j].FirePointerReleased(e);
486  found = true;
487  break;
488  }
489  else if (TaggedRenderActions[i][j].ActionType == SKRenderAction.ActionTypes.RasterImage)
490  {
491  TaggedRenderActions[i][j].FirePointerReleased(e);
492  found = true;
493  break;
494  }
495  }
496  }
497 
498  if (found)
499  {
500  break;
501  }
502  }
503  }
504  }
505 
506  private SKRenderAction CurrentOverAction = null;
507  private void PointerMoveAction(object sender, Avalonia.Input.PointerEventArgs e)
508  {
509  SKPoint position = e.GetPosition(this).ToSKPoint();
510 
511  bool found = false;
512 
513  for (int i = 0; i < TaggedRenderActions.Count; i++)
514  {
515  for (int j = 0; j < TaggedRenderActions[i].Count; j++)
516  {
517  if (TaggedRenderActions[i][j].LastRenderedGlobalHitTestPath?.Contains(position.X, position.Y) == true)
518  {
519  if (TaggedRenderActions[i][j].ActionType == SKRenderAction.ActionTypes.Path)
520  {
521  found = true;
522 
523  if (CurrentOverAction != TaggedRenderActions[i][j])
524  {
525  if (CurrentOverAction != null && !CurrentOverAction.Disposed)
526  {
527  CurrentOverAction.FirePointerLeave(e);
528  }
529  CurrentOverAction = TaggedRenderActions[i][j];
530  CurrentOverAction.FirePointerEnter(e);
531  }
532 
533  break;
534  }
535  else if (TaggedRenderActions[i][j].ActionType == SKRenderAction.ActionTypes.Text)
536  {
537  found = true;
538 
539  if (CurrentOverAction != TaggedRenderActions[i][j])
540  {
541  if (CurrentOverAction != null && !CurrentOverAction.Disposed)
542  {
543  CurrentOverAction.FirePointerLeave(e);
544  }
545  CurrentOverAction = TaggedRenderActions[i][j];
546  CurrentOverAction.FirePointerEnter(e);
547  }
548 
549  break;
550  }
551  else if (TaggedRenderActions[i][j].ActionType == SKRenderAction.ActionTypes.RasterImage)
552  {
553  found = true;
554 
555  if (CurrentOverAction != TaggedRenderActions[i][j])
556  {
557  if (CurrentOverAction != null && !CurrentOverAction.Disposed)
558  {
559  CurrentOverAction.FirePointerLeave(e);
560  }
561  CurrentOverAction = TaggedRenderActions[i][j];
562  CurrentOverAction.FirePointerEnter(e);
563  }
564 
565  break;
566  }
567  }
568  }
569 
570  if (found)
571  {
572  break;
573  }
574  }
575 
576  if (!found)
577  {
578  if (CurrentOverAction != null && !CurrentOverAction.Disposed)
579  {
580  CurrentOverAction.FirePointerLeave(e);
581  }
582  CurrentOverAction = null;
583  }
584  }
585 
586  private void PointerLeaveAction(object sender, Avalonia.Input.PointerEventArgs e)
587  {
588  if (CurrentOverAction != null && !CurrentOverAction.Disposed)
589  {
590  CurrentOverAction.FirePointerLeave(e);
591  }
592  CurrentOverAction = null;
593  }
594 
595  /// <inheritdoc/>
596  protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
597  {
598  base.OnAttachedToVisualTree(e);
599 
600  StartRenderingThread();
601  }
602 
603  /// <inheritdoc/>
604  protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
605  {
606  StopRenderingThread();
607  }
608 
609  private void StopRenderingThread()
610  {
611  DisposedHandle.Set();
612  }
613 
614  private EventWaitHandle DisposedHandle;
615  private EventWaitHandle RenderRequestedHandle;
616 
617  private RenderingParameters RenderingRequest = null;
618  private readonly object RenderingRequestLock = new object();
619 
620  private void StartRenderingThread()
621  {
622  DisposedHandle = new EventWaitHandle(false, EventResetMode.ManualReset);
623  RenderRequestedHandle = new EventWaitHandle(false, EventResetMode.ManualReset);
624 
625  Thread renderingThread = new Thread(async () =>
626  {
627  bool finished = false;
628  WaitHandle[] handles = new WaitHandle[] { DisposedHandle, RenderRequestedHandle };
629 
630  while (!finished)
631  {
632  int handle = EventWaitHandle.WaitAny(handles);
633 
634  if (handle == 0)
635  {
636  finished = true;
637  }
638  else if (handle == 1)
639  {
640  RenderingParameters requestParams;
641 
642  lock (RenderingRequestLock)
643  {
644  requestParams = RenderingRequest.Clone();
645  RenderRequestedHandle.Reset();
646  }
647 
648  SKCanvas canvas;
649 
650  if (BackBuffer == null || BackBufferRenderingParams == null || requestParams.RenderWidth > BackBufferRenderingParams.RenderWidth || requestParams.RenderHeight > BackBufferRenderingParams.RenderHeight)
651  {
652  await Avalonia.Threading.Dispatcher.UIThread.InvokeAsync(() =>
653  {
654  SKCanvas tempCanvasReference = BackBufferSkiaCanvas;
655  ISkiaDrawingContextImpl tempContextReference = BackBufferSkiaContext;
656  RenderTargetBitmap tempBufferReference = BackBuffer;
657 
658  _ = Avalonia.Threading.Dispatcher.UIThread.InvokeAsync(() =>
659  {
660  tempCanvasReference?.Dispose();
661  tempContextReference?.Dispose();
662  tempBufferReference?.Dispose();
663  }, Avalonia.Threading.DispatcherPriority.MinValue);
664 
665 
666  BackBuffer = new RenderTargetBitmap(new PixelSize(requestParams.RenderWidth, requestParams.RenderHeight), new Vector(96, 96));
667  BackBufferSkiaContext = BackBuffer.CreateDrawingContext(null) as ISkiaDrawingContextImpl;
668  BackBufferSkiaCanvas = BackBufferSkiaContext.SkCanvas;
669  }, Avalonia.Threading.DispatcherPriority.MaxValue);
670  }
671 
672  canvas = BackBufferSkiaCanvas;
673 
674  canvas.Save();
675 
676  canvas.Scale(requestParams.Scale);
677  canvas.Translate(-requestParams.Left, -requestParams.Top);
678 
679  canvas.Clear(BackgroundColour);
680 
681  canvas.ClipRect(new SKRect(requestParams.Left, requestParams.Top, requestParams.Left + requestParams.Width, requestParams.Top + requestParams.Height));
682 
683  RenderImage(canvas);
684 
685  canvas.RestoreToCount(-1);
686 
687  BackBufferRenderingParams = requestParams;
688 
689  lock (FrontBufferLock)
690  {
691  RenderTargetBitmap tempFrontReference = FrontBuffer;
692  RenderingParameters tempFrontRenderingParameters = FrontBufferRenderingParams;
693  SKCanvas tempFrontCanvas = FrontBufferSkiaCanvas;
694  ISkiaDrawingContextImpl tempFrontContext = FrontBufferSkiaContext;
695 
696  FrontBuffer = BackBuffer;
697  FrontBufferRenderingParams = BackBufferRenderingParams;
698  FrontBufferSkiaCanvas = BackBufferSkiaCanvas;
699  FrontBufferSkiaContext = BackBufferSkiaContext;
700 
701  BackBuffer = BackBuffer2;
702  BackBufferRenderingParams = BackBufferRenderingParams2;
703  BackBufferSkiaCanvas = BackBufferSkiaCanvas2;
704  BackBufferSkiaContext = BackBufferSkiaContext2;
705 
706  BackBuffer2 = tempFrontReference;
707  BackBufferRenderingParams2 = tempFrontRenderingParameters;
708  BackBufferSkiaCanvas2 = tempFrontCanvas;
709  BackBufferSkiaContext2 = tempFrontContext;
710  }
711 
712  await Avalonia.Threading.Dispatcher.UIThread.InvokeAsync(() =>
713  {
714  base.InvalidateVisual();
715  }, Avalonia.Threading.DispatcherPriority.MaxValue);
716  }
717  }
718  });
719 
720  renderingThread.Start();
721  }
722 
723  /// <summary>
724  /// An lock for the rendering loop. The public methods of this class already lock on this, but you may need it if you want to directly manipulate the contents of the canvas.
725  /// </summary>
726  public object RenderLock = new object();
727 
728  /// <summary>
729  /// Render the image at to a bitmap at the specified resolution.
730  /// </summary>
731  /// <param name="width">The width of the rendered image. Note that the actual width of the returned image might be lower than this, depending on the aspect ratio of the image.</param>
732  /// <param name="height">The height of the rendered image. Note that the actual height of the returned image might be lower than this, depending on the aspect ratio of the image.</param>
733  /// <param name="background">The background colour for the image. If this is <see langword="null" />, the current background colour is used.</param>
734  /// <returns>A <see cref="RenderTargetBitmap"/> containing the image rendered at the specified resolution.</returns>
735  public RenderTargetBitmap RenderAtResolution(int width, int height, SKColor? background = null)
736  {
737  SKColor realBackground = background ?? this.BackgroundColour;
738 
739  double scale = Math.Min(width / this.PageWidth, height / this.PageHeight);
740 
741  width = (int)Math.Round(this.PageWidth * scale);
742  height = (int)Math.Round(this.PageHeight * scale);
743 
744  RenderTargetBitmap tbr = new RenderTargetBitmap(new PixelSize(width, height), new Vector(96, 96));
745  ISkiaDrawingContextImpl context = tbr.CreateDrawingContext(null) as ISkiaDrawingContextImpl;
746  SKCanvas canvas = context.SkCanvas;
747 
748  canvas.Clear(realBackground);
749 
750  canvas.Save();
751 
752  canvas.Scale((float)scale);
753 
754  RenderImage(canvas);
755 
756  canvas.RestoreToCount(-1);
757 
758  canvas.Dispose();
759  context.Dispose();
760 
761  return tbr;
762  }
763 
764  private void RenderImage(SKCanvas canvas)
765  {
766  lock (RenderLock)
767  {
768  for (int i = 0; i < this.RenderActions.Count; i++)
769  {
770  canvas.Save();
771 
772  if (this.LayerTransforms[i].ActionType == SKRenderAction.ActionTypes.Transform)
773  {
774  SKMatrix mat = this.LayerTransforms[i].Transform.Value;
775  canvas.Concat(ref mat);
776  }
777 
778  RenderLayer(canvas, i);
779  canvas.Restore();
780  }
781  }
782 
783  /*for (int i = 0; i < this.RenderActions.Count; i++)
784  {
785  SKPaint selectionPaint = new SKPaint() { Color = new SKColor(255, 0, 255, 128) };
786 
787  for (int j = 0; j < this.TaggedRenderActions[i].Count; j++)
788  {
789  if (this.TaggedRenderActions[i][j].LastRenderedGlobalHitTestPath != null)
790  {
791  canvas.DrawPath(this.TaggedRenderActions[i][j].LastRenderedGlobalHitTestPath, selectionPaint);
792  }
793  }
794  }*/
795  }
796 
797  private void RenderLayer(SKCanvas canvas, int layer)
798  {
799  bool updateHitTests = this.TaggedRenderActions[this.TaggedRenderActions.Count - 1 - layer].Count > 0;
800 
801  HashSet<uint> ZIndices = new HashSet<uint>();
802 
803  canvas.Save();
804 
805  SKMatrix invertedInitialTransform;
806 
807  if (this.LayerTransforms[layer].ActionType == SKRenderAction.ActionTypes.Transform)
808  {
809  invertedInitialTransform = (canvas.TotalMatrix.PreConcat(this.LayerTransforms[layer].Transform.Value.Invert())).Invert();
810  }
811  else
812  {
813  invertedInitialTransform = canvas.TotalMatrix.Invert();
814  }
815 
816  SKPath clipPath = null;
817  Stack<SKPath> clipPaths = new Stack<SKPath>();
818  clipPaths.Push(null);
819 
820  for (int i = 0; i < this.RenderActions[layer].Count; i++)
821  {
822  ZIndices.Add(this.RenderActions[layer][i].ZIndex);
823 
824  if (this.RenderActions[layer][i].ActionType == SKRenderAction.ActionTypes.Clip)
825  {
826  canvas.ClipPath(this.RenderActions[layer][i].Path, antialias: true);
827 
828  if (updateHitTests)
829  {
830  if (clipPath == null)
831  {
832  clipPath = this.RenderActions[layer][i].Path.Clone();
833  clipPath.Transform(canvas.TotalMatrix.PostConcat(invertedInitialTransform));
834  }
835  else
836  {
837  using (SKPath tempPath = this.RenderActions[layer][i].Path.Clone())
838  {
839  tempPath.Transform(canvas.TotalMatrix.PostConcat(invertedInitialTransform));
840  clipPath.Op(tempPath, SKPathOp.Intersect);
841  }
842  }
843  }
844  }
845  else if (this.RenderActions[layer][i].ActionType == SKRenderAction.ActionTypes.Restore)
846  {
847  canvas.Restore();
848 
849  if (updateHitTests)
850  {
851  clipPath = clipPaths.Pop();
852  }
853  }
854  else if (this.RenderActions[layer][i].ActionType == SKRenderAction.ActionTypes.Save)
855  {
856  canvas.Save();
857 
858  if (updateHitTests)
859  {
860  clipPaths.Push(clipPath?.Clone());
861  }
862  }
863  else if (this.RenderActions[layer][i].ActionType == SKRenderAction.ActionTypes.Transform)
864  {
865  SKMatrix mat = this.RenderActions[layer][i].Transform.Value;
866  canvas.Concat(ref mat);
867  }
868  else
869  {
870  if (this.RenderActions[layer][i].ZIndex == 0)
871  {
872 
873  if (this.RenderActions[layer][i].ActionType == SKRenderAction.ActionTypes.Path && this.RenderActions[layer][i].Path != null)
874  {
875  canvas.DrawPath(this.RenderActions[layer][i].Path, this.RenderActions[layer][i].Paint);
876  }
877  else if (this.RenderActions[layer][i].ActionType == SKRenderAction.ActionTypes.Text)
878  {
879  canvas.DrawText(this.RenderActions[layer][i].Text, this.RenderActions[layer][i].TextX, this.RenderActions[layer][i].TextY, this.RenderActions[layer][i].Font, this.RenderActions[layer][i].Paint);
880  }
881  else if (this.RenderActions[layer][i].ActionType == SKRenderAction.ActionTypes.RasterImage)
882  {
883  (SKBitmap image, bool interpolate) = this.Images[this.RenderActions[layer][i].ImageId];
884 
885  SKPaint paint;
886 
887  if (!interpolate)
888  {
889  paint = null;
890  }
891  else
892  {
893  paint = new SKPaint() { FilterQuality = SKFilterQuality.Medium };
894  }
895 
896  canvas.DrawBitmap(image, this.RenderActions[layer][i].ImageSource.Value, this.RenderActions[layer][i].ImageDestination.Value, paint);
897 
898  paint?.Dispose();
899  }
900  }
901 
902  if (updateHitTests && this.RenderActions[layer][i].Tag != null && this.RenderActions[layer][i].HitTestPath != null)
903  {
904  SKPath hitTestPath = this.RenderActions[layer][i].HitTestPath.Clone();
905  hitTestPath.Transform(canvas.TotalMatrix.PostConcat(invertedInitialTransform));
906 
907  if (clipPath != null)
908  {
909  hitTestPath.Op(clipPath, SKPathOp.Intersect);
910  }
911 
912  this.RenderActions[layer][i].LastRenderedGlobalHitTestPath = hitTestPath;
913  }
914  }
915  }
916 
917  canvas.Restore();
918 
919  uint[] sortedIndices = ZIndices.OrderBy(x => x).ToArray();
920 
921  if (sortedIndices.Length > 1 || sortedIndices[0] != 0)
922  {
923  for (int j = 0; j < sortedIndices.Length; j++)
924  {
925  canvas.Save();
926 
927  for (int i = 0; i < this.RenderActions[layer].Count; i++)
928  {
929  if (this.RenderActions[layer][i].ActionType == SKRenderAction.ActionTypes.Clip)
930  {
931  canvas.ClipPath(this.RenderActions[layer][i].Path, antialias: true);
932  }
933  else if (this.RenderActions[layer][i].ActionType == SKRenderAction.ActionTypes.Restore)
934  {
935  canvas.Restore();
936  }
937  else if (this.RenderActions[layer][i].ActionType == SKRenderAction.ActionTypes.Save)
938  {
939  canvas.Save();
940  }
941  else if (this.RenderActions[layer][i].ActionType == SKRenderAction.ActionTypes.Transform)
942  {
943  SKMatrix mat = this.RenderActions[layer][i].Transform.Value;
944  canvas.Concat(ref mat);
945  }
946  else if (sortedIndices[j] != 0 && this.RenderActions[layer][i].ZIndex == sortedIndices[j])
947  {
948  if (this.RenderActions[layer][i].ActionType == SKRenderAction.ActionTypes.Path && this.RenderActions[layer][i].Path != null)
949  {
950  canvas.DrawPath(this.RenderActions[layer][i].Path, this.RenderActions[layer][i].Paint);
951  }
952  else if (this.RenderActions[layer][i].ActionType == SKRenderAction.ActionTypes.Text)
953  {
954  canvas.DrawText(this.RenderActions[layer][i].Text, this.RenderActions[layer][i].TextX, this.RenderActions[layer][i].TextY, this.RenderActions[layer][i].Font, this.RenderActions[layer][i].Paint);
955  }
956  else if (this.RenderActions[layer][i].ActionType == SKRenderAction.ActionTypes.RasterImage)
957  {
958  (SKBitmap image, bool interpolate) = this.Images[this.RenderActions[layer][i].ImageId];
959 
960  SKPaint paint;
961 
962  if (!interpolate)
963  {
964  paint = null;
965  }
966  else
967  {
968  paint = new SKPaint() { FilterQuality = SKFilterQuality.Medium };
969  }
970 
971  canvas.DrawBitmap(image, this.RenderActions[layer][i].ImageSource.Value, this.RenderActions[layer][i].ImageDestination.Value, paint);
972 
973  paint?.Dispose();
974  }
975  }
976  }
977 
978  canvas.Restore();
979  }
980  }
981  }
982 
983  private bool IsDirty = false;
984 
985  /// <summary>
986  /// Invalidate the contents of the canvas, forcing it to redraw itself.
987  /// </summary>
988  public void InvalidateDirty()
989  {
990  this.IsDirty = true;
991  base.InvalidateVisual();
992  }
993 
994  /// <summary>
995  /// Invalidate the contents of the canvas, specifying that the order of the layers has changed.
996  /// </summary>
997  public void InvalidateZIndex()
998  {
999  for (int i = 0; i < this.TaggedRenderActions.Count; i++)
1000  {
1001  this.TaggedRenderActions[i] = this.TaggedRenderActions[i].OrderByDescending(a => a.ZIndex).ToList();
1002  }
1003  this.InvalidateDirty();
1004  }
1005 
1006 
1007  private RenderTargetBitmap FrontBuffer = null;
1008  private RenderingParameters FrontBufferRenderingParams = null;
1009  private readonly object FrontBufferLock = new object();
1010  ISkiaDrawingContextImpl FrontBufferSkiaContext = null;
1011  SKCanvas FrontBufferSkiaCanvas = null;
1012 
1013  private RenderTargetBitmap BackBuffer = null;
1014  private RenderingParameters BackBufferRenderingParams = null;
1015  ISkiaDrawingContextImpl BackBufferSkiaContext = null;
1016  SKCanvas BackBufferSkiaCanvas = null;
1017 
1018  private RenderTargetBitmap BackBuffer2 = null;
1019  private RenderingParameters BackBufferRenderingParams2 = null;
1020  ISkiaDrawingContextImpl BackBufferSkiaContext2 = null;
1021  SKCanvas BackBufferSkiaCanvas2 = null;
1022 
1023  /// <inheritdoc/>
1024  public override void Render(DrawingContext context)
1025  {
1026  lock (FrontBufferLock)
1027  {
1028  IVisualNode node = GetNode(context.PlatformImpl);
1029 
1030  Rect layoutBounds = node.LayoutBounds;
1031  Rect clipBounds = node.ClipBounds;
1032 
1033 
1034  double DPIscaling = (VisualRoot as Avalonia.Layout.ILayoutRoot)?.LayoutScaling ?? 1;
1035 
1036  double scale = 0.5 * (this.PageWidth / layoutBounds.Width + this.PageHeight / layoutBounds.Height);
1037 
1038 
1039  double left = Math.Max(0, (clipBounds.Left - layoutBounds.Left) * scale);
1040  double top = Math.Max(0, (clipBounds.Top - layoutBounds.Top) * scale);
1041 
1042  double width = Math.Min((clipBounds.Right - layoutBounds.Left) * scale, this.PageWidth) - left;
1043  double height = Math.Min((clipBounds.Bottom - layoutBounds.Top) * scale, this.PageHeight) - top;
1044 
1045  int pixelWidth = (int)Math.Round(width / scale * DPIscaling);
1046  int pixelHeight = (int)Math.Round(height / scale * DPIscaling);
1047 
1048  if (pixelWidth > 0 && pixelHeight > 0)
1049  {
1050  RenderingParameters currentParameters = new RenderingParameters((float)left, (float)top, (float)width, (float)height, (float)(1 / scale * DPIscaling), pixelWidth, pixelHeight);
1051 
1052  if (FrontBuffer != null && FrontBufferRenderingParams == currentParameters && !IsDirty)
1053  {
1054  if (!RenderRequestedHandle.WaitOne(0))
1055  {
1056  LastFrameStopwatch.Reset();
1057  }
1058  else
1059  {
1060  Avalonia.Threading.Dispatcher.UIThread.InvokeAsync(() =>
1061  {
1062  this.InvalidateVisual();
1063  });
1064  }
1065  context.DrawImage(FrontBuffer, new Rect(0, 0, pixelWidth, pixelHeight), new Rect(left, top, width, height));
1066  }
1067  else if (FrontBuffer != null && FrontBufferRenderingParams.GoodEnough(currentParameters) && !IsDirty)
1068  {
1069  if (!RenderRequestedHandle.WaitOne(0))
1070  {
1071  LastFrameStopwatch.Reset();
1072  }
1073  else
1074  {
1075  Avalonia.Threading.Dispatcher.UIThread.InvokeAsync(() =>
1076  {
1077  this.InvalidateVisual();
1078  });
1079  }
1080  context.DrawImage(FrontBuffer, new Rect(0, 0, FrontBufferRenderingParams.RenderWidth, FrontBufferRenderingParams.RenderHeight), new Rect(FrontBufferRenderingParams.Left, FrontBufferRenderingParams.Top, FrontBufferRenderingParams.Width, FrontBufferRenderingParams.Height));
1081  }
1082  else
1083  {
1084  lock (RenderingRequestLock)
1085  {
1086  IsDirty = false;
1087  RenderingRequest = currentParameters;
1088  RenderRequestedHandle.Set();
1089  LastFrameStopwatch.Start();
1090  }
1091 
1092  if (FrontBuffer != null)
1093  {
1094  context.DrawImage(FrontBuffer, new Rect(0, 0, FrontBufferRenderingParams.RenderWidth, FrontBufferRenderingParams.RenderHeight), new Rect(FrontBufferRenderingParams.Left, FrontBufferRenderingParams.Top, FrontBufferRenderingParams.Width, FrontBufferRenderingParams.Height));
1095  }
1096 
1097  long milliseconds = LastFrameStopwatch.ElapsedMilliseconds;
1098 
1099  Bitmap spinner = Spinner?.Invoke(milliseconds);
1100 
1101  if (spinner != null)
1102  {
1103  double spinnerWidth = spinner.Size.Width * scale;
1104  double spinnerHeight = spinner.Size.Height * scale;
1105 
1106  double actualLeft = (clipBounds.Left - layoutBounds.Left) * scale;
1107  double actualTop = (clipBounds.Top - layoutBounds.Top) * scale;
1108 
1109  double actualRight = (clipBounds.Right - layoutBounds.Left) * scale;
1110  double actualBottom = (clipBounds.Bottom - layoutBounds.Top) * scale;
1111 
1112  context.DrawImage(spinner, new Rect(0, 0, spinner.Size.Width, spinner.Size.Height), new Rect((actualLeft + actualRight - spinnerWidth) * 0.5, (actualTop + actualBottom - spinnerHeight) * 0.5, spinnerWidth, spinnerHeight));
1113  }
1114 
1115  Avalonia.Threading.Dispatcher.UIThread.InvokeAsync(() =>
1116  {
1117  this.InvalidateVisual();
1118  });
1119  }
1120  }
1121  }
1122  }
1123 
1124  private bool disposedValue;
1125 
1126  /// <inheritdoc cref="Dispose()"/>
1127  protected virtual void Dispose(bool disposing)
1128  {
1129  if (!disposedValue)
1130  {
1131  if (disposing)
1132  {
1133  this.BackBufferSkiaCanvas?.Dispose();
1134  this.BackBufferSkiaCanvas2?.Dispose();
1135  this.BackBufferSkiaContext?.Dispose();
1136  this.BackBufferSkiaContext2?.Dispose();
1137  this.BackBuffer?.Dispose();
1138  this.BackBuffer2?.Dispose();
1139  this.DisposedHandle?.Set();
1140  this.FrontBufferSkiaCanvas?.Dispose();
1141  this.FrontBufferSkiaContext?.Dispose();
1142  this.FrontBuffer?.Dispose();
1143 
1144  foreach (KeyValuePair<string, (SKBitmap, bool)> image in this.Images)
1145  {
1146  image.Value.Item1?.Dispose();
1147  }
1148 
1149  for (int i = 0; i < this.RenderActions?.Count; i++)
1150  {
1151  foreach (SKRenderAction act in this.RenderActions[i])
1152  {
1153  act?.Dispose();
1154  }
1155 
1156  this.LayerTransforms?[i]?.Dispose();
1157  }
1158  }
1159 
1160  disposedValue = true;
1161  }
1162  }
1163 
1164  /// <inheritdoc/>
1165  public void Dispose()
1166  {
1167  Dispose(disposing: true);
1168  GC.SuppressFinalize(this);
1169  }
1170  }
1171 }
VectSharp.Canvas.SKRenderAction.ActionTypes
ActionTypes
Types of rendering actions.
Definition: SKRenderContext.cs:43
VectSharp.Canvas
Definition: AvaloniaContext.cs:29
VectSharp.Canvas.SKRenderContext
Represents a page that has been prepared for fast rendering using the SkiaSharp renderer.
Definition: SKRenderContext.cs:547
VectSharp.Canvas.SKMultiLayerRenderCanvas.PageHeight
double PageHeight
The height of the page that is rendered on this canvas.
Definition: SKMultiLayerRenderCanvas.cs:53
VectSharp.Canvas.SKMultiLayerRenderCanvas.SKMultiLayerRenderCanvas
SKMultiLayerRenderCanvas(Document document, Colour backgroundColour, double width, double height, List< SKRenderAction > layerTransforms=null)
Create a new SKMultiLayerRenderCanvas from a Document, where each page represents a layer.
Definition: SKMultiLayerRenderCanvas.cs:98
VectSharp.Canvas.SKMultiLayerRenderCanvas.Dispose
void Dispose()
Definition: SKMultiLayerRenderCanvas.cs:1165
VectSharp.Colour
Represents an RGB colour.
Definition: Colour.cs:26
VectSharp.Canvas.SKMultiLayerRenderCanvas.RenderAtResolution
RenderTargetBitmap RenderAtResolution(int width, int height, SKColor? background=null)
Render the image at to a bitmap at the specified resolution.
Definition: SKMultiLayerRenderCanvas.cs:735
VectSharp.Canvas.SKMultiLayerRenderCanvas.RenderLock
object RenderLock
An lock for the rendering loop. The public methods of this class already lock on this,...
Definition: SKMultiLayerRenderCanvas.cs:726
VectSharp.Canvas.SKMultiLayerRenderCanvas.MoveLayer
void MoveLayer(int oldIndex, int newIndex)
Move the specified layer to the specified position, shifting all other layers as necessary.
Definition: SKMultiLayerRenderCanvas.cs:389
VectSharp.Canvas.SKMultiLayerRenderCanvas.UpdateWith
void UpdateWith(List< SKRenderContext > contents, List< SKRenderAction > contentTransforms, Colour backgroundColour, double width, double height)
Replace the contents of the SKMultiLayerRenderCanvas with the specified layers.
Definition: SKMultiLayerRenderCanvas.cs:166
VectSharp.Canvas.SKMultiLayerRenderCanvas.InvalidateDirty
void InvalidateDirty()
Invalidate the contents of the canvas, forcing it to redraw itself.
Definition: SKMultiLayerRenderCanvas.cs:988
VectSharp.Canvas.SKMultiLayerRenderCanvas.AddLayer
void AddLayer(SKRenderContext newContent, SKRenderAction newTransform)
Add a new layer to the image.
Definition: SKMultiLayerRenderCanvas.cs:276
VectSharp.Document
Represents a collection of pages.
Definition: Document.cs:28
VectSharp.Canvas.SKMultiLayerRenderCanvas.InvalidateZIndex
void InvalidateZIndex()
Invalidate the contents of the canvas, specifying that the order of the layers has changed.
Definition: SKMultiLayerRenderCanvas.cs:997
VectSharp.Canvas.SKMultiLayerRenderCanvas.UpdateLayer
void UpdateLayer(int layer, SKRenderContext newContent, SKRenderAction newTransform)
Replace a single layer with the specified content.
Definition: SKMultiLayerRenderCanvas.cs:244
VectSharp.Canvas.SKMultiLayerRenderCanvas
Represents a multi-threaded, triple-buffered canvas on which the image is drawn using SkiaSharp.
Definition: SKMultiLayerRenderCanvas.cs:44
VectSharp.Canvas.SKMultiLayerRenderCanvas.SKMultiLayerRenderCanvas
SKMultiLayerRenderCanvas(IEnumerable< Page > layers, Colour backgroundColour, double width, double height, List< SKRenderAction > layerTransforms=null)
Create a new SKMultiLayerRenderCanvas from a collection of Pages, each representing a layer.
Definition: SKMultiLayerRenderCanvas.cs:108
VectSharp.Canvas.SKRenderAction
Represents a light-weight rendering action.
Definition: SKRenderContext.cs:31
VectSharp.Canvas.SKMultiLayerRenderCanvas.PageWidth
double PageWidth
The width of the page that is rendered on this canvas.
Definition: SKMultiLayerRenderCanvas.cs:48
VectSharp.Canvas.SKMultiLayerRenderCanvas.SKMultiLayerRenderCanvas
SKMultiLayerRenderCanvas(List< SKRenderContext > contents, List< SKRenderAction > contentTransforms, Colour backgroundColour, double width, double height)
Create a new SKMultiLayerRenderCanvas from a list of SKRenderContexts, each representing a layer.
Definition: SKMultiLayerRenderCanvas.cs:143
VectSharp.Canvas.SKRenderAction.Disposed
bool Disposed
Returns a boolean value indicating whether the current instance has been disposed.
Definition: SKRenderContext.cs:35
VectSharp.Canvas.SKMultiLayerRenderCanvas.Spinner
Func< long, Bitmap > Spinner
If the image to draw is not already cached, this method is called with an argument containing the num...
Definition: SKMultiLayerRenderCanvas.cs:70
VectSharp.Canvas.SKMultiLayerRenderCanvas.Render
override void Render(DrawingContext context)
Definition: SKMultiLayerRenderCanvas.cs:1024
VectSharp.Canvas.SKMultiLayerRenderCanvas.RemoveLayer
void RemoveLayer(int layer)
Remove the specified layer from the image.
Definition: SKMultiLayerRenderCanvas.cs:340
VectSharp.Canvas.SKRenderAction.TransformAction
static SKRenderAction TransformAction(SKMatrix transform, string tag=null)
Creates a new SKRenderAction representing a transform.
Definition: SKRenderContext.cs:422
VectSharp.Canvas.SKMultiLayerRenderCanvas.SwitchLayers
void SwitchLayers(int layer1, int layer2)
Switch the position of the two specified layers.
Definition: SKMultiLayerRenderCanvas.cs:364
VectSharp.Canvas.SKMultiLayerRenderCanvas.RenderActions
List< List< SKRenderAction > > RenderActions
The list of render actions, each element in this list is itself a list, containing the actions that c...
Definition: SKMultiLayerRenderCanvas.cs:59
VectSharp.Canvas.SKMultiLayerRenderCanvas.LayerTransforms
List< SKRenderAction > LayerTransforms
The list of transforms associated with each layer.
Definition: SKMultiLayerRenderCanvas.cs:64
VectSharp.Canvas.SKMultiLayerRenderCanvas.InsertLayer
void InsertLayer(int index, SKRenderContext newContent, SKRenderAction newTransform)
Insert a new layer at the specified index.
Definition: SKMultiLayerRenderCanvas.cs:309