[UWP]用Win2D實現鏤空文字

  • 2019 年 11 月 27 日
  • 筆記

1. 前言

之前用PointLight做了一個番茄鍾,效果還不錯,具體可見這篇文章:

[UWP]使用PointLight並實現動畫效果

後來試玩了Win2D,這次就用Win2D實現文字的鏤空效果,配合PointLight做一個內斂不張揚的番茄鍾。

實現鏤空文字的核心思想是使用CanvasGeometry.CreateText從TextLayout獲取一個Geometry,然後使用DrawGeometry將它畫到DrawingSurface。這篇文章介紹了具體的實現步驟。

2. 參考例子

Win2D Gallery提供了大量Win2D的Sample,這次就參考了其中的文字鏤空效果例子,地址和運行效果如下:

https://github.com/microsoft/Win2D-Samples/blob/master/ExampleGallery/TextOutlines.xaml.cs

3. 實現步驟

Sample的程式碼量雖多,其實核心並不複雜,下面講講需要用到的API:

3.1 CanvasDevice.GetSharedDevice

因為要用到Win2D,所以首先要引用Win2D.uwp nuget包。因為我的目標不是輸出到CanvasControl上,而是想要輸出到一個SpriteVisual上,所以使用CanvasDevice

var canvasDevice = CanvasDevice.GetSharedDevice();

3.2 CanvasComposition.CreateCompositionGraphicsDevice

然後創建一個Compositor,並將這個Compositor和CanvasDevice關聯起來,這裡需要使用 CanvasComposition 創建 GraphicsDevice

var compositor = ElementCompositionPreview.GetElementVisual(this).Compositor;  var graphicsDevice = CanvasComposition.CreateCompositionGraphicsDevice(compositor, canvasDevice);

3.3 CompositionGraphicsDevice.CreateDrawingSurface

然後使用CompositionGraphicsDevice.CreateDrawingSurface創建一個CompositionDrawingSurface對象,它是用來繪畫內容的表面:

var drawingSurface = graphicsDevice.CreateDrawingSurface(e.NewSize, DirectXPixelFormat.B8G8R8A8UIntNormalized, DirectXAlphaMode.Premultiplied);

3.4 Compositor.CreateSurfaceBrush

使用Compositor.CreateSurfaceBrush創建一個CompositionSurfaceBrush,它的作用是使用像素繪製SpriteVisual,簡單來說它就是一張點陣圖,然後輸出到SpriteVisual上:

var maskSurfaceBrush = compositor.CreateSurfaceBrush(drawingSurface);  spriteTextVisual.Brush = maskSurfaceBrush;

3.5 CanvasComposition.CreateDrawingSession

有了CompositionDrawingSurface就可以為所欲為了,將這個DrawingSurface作為參數,調用CanvasComposition.CreateDrawingSession創建DrawingSession,DrawingSession提供了多個函數,可以自由地在DrawingSurface上畫文字、形狀、圖片甚至SVG。

using (var session = CanvasComposition.CreateDrawingSession(drawingSurface))  {    }

3.6 CanvasTextFormat和CanvasTextLayout

要再DrawingSurface上寫字,需要CanvasTextLayout,而CanvasTextLayout中的文字大小、格式等則由CanvasTextFormat定義:

using (var textFormat = new CanvasTextFormat()  {      FontSize = (float)FontSize,      Direction = CanvasTextDirection.LeftToRightThenTopToBottom,      VerticalAlignment = CanvasVerticalAlignment.Center,      HorizontalAlignment = CanvasHorizontalAlignment.Center,    })  {      using (var textLayout = new CanvasTextLayout(session, Text, textFormat, width, height))      {          Color fontColor = FontColor;          session.DrawTextLayout(textLayout, 0, 0, fontColor);      }  }

3.7 CanvasGeometry.CreateText

因為我的目標是鏤空的文字,所以不能直接使用DrawTextLayout。這裡需要使用CanvasGeometry.CreateText從TextLayout獲取一個Geometry,然後使用DrawGeometry將它畫到DrawingSurface。CanvasStrokeStyle是可選的,它控制邊框的虛線。

using (var textGeometry = CanvasGeometry.CreateText(textLayout))  {      var dashedStroke = new CanvasStrokeStyle()      {          DashStyle = DashStyle      };      session.DrawGeometry(textGeometry, OutlineColor, (float)StrokeWidth, dashedStroke);  }

4. 封裝為控制項

將上面的程式碼總結一下,封裝為一個OutlineTextControl 控制項,它提供了Text、OutlineColor、FontColor等屬性,在控制項SizeChanged時,或者各個屬性改變時調用DrawText重新在CompositionDrawingSurface上繪製文字。程式碼大致如下:

public class OutlineTextControl : Control  {      private CompositionDrawingSurface _drawingSurface;        public OutlineTextControl()      {          var compositor = ElementCompositionPreview.GetElementVisual(this).Compositor;          var graphicsDevice = CanvasComposition.CreateCompositionGraphicsDevice(compositor, CanvasDevice.GetSharedDevice());          var spriteTextVisual = compositor.CreateSpriteVisual();            ElementCompositionPreview.SetElementChildVisual(this, spriteTextVisual);          SizeChanged += (s, e) =>          {              _drawingSurface = graphicsDevice.CreateDrawingSurface(e.NewSize, DirectXPixelFormat.B8G8R8A8UIntNormalized, DirectXAlphaMode.Premultiplied);              DrawText();              var maskSurfaceBrush = compositor.CreateSurfaceBrush(_drawingSurface);              spriteTextVisual.Brush = maskSurfaceBrush;              spriteTextVisual.Size = e.NewSize.ToVector2();          };          RegisterPropertyChangedCallback(FontSizeProperty, new DependencyPropertyChangedCallback((s, e) =>          {              DrawText();          }));      }          private void DrawText()      {          if (ActualHeight == 0 || ActualWidth == 0 || string.IsNullOrWhiteSpace(Text) || _drawingSurface == null)              return;            var width = (float)ActualWidth;          var height = (float)ActualHeight;          using (var session = CanvasComposition.CreateDrawingSession(_drawingSurface))          {              session.Clear(Colors.Transparent);              using (var textFormat = new CanvasTextFormat()              {                  FontSize = (float)FontSize,                  Direction = CanvasTextDirection.LeftToRightThenTopToBottom,                  VerticalAlignment = CanvasVerticalAlignment.Center,                  HorizontalAlignment = CanvasHorizontalAlignment.Center,                })              {                  using (var textLayout = new CanvasTextLayout(session, Text, textFormat, width, height))                  {                      if (ShowNonOutlineText)                      {                          session.DrawTextLayout(textLayout, 0, 0, FontColor);                      }                        using (var textGeometry = CanvasGeometry.CreateText(textLayout))                      {                          var dashedStroke = new CanvasStrokeStyle()                          {                              DashStyle = DashStyle                          };                          session.DrawGeometry(textGeometry, OutlineColor, (float)StrokeWidth, dashedStroke);                      }                  }              }          }      }    //SOME CODE AND PROPERTIES    }

5. 結語

文章開頭的那個番茄鍾源碼可以在這裡查看:

OnePomodoro_OutlineTextView.xaml at master

也可以安裝我的番茄鍾應用試玩一下,安裝地址:

一個番茄鍾

6. 參考

CanvasComposition Class

CanvasDrawingSession Class

CanvasGeometry Class

CompositionGraphicsDevice Class (Windows.UI.Composition) – Windows UWP applications _ Microsoft Docs

CompositionDrawingSurface Class (Windows.UI.Composition) – Windows UWP applications _ Microsoft Docs

CompositionGraphicsDevice Class (Windows.UI.Composition) – Windows UWP applications _ Microsoft Docs

CompositionSurfaceBrush Class (Windows.UI.Composition) – Windows UWP applications _ Microsoft Docs