建置簡單的 Win2D 應用程式
本教學課程會介紹 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``)。 如果您未使用 AutoComplete,請不要擔心;您可以在下一個步驟手動新增事件處理常式方法。
在 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)
{
}
(如果您在上一個步驟未使用 AutoComplete,現在請加入此程式碼。)
- 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 語言撰寫並且使用像 CanvasControl 這類 Win2D 控制項的 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) 和色彩 (Green) 所定義。 線條是由開始 (0, 0)、結尾 (50, 200) 和色彩 (Red)+ 所定義。
- 現在,按下 F5 執行應用程式。 您應該會看到 "Hello, world!" 伴隨一個綠色圓圈和一段紅色線條。
您可能想知道如何控制更進階的繪圖選項,例如線條粗細和虛線,或是更複雜的填滿選項,例如使用筆刷。 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 位移參數已取代為單一 System.Numerics.Vector2,此值是由 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()));
}
}
如同您從 CanvasDrawEventArgs
中取得 CanvasDrawingSession
來用以繪製,您也可以從 CanvasCommandList
中建立 CanvasDrawingSession
。 唯一的差別在於,當您繪製至命令清單的繪圖工作階段 (clds),您不會直接轉譯為 CanvasControl。 相對的,命令清單是中繼物件,可儲存轉譯結果以供後續使用。
您可能已經注意到,using
區塊包裝了命令清單的繪圖工作階段。 繪圖工作階段會實作 IDisposable,而且必須在完成轉譯時處置 (using
區塊做了這個工作)。 您自動從 CanvasDrawEventArgs
取得的 CanvasDrawingSession
會為您關閉,但您必須處置您明確建立的任何繪圖工作階段。
- 最後,將下列程式碼新增至
canvas_Draw
方法的結尾,以定義GaussianBlurEffect
:
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 視覺效果資源並在每個畫面重複使用,會更有效率。 當內容保持靜態時,每秒 60 次建立 CanvasCommandList
並繪製 300 個元素到其中,是沒有效率的。 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 程式碼,建立豐富的動畫視覺效果場景。