本教程介绍 Win2D 的一些基本绘图功能。 你将了解如何:
- 将 Win2D 添加到 C# XAML Windows 项目。
- 绘制文本和几何图形。
- 应用滤镜效果。
- 对 Win2D 内容进行动画处理。
- 遵循 Win2D 最佳做法。
设置您的开发计算机
请确保使用所有必要的工具设置计算机:
-
安装 Visual Studio
- 请根据您的需求包括 UWP 或 Windows SDK,版本要求为 17763 及以上。
- 如果使用 UWP 平台,请务必同时 启用开发人员模式
创建新的 Win2D 项目
按照 Win2D“Hello, World!” 快速入门 中的步骤使用 Win2D 创建新项目,并添加对 Win2D NuGet 包的引用。 可以使用 WinUI 3(Windows 应用 SDK)或通用 Windows 平台(UWP)。
将 Win2D CanvasControl 添加到应用的 XAML
- 若要使用 Win2D,需要在某个位置绘制图形。 在 XAML 应用中,执行此作的最简单方法是将 CanvasControl 添加到 XAML 页面。
在继续之前,请先确保项目的“体系结构”选项设置为“x86
”或“x64
”及“”,而不是“”至“Any CPU
”。 Win2D 在C++中实现,因此使用 Win2D 的项目需要面向特定的 CPU 体系结构。
通过在解决方案资源管理器中双击项目中的
MainPage.xaml
来导航。 这将打开该文件。 为方便起见,可以在“设计器”选项卡中双击 XAML 按钮;这将隐藏视觉对象设计器并保留代码视图的所有空间。在添加控件之前,首先必须告诉 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">
- 现在,将一个新
canvas:CanvasControl
作为子元素添加到根 Grid 元素。 为控件指定一个名称,例如“canvas”。 XAML 现在应如下所示:
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<canvas:CanvasControl x:Name="canvas"/>
</Grid>
- 接下来,定义 Draw 事件的事件处理程序。 只要应用需要绘制或重新绘制其内容,CanvasControl 就引发
Draw
。 最简单的方法是让 Visual Studio AutoComplete 为你提供帮助。 在 CanvasControl 定义中,开始键入Draw
事件处理程序的新属性:
<canvas:CanvasControl x:Name="canvas" Draw="canvas_Draw" />
注释
输入 Draw="
后,Visual Studio 应弹出一个框,提示你自动填写事件处理程序的正确定义。 按 Tab 接受 Visual Studio 的默认事件处理程序。 这还会在后台代码(《MainPage.xaml.cs》)中自动添加格式正确的事件处理程序方法。 如果不使用自动完成,请不要担心;可以在下一步中手动添加事件处理程序方法。
在 Win2D 中绘制第一个文本
现在,让我们转到 C# 后台代码。 从解决方案资源管理器打开
MainPage.xaml.cs
。C# 文件的顶部是各种命名空间定义。 添加以下命名空间:
using Windows.UI;
using System.Numerics;
using Microsoft.Graphics.Canvas;
using Microsoft.Graphics.Canvas.Effects;
- 接下来,应会看到由 AutoComplete 插入的以下空白事件处理程序:
private void canvas_Draw(
Microsoft.Graphics.Canvas.UI.Xaml.CanvasControl sender,
Microsoft.Graphics.Canvas.UI.Xaml.CanvasDrawEventArgs args)
{
}
(如果上一步未使用自动完成,请立即添加此代码。
- CanvasDrawEventArgs 参数公开一个成员 DrawingSession,该成员的类型为 CanvasDrawingSession。 此类提供 Win2D 中的大多数基本绘图功能:它具有 CanvasDrawingSession.DrawRectangle、 CanvasDrawingSession.DrawImage 和绘制文本所需的方法 CanvasDrawingSession.DrawText 等方法。
将以下代码添加到 canvas_Draw
方法中:
args.DrawingSession.DrawText("Hello, World!", 100, 100, Colors.Black);
第一个参数 "Hello, World!"
是希望 Win2D 显示的字符串。 两个“100”指示 Win2D 将此文本向右和向下偏移 100 个 DIP(设备无关像素)。 最后,Colors.Black
定义文本的颜色。
- 现在,你已准备好运行第一个 Win2D 应用。 按 F5 键进行编译和启动。 你应该会看到一个空的窗口,其中“Hello, world!”显示为黑色。
正确释放 Win2D 资源
- 在继续绘制其他类型的内容之前,首先应添加一些代码,以确保应用避免内存泄漏。 大多数以 .NET 语言编写的 Win2D 应用程序以及使用 像 CanvasControl 这样的 Win2D 控件需要执行以下步骤。 严格地说,你的简单“Hello, world”应用不受影响,但一般情况下,这是一个很好的做法。
有关详细信息,请参阅 避免内存泄漏。
打开
MainPage.xaml
并查找包含 CanvasControl 的 Page XAML 元素。 它应该是文件中的第一个元素。为
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">
- 转到
MainPage.xaml.cs
并找到Page_Unloaded
事件处理程序。 添加以下代码:
void Page_Unloaded(object sender, RoutedEventArgs e)
{
this.canvas.RemoveFromVisualTree();
this.canvas = null;
}
- 如果应用包含多个 Win2D 控件,则需要对包含 Win2D 控件的每个 XAML 页面重复上述步骤。 你的应用当前只有一个 CanvasControl,因此你已经全部完成了。
绘制一些形状
- 向应用添加 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)和颜色(红色)定义。
- 现在,按 F5 运行应用。 应会看到“你好,世界!” 以及绿色圆圈和红线。
你可能想知道如何控制更高级的绘图选项,如线条粗细和短划线,或更复杂的填充选项,例如使用画笔。 Win2D 提供了所有这些选项和更多选项,并在需要时轻松使用它们。
Draw(...)
所有方法都提供了许多重载,这些重载可以接受其他参数,例如 CanvasTextFormat(字体系列、大小等)和 CanvasStrokeStyle(短划线、点、尾帽等)。 可以随意浏览 API 图面,了解有关这些选项的详细信息。
动态生成绘图参数
- 现在,让我们通过绘制一堆带有随机颜色的形状和文本来添加一些多样性。
将以下代码添加到 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);
}
- 修改
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
。 最后,Color.FromArgb
允许你使用 A、R、G 和 B 值定义颜色,而不是使用预定义的颜色。 A指的是alpha或不透明度级别;在这种情况下,您始终需要选择完全不透明(255)。
DrawCircle
和 DrawLine
的工作方式与 DrawText
类似。
- 最后,将绘图代码包裹在
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()));
}
- 再次运行应用。 你应该会看到一大堆随机的文本、线条和圆圈,它们的位置和大小都是随机的。
将图像效果应用于内容
图像效果(也称为筛选器效果)是应用于像素数据的图形转换。 饱和度、色调旋转和高斯模糊是一些常见的图像效果。 图像效果可以链接在一起,从而产生复杂的视觉外观,以尽量减少工作量。
通过提供源图像(起始内容)、创建效果(如 GaussianBlurEffect)、设置 BlurAmount 等属性,然后使用该效果的输出 DrawImage
来使用图像效果。
若要将图像效果应用于文本和形状,需要首先将该内容呈现到 CanvasCommandList 中。 该对象可以用作您效果的输入。
- 更改
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()));
}
}
就像从 CanvasDrawingSession
获取 CanvasDrawEventArgs
并可以进行绘画一样,你可以从 CanvasDrawingSession
创建 CanvasCommandList
。 唯一的区别是,在绘制到属于命令列表的绘图会话 (CLDS) 时,不会直接呈现到 CanvasControl。 相反,命令列表是一个中间对象,用于存储呈现结果以供以后使用。
你可能已经注意到 using
块是用于封装命令列表的绘图会话的。 绘图会话实现 IDisposable ,并且必须在完成渲染后进行处置( using
块负责执行这一步)。 自动为你关闭从 CanvasDrawingSession
获取的 CanvasDrawEventArgs
,但你必须处置任何显式创建的绘图会话。
- 最后,通过将以下代码添加到
GaussianBlurEffect
方法的末尾来定义canvas_Draw
:
GaussianBlurEffect blur = new GaussianBlurEffect();
blur.Source = cl;
blur.BlurAmount = 10.0f;
args.DrawingSession.DrawImage(blur);
- 再次运行应用。 你应该能看到线条、文本和圆圈呈现出模糊的外观。
使用 CanvasAnimatedControl 对应用进行动画处理
. Win2D 使你能够实时更新并为内容添加动画效果,例如,通过在每一帧中更改高斯模糊的模糊半径范围。 为此,你将使用 CanvasAnimatedControl。
CanvasControl 最适合大多数静态图形内容 - 它仅在需要更新或重绘内容时引发 Draw
事件。 如果不断更改内容,则应考虑改用 CanvasAnimatedControl
。 这两个控件的工作方式非常相似,除了 CanvasAnimatedControl
定期触发 Draw
事件外,默认情况下,它被调用每秒 60 次。
- 若要切换到
CanvasAnimatedControl
,请转到MainPage.xaml
,删除 CanvasControl 行,并将其替换为以下 XAML:
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<canvas:CanvasAnimatedControl x:Name="canvas" Draw="canvas_DrawAnimated" CreateResources="canvas_CreateResources"/>
</Grid>
就像使用 CanvasControl 一样,让 AutoComplete 为你创建 Draw
事件处理程序。 默认情况下,Visual Studio 会将此处理程序命名为 canvas_Draw_1
,因为 canvas_Draw
已存在;在这里,我们重命名了方法 canvas_AnimatedDraw
,以表明这是一个不同的事件。
此外,您还需要处理一个新事件 CreateResources。 再次让 AutoComplete 创建处理程序。
现在,你的应用将重新绘制为每秒 60 帧,因此创建一次 Win2D 视觉资源并重复使用每个帧会更高效。 创建 CanvasCommandList
并在内容保持静态时每秒将 300 个元素绘制到其中 60 次是效率低下。
CreateResources
是一个事件,只有在 Win2D 确定需要重新创建视觉资源时(例如加载页面时),才会触发该事件。
- 切换回
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
事件。
- 查找自动生成的
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
};
}
- 现在,你可以对高斯模糊进行动画处理。 找到
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
被渲染。
- 运行应用以查看随时间变化的模糊内容。
祝贺你完成本快速入门教程! 希望你已了解如何使用 Win2D 创建一个丰富的动画视觉场景,只需几行 C# 和 XAML 代码即可。