生成简单的 Win2D 应用

本教程介绍了 Win2D 的一些基本绘图功能。 将了解如何执行以下操作:

  • 将 Win2D 添加到 C# XAML Windows 项目。
  • 绘制文本和几何图形。
  • 应用筛选器效果。
  • 对 Win2D 内容进行动画处理。
  • 遵循 Win2D 最佳做法。

设置开发人员计算机

请确保使用所有必要的工具设置计算机:

创建新的 Win2D 项目

按照 Win2D“Hello, World!”快速入门中的步骤使用 Win2D 创建一个新项目,并添加对 Win2D NuGet 包的引用。 你可以使用 WinUI 3(Windows 应用 SDK)或通用 Windows 平台 (UWP)。

将 Win2D CanvasControl 添加到应用的 XAML

  1. 若要使用 Win2D,你需要在某个位置绘制图形。 在 XAML 应用中,执行此操作的最简单方法是将 CanvasControl 添加到 XAML 页面。

在继续之前,请先确保项目的“体系结构”选项设置为 x86x64,而不是 设置为 Any CPU。 Win2D 是在 C++ 中实现的,因此使用 Win2D 的项目需要面向特定的 CPU 体系结构。

  1. 通过在解决方案资源管理器中双击,导航到项目中的 MainPage.xaml。 这将打开文件。 为方便起见,可以双击“设计器”选项卡中的“XAML”按钮,这会隐藏“可视化设计器”,并保留代码视图的所有空间。

  2. 在添加控件之前,首先必须告知 XAML 定义 CanvasControl 的位置。 为此,请转到 Page 元素的定义,并添加以下语句:xmlns:canvas="using:Microsoft.Graphics.Canvas.UI.Xaml"。 XAML 现在应如下所示:

<Page
    ...
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:canvas="using:Microsoft.Graphics.Canvas.UI.Xaml"
    mc:Ignorable="d">
  1. 现在,将新的 canvas:CanvasControl 作为子元素添加到根 Grid 元素。 为控件指定一个名称,例如“canvas”。 XAML 现在应如下所示:
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <canvas:CanvasControl x:Name="canvas"/>
</Grid>
  1. 接下来,定义 Draw 事件的事件处理程序。 每当应用需要绘制或重新绘制其内容时,CanvasControl 都引发 Draw。 最简单的方法是让 Visual Studio 自动完成为你提供帮助。 在 CanvasControl 定义中,开始键入 Draw 事件处理程序的新属性
<canvas:CanvasControl x:Name="canvas" Draw="canvas_Draw" />

注意

输入 Draw=" 后,Visual Studio 应弹出一个框,提示你自动填写事件处理程序的正确定义。 按 TAB 接受 Visual Studio 的默认事件处理程序。 这还将在 (MainPage.xaml.cs) 的代码中自动添加格式正确的事件处理程序方法。 如果你未使用“自动完成”,请不要担心,可以在下一步中手动添加事件处理程序方法。

在 Win2D 中绘制第一个文本

  1. 现在,我们转到 C# 代码隐藏。 从解决方案资源管理器打开 MainPage.xaml.cs

  2. C# 文件的顶部是各种命名空间定义。 添加以下命名空间:

using Windows.UI;
using System.Numerics;
using Microsoft.Graphics.Canvas;
using Microsoft.Graphics.Canvas.Effects;
  1. 接下来,应会看到由自动完成插入的以下空白事件处理程序:
private void canvas_Draw(
    Microsoft.Graphics.Canvas.UI.Xaml.CanvasControl sender,
    Microsoft.Graphics.Canvas.UI.Xaml.CanvasDrawEventArgs args)
{
}

(如果上一步未使用自动完成,请立即添加此代码。)

  1. CanvasDrawEventArgs 参数公开一个成员 DrawingSession,该成员的类型为 CanvasDrawingSession。 此类在 Win2D 中提供了大多数基本的绘图功能:它具有诸如 CanvasDrawingSession.DrawRectangleCanvasDrawingSession.DrawImage 等方法,以及绘制文本所需的方法 CanvasDrawingSession.DrawText

将以下代码添加到 canvas_Draw 方法中:

args.DrawingSession.DrawText("Hello, World!", 100, 100, Colors.Black);

第一个参数 "Hello, World!" 是希望 Win2D 显示的字符串。 两个“100”指示 Win2D 将此文本偏移到右侧和向下的 100 个 DIP(与设备无关的像素)。 最后,Colors.Black 定义文本的颜色。

  1. 现在,你已准备好运行第一个 Win2D 应用。 按 F5 键编译和发布。 应会看到一个空白窗口,其中“Hello, world!”为黑色。

正确释放 Win2D 资源

  1. 在继续绘制其他类型的内容之前,应首先添加一些代码,以确保应用避免内存泄漏。 大多数以 .NET 语言编写并使用 Win2D 控件(例如 CanvasControl)的 Win2D 应用程序都需要遵循以下步骤。 严格地说,简单的“Hello, world”应用不受影响,但一般情况下,这是一种很好的做法。

有关详细信息,请参阅避免内存泄漏

  1. 打开 MainPage.xaml 并查找包含 CanvasControl 的 Page XAML 元素。 它应该是文件中的第一个元素。

  2. Unloaded 事件添加处理程序。 XAML 应如下所示:

<Page
    ...
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:canvas="using:Microsoft.Graphics.Canvas.UI.Xaml"
    mc:Ignorable="d"
    Unloaded="Page_Unloaded">
  1. 转到 MainPage.xaml.cs 并找到 Page_Unloaded 事件处理程序。 添加以下代码:
void Page_Unloaded(object sender, RoutedEventArgs e)
{
    this.canvas.RemoveFromVisualTree();
    this.canvas = null;
}
  1. 如果应用包含多个 Win2D 控件,则需要对包含 Win2D 控件的每个 XAML 页面重复上述步骤。 你的应用当前只有一个 CanvasControl,因此你已全部完成

绘制一些形状

  1. 向应用添加 2D 几何图形同样简单。 将以下代码添加到 canvas_Draw 的末尾:
args.DrawingSession.DrawCircle(125, 125, 100, Colors.Green);
args.DrawingSession.DrawLine(0, 0, 50, 200, Colors.Red);

这两个方法的参数类似于 DrawText。 圆由中心点(125,125)、半径(100)和颜色(绿色)定义。 线条由开始(0,0)、结尾(50、200)和颜色(红色)定义。

  1. 现在,按 F5 运行应用。 应会看到“Hello, world!”以及一个绿色圆圈和红线。

你可能想知道如何控制更高级的绘图选项(如线条粗细和短划线)或更复杂的填充选项(如使用画笔)。 Win2D 提供所有这些选项以及更多选项,使你能够在需要时轻松使用它们。 所有 Draw(...) 方法都提供了许多重载,这些重载可以接受其他参数,例如 CanvasTextFormat(字体系列、大小等)和 CanvasStrokeStyle(短划线、点、尾帽等)。 请随意浏览 API 图面,详细了解这些选项。

动态生成绘图参数

  1. 现在,让我们通过绘制一组具有随机颜色的形状和文本来添加一些多样性。

将下面的代码添加到 MainPage 类的顶部。 这是生成绘制时将使用的随机值的帮助程序功能:

Random rnd = new Random();
private Vector2 RndPosition()
{
    double x = rnd.NextDouble() * 500f;
    double y = rnd.NextDouble() * 500f;
    return new Vector2((float)x, (float)y);
}

private float RndRadius()
{
    return (float)rnd.NextDouble() * 150f;
}

private byte RndByte()
{
    return (byte)rnd.Next(256);
}
  1. 修改 canvas_Draw 方法以使用以下随机参数进行绘制:
private void canvas_Draw(
    Microsoft.Graphics.Canvas.UI.Xaml.CanvasControl sender,
    Microsoft.Graphics.Canvas.UI.Xaml.CanvasDrawEventArgs args)
{
    args.DrawingSession.DrawText("Hello, World!", RndPosition(), Color.FromArgb(255, RndByte(), RndByte(), RndByte()));
    args.DrawingSession.DrawCircle(RndPosition(), RndRadius(), Color.FromArgb(255, RndByte(), RndByte(), RndByte()));
    args.DrawingSession.DrawLine(RndPosition(), RndPosition(), Color.FromArgb(255, RndByte(), RndByte(), RndByte()));
}

我们来细分一下更改的方式 DrawText"Hello, World!" 与之前相同。 x 和 y 偏移参数已替换为 RndPosition 生成的单个 System.Numerics.Vector2 参数。 最后,Color.FromArgb 允许你使用 A、R、G 和 B 值定义颜色,而不是使用预定义的颜色。 A 为 alpha 或不透明度级别;在这种情况下,始终需要完全不透明(255)。

DrawCircleDrawLine 的运行方式与 DrawText 类似。

  1. 最后,将绘图代码包装在一个 for 循环中。 最终应得到以下 canvas_Draw 代码:
for (int i = 0; i < 100; i++)
{
    args.DrawingSession.DrawText("Hello, World!", RndPosition(), Color.FromArgb(255, RndByte(), RndByte(), RndByte()));
    args.DrawingSession.DrawCircle(RndPosition(), RndRadius(), Color.FromArgb(255, RndByte(), RndByte(), RndByte()));
    args.DrawingSession.DrawLine(RndPosition(), RndPosition(), Color.FromArgb(255, RndByte(), RndByte(), RndByte()));
}
  1. 再次运行应用。 你应该会看到一大堆文本、线条和圆圈,它们具有随机的位置和大小。

将图像效果应用于内容

图像效果也称为筛选器效果,是应用于像素数据的图形转换。 饱和度、色调旋转和高斯模糊是一些常见的图像效果。 图像效果可以链接在一起,以最少的工作量生成复杂的视觉外观。

通过提供源图像(起始内容)、创建效果(如 GaussianBlurEffect)、设置属性(如 BlurAmount),然后使用 DrawImage 绘制效果的输出,从而使用图像效果。

若要将图像效果应用于文本和形状,需要先将内容呈现到 CanvasCommandList 中。 此对象可作为效果的输入使用。

  1. 更改 canvas_Draw 方法以使用以下代码:
CanvasCommandList cl = new CanvasCommandList(sender);

using (CanvasDrawingSession clds = cl.CreateDrawingSession())
{
    for (int i = 0; i < 100; i++)
    {
        clds.DrawText("Hello, World!", RndPosition(), Color.FromArgb(255, RndByte(), RndByte(), RndByte()));
        clds.DrawCircle(RndPosition(), RndRadius(), Color.FromArgb(255, RndByte(), RndByte(), RndByte()));
        clds.DrawLine(RndPosition(), RndPosition(), Color.FromArgb(255, RndByte(), RndByte(), RndByte()));
    }
}

就像从 CanvasDrawEventArgs 获取 CanvasDrawingSession 一样,可以从 CanvasCommandList 创建 CanvasDrawingSession。 唯一的区别是,在绘制到命令列表的绘图会话 (clds) 时,不会直接呈现到 CanvasControl。 相反,命令列表是一个中间对象,用于存储呈现结果以供以后使用。

你可能已注意到包装命令列表绘图会话的 using 块。 绘图会话实现 IDisposable,并且必须在完成呈现时释放(using 块执行此操作)。 从 CanvasDrawEventArgs 获取的 CanvasDrawingSession 会自动关闭,但必须释放显式创建的任何绘图会话。

  1. 最后,通过将以下代码添加到 canvas_Draw 方法的末尾来定义 GaussianBlurEffect
GaussianBlurEffect blur = new GaussianBlurEffect();
blur.Source = cl;
blur.BlurAmount = 10.0f;
args.DrawingSession.DrawImage(blur);
  1. 再次运行应用。 应看到外观模糊的线条、文本和圆圈。

使用 CanvasAnimatedControl 对应用进行动画处理

。 Win2D 使你能够实时更新内容并对其进行动画处理,例如,通过更改每个帧的高斯模糊的模糊半径。 为此,你将使用 CanvasAnimatedControl

CanvasControl 最适用于大多数静态图形内容 - 它仅在内容需要更新或重新绘制时引发 Draw 事件。 如果内容不断更改,则应考虑改用 CanvasAnimatedControl。 这两个控件的操作非常相似,只是 CanvasAnimatedControl 定期引发 Draw 事件。默认情况下,每秒调用 60 次。

  1. 若要切换到 CanvasAnimatedControl,请转到 MainPage.xaml,删除行 CanvasControl,并将其替换为以下 XAML
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <canvas:CanvasAnimatedControl x:Name="canvas" Draw="canvas_DrawAnimated" CreateResources="canvas_CreateResources"/>
</Grid>

就像使用 CanvasControl 一样,让自动完成为你创建 Draw 事件处理程序。 默认情况下,Visual Studio 将命名此处理程序 canvas_Draw_1,因为 canvas_Draw 已存在。此处,我们重命名了 canvas_AnimatedDraw 方法,以明确这是不同的事件。

此外,还处理新事件 CreateResources。 再次让自动完成创建处理程序。

现在,应用将以每秒 60 帧的速度重新绘制,创建一次 Win2D 视觉资源并在每个帧中重复使用它们会更高效。 创建 CanvasCommandList 并在内容保持静态时每秒将 300 个元素绘制到其中 60 次,效率低下。 CreateResources 是仅在 Win2D 确定需要重新创建视觉资源(例如加载页面时)时触发的事件。

  1. 切换回 MainPage.xaml.cs。 查找 canvas_Draw 方法应如下所示:
private void canvas_Draw(
    Microsoft.Graphics.Canvas.UI.Xaml.CanvasControl sender,
    Microsoft.Graphics.Canvas.UI.Xaml.CanvasDrawEventArgs args)
{
    CanvasCommandList cl = new CanvasCommandList(sender);
    using (CanvasDrawingSession clds = cl.CreateDrawingSession())
    {
        for (int i = 0; i < 100; i++)
        {
            clds.DrawText("Hello, World!", RndPosition(), Color.FromArgb(255, RndByte(), RndByte(), RndByte()));
            clds.DrawCircle(RndPosition(), RndRadius(), Color.FromArgb(255, RndByte(), RndByte(), RndByte()));
            clds.DrawLine(RndPosition(), RndPosition(), Color.FromArgb(255, RndByte(), RndByte(), RndByte()));
        }
    }

    GaussianBlurEffect blur = new GaussianBlurEffect();
    blur.Source = cl;
    blur.BlurAmount = 10.0f;
    args.DrawingSession.DrawImage(blur);
}

此现有绘图代码的绝大多数不需要在每个帧中执行:包含文本、线条和圆圈的命令列表在每个帧中保持不变,唯一发生更改的是模糊半径。 因此,可以将此“静态”代码移动到 CreateResources

为此,首先剪切(CTRL+X) canvas_Draw 的全部内容,除了最后一行(args.DrawingSession.DrawImage(blur);)。 现在可以删除 canvas_Draw 的其余部分,因为不再需要它:回想一下,CanvasAnimatedControl 具有其自己的不同 Draw 事件。

  1. 查找自动生成 canvas_CreateResources 的方法:
private void canvas_CreateResources(
    Microsoft.Graphics.Canvas.UI.Xaml.CanvasAnimatedControl sender, 
    Microsoft.Graphics.Canvas.UI.CanvasCreateResourcesEventArgs args)
{}

将 (CTRL+V) 之前剪切的代码粘贴到此方法中。 接下来,将 GaussianBlurEffect 的声明移到方法主体之外,使变量成为 MainPage 类的成员。 代码现在应如下所示:

GaussianBlurEffect blur;
private void canvas_CreateResources(
    Microsoft.Graphics.Canvas.UI.Xaml.CanvasAnimatedControl sender,
    Microsoft.Graphics.Canvas.UI.CanvasCreateResourcesEventArgs args)
{
    CanvasCommandList cl = new CanvasCommandList(sender);
    using (CanvasDrawingSession clds = cl.CreateDrawingSession())
    {
        for (int i = 0; i < 100; i++)
        {
            clds.DrawText("Hello, World!", RndPosition(), Color.FromArgb(255, RndByte(), RndByte(), RndByte()));
            clds.DrawCircle(RndPosition(), RndRadius(), Color.FromArgb(255, RndByte(), RndByte(), RndByte()));
            clds.DrawLine(RndPosition(), RndPosition(), Color.FromArgb(255, RndByte(), RndByte(), RndByte()));
        }
    }

    blur = new GaussianBlurEffect()
    {
        Source = cl,
        BlurAmount = 10.0f
    };
}
  1. 现在,你可以对高斯模糊进行动画处理。 找到 canvas_DrawAnimated 方法并添加以下代码:
private void canvas_DrawAnimated(
    Microsoft.Graphics.Canvas.UI.Xaml.ICanvasAnimatedControl sender,
    Microsoft.Graphics.Canvas.UI.Xaml.CanvasAnimatedDrawEventArgs args)
{
    float radius = (float)(1 + Math.Sin(args.Timing.TotalTime.TotalSeconds)) * 10f;
    blur.BlurAmount = radius;
    args.DrawingSession.DrawImage(blur);
}

这会读取提供 CanvasAnimatedDrawEventArgs 的总已用时间,并使用它来计算所需的模糊量;正弦函数会随时间推移提供一个有趣的变化。 最后,呈现 GaussianBlurEffect

  1. 运行应用以查看模糊内容随时间推移的变化。

祝贺你完成本快速入门教程! 希望你已了解如何使用 Win2D 通过几行 C# 和 XAML 代码创建丰富的动画视觉场景。