20 private readonly
object _presentSync =
new();
23 private WriteableBitmap? _bitmap;
26 private SKImage? _currentImage;
27 private SKRectI _pendingSourceRect;
28 private bool _blitScheduled;
31 private readonly ConcurrentQueue<SKImage> _toDispose =
new();
33 private bool _disposed;
41 : base(Math.Max(1, (int)control.Bounds.
Width), Math.Max(1, (int)control.Bounds.
Height), initialSizeAvailable: false)
43 _control = control ??
throw new ArgumentNullException(nameof(control));
44 _control.SizeChanged += OnSizeChanged;
47 private void OnSizeChanged(
object? sender, global::Avalonia.Controls.SizeChangedEventArgs e)
57 if (_disposed)
return;
58 var w = (int)_control.Bounds.Width;
59 var h = (int)_control.Bounds.Height;
71 public override void Present(SKImage bufferImage, SKRectI bufferRect, SKRect destRect)
73 var sourceRect = SKRectI.Intersect(
75 new SKRectI(0, 0, bufferImage.Width, bufferImage.Height));
77 if (sourceRect.IsEmpty)
79 bufferImage.Dispose();
88 bufferImage.Dispose();
92 var old = _currentImage;
93 _currentImage = bufferImage;
94 if (!ReferenceEquals(old, _currentImage) && old is not
null)
95 _toDispose.Enqueue(old);
97 _pendingSourceRect = Union(_pendingSourceRect, sourceRect);
98 scheduleBlit = !_blitScheduled;
99 _blitScheduled =
true;
105 Dispatcher.UIThread.Post(BlitAndInvalidate, DispatcherPriority.Render);
108 private void BlitAndInvalidate()
117 _blitScheduled =
false;
122 sourceRect = _pendingSourceRect;
123 _pendingSourceRect = SKRectI.Empty;
124 _blitScheduled =
false;
127 if (img ==
null)
return;
133 if (w <= 0 || h <= 0)
return;
135 var imageBounds =
new SKRectI(0, 0, w, h);
136 sourceRect = SKRectI.Intersect(sourceRect, imageBounds);
137 if (sourceRect.IsEmpty)
return;
140 var bitmap = _bitmap;
141 if (bitmap ==
null || bitmap.PixelSize.Width != w || bitmap.PixelSize.Height != h)
144 bitmap =
new WriteableBitmap(
147 PixelFormat.Bgra8888,
151 sourceRect = imageBounds;
156 using (var fb = bitmap.Lock())
158 var info =
new SKImageInfo(
161 SKColorType.Bgra8888,
164 var destinationOffset = checked((sourceRect.Top * fb.RowBytes) + (sourceRect.Left * 4));
165 var destination = IntPtr.Add(fb.Address, destinationOffset);
167 copied = img.ReadPixels(
177 _control.SetBitmap(bitmap);
178 _control.InvalidateVisual();
182 DisposeStaleImages();
186 private static SKRectI Union(SKRectI left, SKRectI right)
188 if (left.IsEmpty)
return right;
189 if (right.IsEmpty)
return left;
192 Math.Min(left.Left, right.Left),
193 Math.Min(left.Top, right.Top),
194 Math.Max(left.Right, right.Right),
195 Math.Max(left.Bottom, right.Bottom));
198 private void DisposeStaleImages()
200 while (_toDispose.TryDequeue(out var stale))
209 SKImage? currentImage;
212 if (_disposed)
return;
215 currentImage = _currentImage;
216 _currentImage =
null;
217 _pendingSourceRect = SKRectI.Empty;
218 _blitScheduled =
false;
221 _control.SizeChanged -= OnSizeChanged;
223 DisposeStaleImages();
225 currentImage?.Dispose();
230 GC.SuppressFinalize(
this);