using System; using System.Runtime.InteropServices; using System.Runtime.InteropServices.WindowsRuntime; #if WINUI using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Media; using Microsoft.UI.Xaml.Media.Imaging; #else using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Media; using Windows.UI.Xaml.Media.Imaging; #endif #if WINDOWS || WINUI namespace SkiaSharp.Views.Windows #else namespace SkiaSharp.Views.UWP #endif { public partial class SKXamlCanvas : Canvas { private byte[] pixels; private GCHandle pixelsHandle; private int pixelWidth; private int pixelHeight; private WriteableBitmap bitmap; public SKXamlCanvas() { Initialize(); } partial void DoUnloaded() => FreeBitmap(); private void DoInvalidate() { if (designMode) return; if (!isVisible) return; if (ActualWidth <= 0 || ActualHeight <= 0) { CanvasSize = SKSize.Empty; return; } var info = CreateBitmap(out var unscaledSize, out var dpi); using (var surface = SKSurface.Create(info, pixelsHandle.AddrOfPinnedObject(), info.RowBytes)) { var userVisibleSize = IgnorePixelScaling ? unscaledSize : info.Size; CanvasSize = userVisibleSize; if (IgnorePixelScaling) { var canvas = surface.Canvas; canvas.Scale(dpi); canvas.Save(); } OnPaintSurface(new SKPaintSurfaceEventArgs(surface, info.WithSize(userVisibleSize), info)); } // This implementation is not fast enough, and providing the original pixel buffer // is needed, yet the internal `IBufferByteAccess` interface is not yet available in Uno. // Once it is, we can replace this implementation and provide the pinned array directly // to skia. using (var data = bitmap.PixelBuffer.AsStream()) { data.Write(pixels, 0, pixels.Length); data.Flush(); } bitmap.Invalidate(); } private SKImageInfo CreateBitmap(out SKSizeI unscaledSize, out float dpi) { var size = CreateSize(out unscaledSize, out dpi); var info = new SKImageInfo(size.Width, size.Height, SKColorType.Bgra8888, SKAlphaType.Premul); if (bitmap?.PixelWidth != info.Width || bitmap?.PixelHeight != info.Height) FreeBitmap(); if (bitmap == null && info.Width > 0 && info.Height > 0) { bitmap = new WriteableBitmap(info.Width, info.Height); var brush = new ImageBrush { ImageSource = bitmap, AlignmentX = AlignmentX.Left, AlignmentY = AlignmentY.Top, Stretch = Stretch.Fill }; Background = brush; } if (pixels == null || pixelWidth != info.Width || pixelHeight != info.Height) { FreeBitmap(); pixels = new byte[info.BytesSize]; pixelsHandle = GCHandle.Alloc(pixels, GCHandleType.Pinned); pixelWidth = info.Width; pixelHeight = info.Height; } return info; } private void FreeBitmap() { if (pixels != null) { pixelsHandle.Free(); pixels = null; bitmap = null; } } } }