Win2D provides the ability to use GPU-accelerated 2D graphics in WinUI 3 apps.

The official documentaion positions Win2D as a solution for simple games, charts and other simple 2D graphics. I use it for the image preview area of my small pet project (annonce will come soon).

To use Win2D in WinUI 3 projects Microsoft.Graphics.Win2D package is used. API Reference is under reconstruction.

The simplest way to start using Win2D is to place CanvasControl and add handlers for Draw and CreateResources events. The main trick is to capture the load task to control resource loading process:

xmlns:win2d="using:Microsoft.Graphics.Canvas.UI.Xaml"

<win2d:CanvasControl CreateResources="CanvasControl_CreateResources" Draw="CanvasControl_Draw"/>

private Task? imageLoadTask;
private CanvasBitmap? image;

// Draw an image
private void CanvasControl_Draw(CanvasControl sender, CanvasDrawEventArgs args)
{
    // Wait for resource loading
    if (!IsImageLoading() && image != null)
    {
        // Calculate the factor to fit an image into the canvas
        var factor = Math.Min(sender.ActualWidth / image.Size.Width, sender.ActualHeight / image.Size.Height);
       
        // Calculate a new image size
        var width = factor * image.Size.Width;
        var height = factor * image.Size.Height;

        // Calculate an image center
        var x = (sender.ActualWidth - width) / 2;
        var y = (sender.ActualHeight - height) / 2;

        // Draw an image
        args.DrawingSession.DrawImage(
            image,
            new Rect(x, y, width, height),
            new Rect(0, 0, image.Size.Width, image.Size.Width),
            1,
            CanvasImageInterpolation.NearestNeighbor);
    }
}

// Creates resources needed for drawing
private void CanvasControl_CreateResources(CanvasControl sender, CanvasCreateResourcesEventArgs args)
{
    args.TrackAsyncAction(CreateResources().AsAsyncAction());
}

// Creates resources
private async Task CreateResources()
{
    // Cancel the current loading task
    await CancelTask();

    // Dispose the current image
    image?.Dispose();
    image = null;

    // Load a new image from ImageStream stream
    await LoadImage(ImageStream?.AsRandomAccessStream());
}

// Cancels the current loading
private async Task CancelTask()
{
    if (imageLoadTask != null)
    {
        imageLoadTask.AsAsyncAction().Cancel();
        try
        {
            await imageLoadTask;
        }
        catch
        { }

        imageLoadTask = null;
    }
}

// Loads a new image
private async Task LoadImage(IRandomAccessStream? stream)
{
    // Loads an image asynchronously
    if (stream != null)
    {
        image = await CanvasBitmap.LoadAsync(ImageCanvas, stream);
    }
    else
    {
        image = null;
    }
    
    // Update a canvas
    ImageCanvas.Invalidate();
}

// Waits for an image loading
private bool IsImageLoading()
{
    if (imageLoadTask == null)
    {
        return false;
    }
    else if (!imageLoadTask.IsCompleted)
    {
        return true;
    }
    else
    {
        try
        {
            imageLoadTask.Wait();
        }
        catch (AggregateException ex)
        {
            ex.Handle(x => throw x);
        }
        finally
        {
            imageLoadTask = null;
        }

        return false;
    }
}

When the page or other container is unloaded we need to dispose the canvas properly:

canvas.RemoveFromVisualTree();
canvas = null;

I’ll tell you more about Win2D using my project as an example. Stay with us.