SkiaSharp跨平台繪圖研究1-WPF桌面應用
背景
Skia首頁、文檔和下載 - Google 圖形處理引擎 - OSCHINA - 中文開源技術交流社區
skia是個2D向量圖形處理函數庫,包含字型、座標轉換,以及點陣圖都有高效能且簡潔的表現。不僅用於Google Chrome瀏覽器,新興的Android開放手機平台也采用skia作為繪圖處理,搭配OpenGL/ES與特定的硬體特征,強化顯示的效果
自2005年Skia被Google收購后,一直相當神秘低調,直到2007年初,Skia GL相關的程式碼才被揭露,作為Google Android平台的圖形引擎,稍候的Google Chrome瀏覽器也采用Skia引擎。隨着Android與Chrome (開放版本稱為"Chromium")兩大專案公布程式碼后,skia也一並公開原始程式碼,以Apache License v2釋出(注意,這意味着與GPLv2授權不相容) ,而Android與Chrome的程式碼庫中都有一份[skia]的復制,因需求不同,做了部份的修改,比方說Chrome專案底下的 [chrome/trunk/src/skia],需要注意的是,Skia本身是不涉及底層環境,如Linux Framebuffer或Gtk+銜接的處理,這也是何以Android (透過Linux Framebuffer)與Chrome (開發中的Linux版本使用Gtk+)需要提供一份修改,以便系統接軌。
SkiaSharp首頁、文檔和下載 - .NET 平台的跨平台 2D 圖形 API - OSCHINA - 中文開源技術交流社區
SkiaSharp 是基於 Google Skia Graphics Library 的 .NET 平台的跨平台 2D 圖形 API。可用於移動設備,服務器和桌面設備渲染圖像。
SkiaSharp 目前適用於一下平台:
l .NET Standard 1.3
l .NET Core
l Tizen
l Xamarin.Android
l Xamarin.iOS
l Xamarin.tvOS
l Xamarin.watchOS
l Xamarin.Mac
l Windows Classic Desktop (Windows.Forms / WPF)
l Windows UWP (Desktop / Mobile / Xbox / HoloLens)
SkiaSharp是一個強大跨平台繪圖框架,我曾經用SkiaSharp在WPF、安卓Xamarin.Forms客戶端繪圖,也用來創建過PDF繪圖,但是由於它不支持網頁繪圖,所以總覺得很遺憾,因為目前主流的瀏覽器都是谷歌Chrom內核,谷歌為什么不支持自家的Skia在網頁直接繪圖呢?如果SkiaSharp可以直接在網頁繪圖,那它就是跨越全平台的繪圖框架了。
終於到了2021年10月12日,.NET 6發布RC2候選版本(正式發布前最后一版),宣布了一個突破性的技術:支持在Web網頁上采用SkiaSharp畫布繪圖。這是.NET跨平台技術發展的一個創舉,使用C#可以直接在網頁畫布上繪圖,打破了JavaScript+canvas的長期壟斷地位。C#是強類型語言,可以無縫對接從服務端獲取的結構化數據,有效提高開發效率和質量。
ASP.NET Core updates in .NET 6 Release Candidate 2 - ASP.NET Blog (microsoft.com)
SkiaSharp is a cross-platform 2D graphics library for .NET based on the native Skia graphics library, and it now has preview support for Blazor WebAssembly. Let’s give it a try!
受此鼓舞,決定寫一個系列文章,匯總SkiaSharp在各個平台上的繪圖方法。
創建WPF項目
使用VS2022 prewview6.0工具,其實對於WPF項目而言,VS2019完全足夠的。
NuGet安裝SkiaSharp.Views.WPF,當前最新版本是2.88.0-preview.152
簡單地添加一個按鈕和SkiaSharp畫布容器。
<Window x:Class="WpfDemo.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:WpfDemo" xmlns:skia="clr-namespace:SkiaSharp.Views.WPF;assembly=SkiaSharp.Views.WPF" mc:Ignorable="d" Title="MainWindow" Height="400" Width="450"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="*" /> </Grid.RowDefinitions> <Button x:Name="btnRefresh" Content="繪圖" Grid.Row="0" Width="100" Margin="4"></Button> <Grid Grid.Row="1" Margin="4" Height="300"> <skia:SKElement x:Name="skContainer"> </skia:SKElement> </Grid> </Grid> </Window>
編寫一點代碼
public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); btnRefresh.Click += BtnRefresh_Click; skContainer.PaintSurface += SkContainer_PaintSurface; } private void BtnRefresh_Click(object sender, RoutedEventArgs e) { Debug.WriteLine($"{DateTimeOffset.Now}, 刷新畫布"); //強制畫布重繪,有些開發環境InvalidateVisual不會觸發調用OnRender skContainer.InvalidateVisual(); } private void SkContainer_PaintSurface(object? sender, SkiaSharp.Views.Desktop.SKPaintSurfaceEventArgs e) { var canvas = e.Surface.Canvas; canvas.Clear(SKColors.SkyBlue); using var paint = new SKPaint { Color = SKColors.Black, IsAntialias = true, TextSize = 24 }; string msg = $"{DateTimeOffset.Now:T}, 還有1萬行Skia繪圖代碼..."; canvas.DrawText(msg, 0, 30, paint); using var linePaint = new SKPaint() { Color = (DateTimeOffset.Now.Second % 4 <= 1) ? SKColors.Red : SKColors.Green, Style = SKPaintStyle.Stroke,//不填充 StrokeWidth = 3, }; canvas.DrawRect(10, 50, e.Info.Width - 20, e.Info.Height - 60, linePaint); msg += $", linePaint.Color={linePaint.Color}, skContainer.CanvasSize={skContainer.CanvasSize}"; Debug.WriteLine(msg); } }
測試SkiaSharp繪圖
測試發現,點擊繪圖按鈕,畫布內容並沒有更新!我以前用得好好的,怎么突然就不行了?在另外一台電腦上用VS2019創建了一個net 5.0的WPF桌面軟件,還是用上述代碼,運行完全沒問題!那么就是開發環境的問題,老司機都知道,Windows的開發環境對桌面軟件有一些匪夷所思的影響,這也是微軟桌面開發環境帶給.Net開發者多年的痛苦。
運行效果正常的開發環境
dotnet --info
.NET SDK (反映任何 global.json):
Version: 5.0.300
Commit: 2e0c8c940e
運行時環境:
OS Name: Windows
OS Version: 10.0.18363
OS Platform: Windows
RID: win10-x64
Base Path: C:\Program Files\dotnet\sdk\5.0.300\
Host (useful for support):
Version: 5.0.6
Commit: 478b2f8c0e
.NET SDKs installed:
2.2.402 [C:\Program Files\dotnet\sdk]
5.0.102 [C:\Program Files\dotnet\sdk]
5.0.300 [C:\Program Files\dotnet\sdk]
.NET runtimes installed:
Microsoft.AspNetCore.All 2.1.28 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
Microsoft.AspNetCore.All 2.2.7 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
Microsoft.AspNetCore.App 2.1.28 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 2.2.7 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 3.1.15 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 5.0.6 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.NETCore.App 2.1.28 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 2.2.7 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 3.1.15 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 5.0.6 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.WindowsDesktop.App 3.1.15 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 5.0.6 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
To install additional .NET runtimes or SDKs:
https://aka.ms/dotnet-download
運行效果異常的開發環境
dotnet --info
.NET SDK (反映任何 global.json):
Version: 6.0.100-rc.2.21505.57
Commit: ab39070116
運行時環境:
OS Name: Windows
OS Version: 10.0.19042
OS Platform: Windows
RID: win10-x64
Base Path: C:\Program Files\dotnet\sdk\6.0.100-rc.2.21505.57\
Host (useful for support):
Version: 6.0.0-rc.2.21480.5
Commit: 6b11d64e7e
.NET SDKs installed:
5.0.402 [C:\Program Files\dotnet\sdk]
6.0.100-rc.2.21505.57 [C:\Program Files\dotnet\sdk]
.NET runtimes installed:
Microsoft.AspNetCore.App 3.1.20 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 5.0.11 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 6.0.0-rc.2.21480.10 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.NETCore.App 3.1.20 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 5.0.11 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 6.0.0-rc.2.21480.5 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.WindowsDesktop.App 3.1.20 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 5.0.11 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 6.0.0-rc.2.21501.6 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
To install additional .NET runtimes or SDKs:
https://aka.ms/dotnet-download
對於異常的開發環境,可以通過改變窗口大小,強制引發重繪,看到畫布內容刷新。
解決中文顯示問題
SkiaSharp要創建中文字體,才能顯示中文,這也是個麻煩,為什么不能自動從操作系統獲取Unicode字庫呢?為了減少安裝包體積,選用體積小的DroidSansFallback.ttf字庫文件。
/// <summary> /// Skia中文字體 /// </summary> public static class SkiaChinaFont { public static SKTypeface ChinaFont { get; private set; } static SkiaChinaFont() { //加載字體資源文件方案,需要把字體文件復制運行目錄下,設置文件屬性為如果較新則復制 string fontPath = Path.Combine(AppContext.BaseDirectory, "DroidSansFallback.ttf"); ChinaFont = SKTypeface.FromFile(fontPath); } }
顯示文字代碼增加中文字體即可
using var paint = new SKPaint { Color = SKColors.Black, IsAntialias = true, Typeface = SkiaChinaFont.ChinaFont, TextSize = 24 }; string msg = $"{DateTimeOffset.Now:T}, 還有1萬行Skia繪圖代碼..."; canvas.DrawText(msg, 0, 30, paint);
最終結果如圖
DEMO源代碼參見:https://gitee.com/woodsun/skia-sharp-demo