win10 uwp 通過 Win2d 完全控制筆跡繪製邏輯

lindexi發表於2021-09-01

本文來告訴大家如何通過 Win2d 完全控制筆跡繪製邏輯,本文適合用來實現複雜的自定義邏輯,可以完全控制筆跡的行為。包括在書寫過程中切換模式,如進行手勢擦除切換為橡皮擦模式

本文提供的方法適合用來做複雜的自定義,本文的方法的優點也是缺點。優點是啥都可以自己控制,缺點是啥都需要自己控制。需要自己處理筆跡的多筆同步問題,處理筆跡的長筆跡分段問題,處理筆跡的繪製問題,處理動態筆跡切換

本文提供的方法依然可以實現非常高效能的筆跡,比 WPF 最快的筆跡實現還要快,但需要自己處理好各個部分的邏輯,如動態筆跡和靜態筆跡,筆跡分段等邏輯。本文提供的方法的效能依然不如只使用預設的 InkCanvas 快

介面

在開始之前,請先安裝 Win2d 庫,可參閱 win10 uwp win2d 入門 看這一篇就夠了 部落格瞭解如何安裝

在 XAML 介面上加上 xmlns:canvas="using:Microsoft.Graphics.Canvas.UI.Xaml" 名稱空間,用來匯入 Win2d 控制元件。介面如下

  <Grid Background="#565656">
    <canvas:CanvasControl x:Name="Canvas" Draw="Canvas_OnDraw"/>
    <InkCanvas x:Name="InkCanvas" />
  </Grid>

本文將使用一個 InkCanvas 放在 Win2d 的 CanvasControl 上層,讓 InkCanvas 作為快速的事件接收層,讓 Win2d 的 CanvasControl 作為實際的繪製層。其實,更好的介面框架是存放兩個 Win2d 的 CanvasControl 分別用來存放動態筆跡和靜態筆跡。如果自己實現的筆跡沒有分動態筆跡和靜態筆跡,那麼可以忽略,本文為了簡潔將不演示動態筆跡和靜態筆跡的處理

此時的介面邏輯如下

<Page
  x:Class="KeanearkallhawDaherenenallyi.MainPage"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:local="using:KeanearkallhawDaherenenallyi"
  xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
  xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
  mc:Ignorable="d"
  xmlns:canvas="using:Microsoft.Graphics.Canvas.UI.Xaml"
  Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">

  <Grid Background="#565656">
    <canvas:CanvasControl x:Name="Canvas" Draw="Canvas_OnDraw"/>
    <InkCanvas x:Name="InkCanvas" />
  </Grid>
</Page>

初始化筆跡接收

在建構函式初始化筆跡的接收邏輯,通過 InkCanvas 進行快速的事件接收

        private readonly InkSynchronizer _inkSynchronizer;

        public MainPage()
        {
            this.InitializeComponent();
            _inkSynchronizer = InkCanvas.InkPresenter.ActivateCustomDrying();
            InkCanvas.InkPresenter.SetPredefinedConfiguration(InkPresenterPredefinedConfiguration
                .SimpleMultiplePointer);
            InkCanvas.InkPresenter.InputDeviceTypes =
                CoreInputDeviceTypes.Touch | CoreInputDeviceTypes.Pen | CoreInputDeviceTypes.Mouse;
            InkCanvas.InkPresenter.UnprocessedInput.PointerMoved += UnprocessedInput_PointerMoved;
            InkCanvas.InkPresenter.InputProcessingConfiguration.Mode = InkInputProcessingMode.None;
            InkCanvas.InkPresenter.InputProcessingConfiguration.RightDragAction =
                InkInputRightDragAction.LeaveUnprocessed;
        }

以上的程式碼裡面,只是監聽了 UnprocessedInput 的 PointerMoved 事件,事實上需要監聽更多的事件用來了解筆跡的繪製開始和完成邏輯。本文為了方便演示,就不詳細寫所有邏輯

以上各個部分邏輯的含義,請參閱 win10 uwp 通過 win2d 畫出筆跡

收集筆跡

UnprocessedInput_PointerMoved 將是本文的核心邏輯,在這裡通過事件引數瞭解到當前是哪個手指或筆觸控,以及通過 InkStrokeBuilder 將輸入的點構造筆跡

        private void UnprocessedInput_PointerMoved(InkUnprocessedInput sender, Windows.UI.Core.PointerEventArgs args)
        {
            var id = args.CurrentPoint.PointerId;
            // 需要根據 id 分開多個手指

            InkStrokeBuilder.SetDefaultDrawingAttributes(new InkDrawingAttributes()
            {
                Color = Colors.Blue,
                Size = new Size(5, 5)
            });

            _currentPointerList.AddRange(args.GetIntermediatePoints());
            _inkStroke = InkStrokeBuilder.CreateStrokeFromInkPoints(
                _currentPointerList.Select(t => new InkPoint(t.Position, t.Properties.Pressure)), Matrix3x2.Identity);

            Canvas.Invalidate();
        }

        private readonly List<PointerPoint> _currentPointerList = new List<PointerPoint>();
        private InkStroke _inkStroke;

        private InkStrokeBuilder InkStrokeBuilder { get; } = new InkStrokeBuilder();

以上程式碼沒有編寫的部分是瞭解當前是由哪個 id 的裝置觸發的事件,如有多個手指在觸控,那麼不同的手指的 id 是不相同的。需要自己建立列表陣列進行處理

另外,通過 InkStrokeBuilder 的 CreateStrokeFromInkPoints 建立的 InkStroke 是需要預先在 SetDefaultDrawingAttributes 設定繪製屬性的,而不是在建立之後依然可以設定。另外上面程式碼只使用了一個 InkStroke 欄位,實際上需要根據當前是否有多指觸控的需求,使用列表存放多個筆跡

本文以上程式碼通過 CreateStrokeFromInkPoints 建立是不包含筆跡分段的,也就是說在使用者繪製一段長線,將會需要使用較多的計算資源建立筆跡。請在自己的產品邏輯裡面,手動分開為多個不同的筆跡段,用來提升效能

上面程式碼通過呼叫 CanvasControl 的 Invalidate 讓 Win2d 的畫布重新繪製。重新繪製會進入 Canvas_OnDraw 方法,將在此方法繪製出筆跡

繪製筆跡

繪製筆跡的方法十分簡單,呼叫 Win2d 的 DrawInk 方法傳入筆跡即可

        private void Canvas_OnDraw(CanvasControl sender, CanvasDrawEventArgs args)
        {
            if (_inkStroke != null)
            {
                args.DrawingSession.DrawInk(new []{_inkStroke});
            }
        }

為什麼在 Win2d 的設計裡面,是傳入陣列?原因是筆跡是需要分段的,多段筆跡可以一起繪製。另外,如果有筆跡分段,那麼邏輯上就需要額外的轉換為靜態筆跡的功能,大概就是將一段連續的多段筆跡合成一段筆跡的過程。建議繪製動態筆跡和靜態筆跡放在兩個 Win2d 的 CanvasControl 裡。這樣也能提升筆跡的動態繪製效能,因為筆跡在繪製的時候需要不斷呼叫 Win2d 的重新整理,如果此時重新整理的是一個只包含很少筆跡的動態筆跡層的畫布,那每次重新整理的效能就比較好

無限漫遊

如果需要做無限漫遊,可以使用 CanvasVirtualControl 做一個超級大的畫布,同時只畫出可見的範圍

使用時需要自己轉換座標,可以在 InkStrokeBuilder 的 CreateStrokeFromInkPoints 方法傳入縮放和平移的矩陣,此時建立出來的筆跡是包含了變換的

程式碼

本文所有程式碼放在githubgitee 歡迎訪問

可以通過如下方式獲取本文的原始碼,先建立一個空資料夾,接著使用命令列 cd 命令進入此空資料夾,在命令列裡面輸入以下程式碼,即可獲取到本文的程式碼

git init
git remote add origin https://gitee.com/lindexi/lindexi_gd.git
git pull origin d33e26640f0108ae6bb29b90e7b189a14a92d624

以上使用的是 gitee 的源,如果 gitee 不能訪問,請替換為 github 的源

git remote remove origin
git remote add origin https://github.com/lindexi/lindexi_gd.git

獲取程式碼之後,進入 KeanearkallhawDaherenenallyi 資料夾

參考

更多筆跡和觸控,請參閱 觸控相關

相關文章