在Avalonia应用程序中创建Skia Canvas元素是否可行?

5
我希望将一个Electron应用程序转移到Avalonia平台。该应用程序目前使用Paper.js来绘制和管理与复杂多边形的交互。研究Avalonia时,我发现它使用Skia,它似乎提供了与Paper.js类似的功能。我希望有一种简单的方法来创建一个Skia“画布”,并直接使用SkiaSharp API。
很遗憾,我没有找到相关文档/答案。 Avalonia gitter上的某人提到我可以使用RenderTargetBitmap,但在查找源代码后(找不到任何文档),我认为直接使用Skia画布更容易/更优雅一些。
这可行吗?
5个回答

7
您有几个选项:
  • 使用WriteableBitmap:锁定位,从中创建SKBitmap,创建SKCanvas,绘制。然后将该WritableBitmap用作控件的Source。这是最安全、最可移植的方法,但效率最低。
  • 在渲染线程上直接访问底层的Skia上下文,您可以在这里找到一个示例。请注意,Render回调可能会从任何线程调用,因此请正确管理锁定。还要注意,Avalonia具有可插拔的渲染器架构,因此即使当前在所有平台上默认使用Skia并且高度可能仍然是主要渲染器,但技术上不能保证渲染器是Skia。
  • 一旦0.10推出,您就可以创建一个硬件加速的SKCanvas,并渲染到OpenGL纹理中。目前正在OpenGL控制分支中开发使用OpenGL纹理作为Avalonia图像所需的基础设施。

有没有其他方法可以做到这一点?我的意思是除了直接使用Skia之外。是否有一个Avalonia控件允许绘制任意多边形?Canvas控件看起来可能会起作用,但我觉得它更像是一个布局工具(毕竟它是一个“Panel”),而不是一个绘图工具(就像HTML Canvas)。 - Matthew Goulart
你可以只需重写OnRender并使用DrawingContext,如果StreamGeometry足够满足你的需求。 - kekekeks
@kekekeks,我正在尝试基于您的第三个建议进行SkiaSharp渲染,但没有成功。您能否给出一个示例来说明如何完成这项工作? 我已经查看了链接PR中的演示,并尝试将其与此问题的答案相结合:https://dev59.com/4GwMtIcB2Jgan1zn71mL - Netråm
目前,除非您愿意使用WGL而不是ANGLE,否则Windows的OpenGL渲染有些问题,因此第二个选项目前提供了最佳性能。我们计划在今年为SkiaSharp渲染提供内置控件,并提供多个选项Q1或Q2。 - kekekeks
@kekekeks,我正在评估Avalonia,内置的SkiaSharp渲染控件是否已准备好支持多个选项? - Raid

3

我在这个网站上使用其他答案时遇到了困难,希望这能帮助其他人。

想法是任何控件都有一个可重写的渲染方法。此方法可以调用context.Custom,该方法接受一个ICustomDrawOperation实例。我的理解是Skia渲染不能保证,因此代码需要检查并转换为ISkiaDrawingContextImpl

对于那些只想要放置一些东西的人(就像我一样),这里有一些应该有所帮助的代码:

public partial class SkiaCanvas : UserControl
{
    class RenderingLogic : ICustomDrawOperation
    {
        public Action<SKCanvas> RenderCall;
        public Rect Bounds { get; set; }

        public void Dispose() {}

        public bool Equals(ICustomDrawOperation? other) => other == this;

        // not sure what goes here....
        public bool HitTest(Point p) { return false; }

        public void Render(IDrawingContextImpl context)
        {
            var canvas = (context as ISkiaDrawingContextImpl)?.SkCanvas;
            if(canvas != null)
            {
                Render(canvas);
            }
        }

        private void Render(SKCanvas canvas)
        {
            RenderCall?.Invoke(canvas);
        }
    }

    RenderingLogic renderingLogic;

    public event Action<SKCanvas> RenderSkia;

    public SkiaCanvas()
    {
        InitializeComponent();

        renderingLogic = new RenderingLogic();
        renderingLogic.RenderCall += (canvas) => RenderSkia?.Invoke(canvas);
    }

    public override void Render(DrawingContext context)
    {
        renderingLogic.Bounds = new Rect(0, 0, this.Bounds.Width, this.Bounds.Height);

        context.Custom(renderingLogic);
        
        // If you want continual invalidation (like a game):
        //Dispatcher.UIThread.InvokeAsync(InvalidateVisual, DispatcherPriority.Background);
    }
}


现在你可以在自己的AXAML中使用Skia画布:
<views:SkiaCanvas Height="200" RenderSkia="HandleRenderSkia"></views:SkiaCanvas>

在您的代码后台中实现HandleRenderSkia:
void HandleRenderSkia(SKCanvas canvas)
{
    canvas.DrawCircle(100, 100, 80, new SKPaint() { Color =  SKColors.Green, IsAntialias = true });
}

现在您将拥有完整的Skia渲染:

在此输入图片描述


1
这个程序是基于官方Avalonia使用Skia的示例:https://github.com/AvaloniaUI/Avalonia/blob/master/samples/RenderDemo/Pages/CustomSkiaPage.cs。 - KallDrexx

1

谢谢Victor,对Avalonia 0.10.18有很大帮助。

自从Avalonia 11.0.0 preview3版本以来,一种特性方法可以帮助我们获取SkCanvas,如下所示:

public void Render(IDrawingContextImpl context) {
    // Avalonia 0.10.18 method
    //var canvas = (context as ISkiaDrawingContextImpl)?.SkCanvas;
    //if (canvas != null) RenderAction?.Invoke(canvas);

    // Avalonia 11.0.0 preview feature method
    var skia = context.GetFeature<ISkiaSharpApiLeaseFeature>();
    using (var lease = skia.Lease()) {
        SKCanvas canvas = lease.SkCanvas;
        if (canvas != null) RenderAction?.Invoke(canvas);
    }
}

调整窗口大小时出现了另一个问题,可能是由于 Skia 位图缓冲区不足以覆盖更大的窗口空间导致的。以下解决方案完全有效:

public SkiaCanvas()
{
    InitializeComponent();

    // create RenderingLogic when rendering
}

public override void Render(DrawingContext context)
{
    if (renderingLogic == null || renderingLogic.Bounds != this.Bounds) {
        // (re)create drawing operation matching actual bounds
        if (renderingLogic != null) renderingLogic.Dispose();
        renderingLogic = new SkiaDrawOperation();
        renderingLogic.RenderAction += (canvas) => OnSkiaRendering(canvas);
        renderingLogic.Bounds = new Rect(0, 0, this.Bounds.Width, this.Bounds.Height);
    }

    renderingLogic.Bounds = new Rect(0, 0, this.Bounds.Width, this.Bounds.Height);

    context.Custom(renderingLogic);

    // ...
}

0
如果有人因为尝试将SkiaSharp的SKBitmap转换为Avalonia Bitmap而来到这里,我希望以下内容能对你有所帮助。你可以按照以下方式进行转换:
public Avalonia.Media.Imaging.Bitmap SKBitmapToAvaloniaBitmap(SKBitmap skBitmap)
{
    SKData data = skBitmap.Encode(SKEncodedImageFormat.Png, 100);
    using (Stream stream = data.AsStream())
    {
        return new Avalonia.Media.Imaging.Bitmap(stream);
    }
}

能否从Avalonia.Media.Imaging.Bitmap获取SKBitmap? - undefined

0

这里是 Victor Chelaru 和 friedrich 使用 Avalonia 11.0.0-preview7 提供的答案混合。我将其添加在此处,因为作为 Avalonia 的新用户,我在使其正常工作方面遇到了一些麻烦。

我没有包含额外的调整大小代码,因为我个人不需要它。

在您的解决方案中,您需要添加 "Avalonia.Skia" NuGet 包。

using Avalonia;
using Avalonia.Controls;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.Rendering.SceneGraph;
using Avalonia.Skia;
using SkiaSharp;
using System;

namespace YOUR_NAMESPACE_GOES_HERE;

public partial class SkiaCanvas : UserControl
{
    class RenderingLogic : ICustomDrawOperation
    {
        public Action<SKCanvas> RenderCall;
        public Rect Bounds { get; set; }

        public void Dispose() { }

        public bool Equals(ICustomDrawOperation? other) => other == this;

        // not sure what goes here....
        public bool HitTest(Point p) { return false; }

        public void Render(IDrawingContextImpl context)
        {
            var skia = context.GetFeature<ISkiaSharpApiLeaseFeature>();
            using (var lease = skia.Lease())
            {
                SKCanvas canvas = lease.SkCanvas;
                if (canvas != null) RenderCall?.Invoke(canvas);
            }
        }
    }

    RenderingLogic renderingLogic;

    public event Action<SKCanvas> RenderSkia;

    public SkiaCanvas(int width, int height)
    {
        InitializeComponent();

        Width = width;
        Height = height;
        Bounds = new Rect(0, 0, width, height);

        HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Left;
        VerticalAlignment = Avalonia.Layout.VerticalAlignment.Top;

        Initialized += SkiaCanvas_Initialized;

        renderingLogic = new RenderingLogic();
        renderingLogic.RenderCall += (canvas) => RenderSkia?.Invoke(canvas);
    }

    private void SkiaCanvas_Initialized(object? sender, EventArgs e)
    {
        // Remove this if you don't need to do anything when this event is raised.
    }

    public override void Render(DrawingContext context)
    {
        renderingLogic.Bounds = new Rect(0, 0, this.Bounds.Width, this.Bounds.Height);
        context.Custom((ICustomDrawOperation)renderingLogic);
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接