在.Net Core下,沒有可以支持跨平台的Drawing類庫,官網提供的Common.Drawing只能在Windows下使用,那么在.Net Core下該如何處理圖片呢?其實有很多第三方提供了解決方案,而我比較喜歡用的是Mono團隊提供的SkiaSharp,原因是穩定而且支持的也很好,性能上也還好。
一、SkiaSharp是什么?
1.Skia介紹
Skia是Google旗下的2D圖形處理庫,下面是援引百科中的詞條:
skia是個2D向量圖形處理函數庫,包含字型、坐標轉換,以及點陣圖都有高效能且簡潔的表現。不僅用於Google Chrome瀏覽器,新興的Android開放手機平台也采用skia作為繪圖處理,搭配OpenGL/ES與特定的硬件特征,強化顯示的效果。
Skia官網中是這樣介紹的:
Skia is an open source 2D graphics library which provides common APIs that work across a variety of hardware and software platforms. It serves as the graphics engine for Google Chrome and Chrome OS, Android, Mozilla Firefox and Firefox OS, and many other products.
2.SkiaSharp介紹
SkiaSharp故名思義,就是在.net下使用Skia API的庫,是SkiaSharp是由mono團隊開發並進行持續維護,至今已經多年了。目前的最新版本是1.68.1.1,當前支持.net下的:
- .NET Standard 1.3
- .NET Core
- Tizen
- Xamarin.Android
- Xamarin.iOS
- Xamarin.tvOS
- Xamarin.watchOS
- Xamarin.Mac
- Windows Classic Desktop (Windows.Forms / WPF)
- Windows UWP (Desktop / Mobile / Xbox / HoloLens)
SkiaSharp項目:https://github.com/mono/SkiaSharp
以下參考:Xamarin.Forms 中的 SkiaSharp Graphics
3.預備知識
SkiaSharp for Xamarin.Forms打包為NuGet軟件包。在Visual Studio或Visual Studio for Mac中創建Xamarin.Forms解決方案之后,可以使用NuGet包管理器搜索SkiaSharp.Views.Forms包並將其添加到解決方案中。
如果您的Xamarin.Forms應用程序以iOS為目標,請使用項目屬性頁將最低部署目標更改為iOS 8.0。
在任何使用SkiaSharp的C#頁面中,您都希望為SkiaSharp名稱空間包含using指令,該指令包含您將在圖形編程中使用的所有SkiaSharp類,結構和枚舉。您還需要針對Xamarin.Forms特定類的SkiaSharp.Views.Forms命名空間使用using指令。這是一個較小的名稱空間,最重要的類是SKCanvasView,該類派生自Xamarin.Forms View類,並承載您的SkiaSharp圖形輸出
注:SkiaSharp.Views.Forms命名空間還包含一個SKGLView類,該類派生自View,但使用OpenGL渲染圖形。 為簡單起見,本指南將自身限制為SKCanvasView,但使用SKGLView卻非常相似。
SkiaSharp繪圖基礎
您可以使用SkiaSharp繪制的一些最簡單的圖形是圓形,橢圓形和矩形。在顯示這些圖形時,您將了解SkiaSharp的坐標,大小和顏色。文本和位圖的顯示更為復雜,但是這些文章還介紹了這些技術。
SkiaSharp線和路徑
圖形路徑是一系列連接的直線和曲線。路徑可以是描邊,填充或兩者兼有。本文涵蓋了線條繪制的許多方面,包括筆划結束和連接以及虛線和虛線,但在曲線幾何形狀方面停滯不前。
SkiaSharp轉換
變換允許圖形對象均勻地平移,縮放,旋轉或傾斜。本文還說明了如何使用標准的3×3變換矩陣創建非仿射變換並將變換應用於路徑。
SkiaSharp曲線和路徑
通過向路徑對象添加曲線以及利用其他強大的路徑功能,繼續探索路徑。您將看到如何在簡潔的文本字符串中指定整個路徑,如何使用路徑效果以及如何深入研究路徑內部。
SkiaSharp位圖Bitmaps
位圖是與顯示設備的像素相對應的位的矩形陣列。本系列文章展示了如何加載,保存,顯示,創建,繪制,動畫化和訪問SkiaSharp位圖的位。
SkiaSharp特效
效果是會改變圖形正常顯示的屬性,包括線性和圓形漸變,位圖平鋪,混合模式,模糊等。
二、SkiaSharp Drawing Basics
1、繪制圓圈
including canvases(畫布) and paint objects(繪制對象)
一些對象:
SKCanvasView類似StackLayout等view。 可以將SKCanvasView與其他Xamarin.Forms View派生詞組合
PaintSurface事件處理程序是您繪制所有圖形的地方
SKImageInfo結構包含有關繪圖表面的信息,最重要的是其寬度和高度(以像素為單位)。
SKSurface對象代表繪圖表面本身,最重要屬性是SKCanvas類型的Canvas。此類是用於執行實際繪圖的圖形繪圖上下文,SKCanvas對象封裝了圖形狀態,其中包括圖形轉換和裁剪。
SKPaint對象只不過是圖形繪制屬性的集合,這些對象是輕量級的。 您可以像該程序一樣重復使用SKPaint對象,也可以為圖形屬性的各種組合創建多個SKPaint對象。 您可以在PaintSurface事件處理程序之外創建和初始化這些對象,也可以將它們另存為頁面類中的字段。
SKPaintStyle
The default is Fill 填充.,Stroke為 划線(輪廓),StrokeAndFill為 描邊線條,並用相同的顏色填充內部。
public partial class SimpleCirclePage : ContentPage { /// <summary> /// 繪制一個圓圈, /// 創建一個SKCanvasView對象來承載圖形,處理PaintSurface事件以及使用SKPaint對象來指定顏色和其他圖形屬性。 /// </summary> public SimpleCirclePage() { InitializeComponent(); Title = "Simple Circle"; //SKCanvasView類似StackLayout等view。 可以將SKCanvasView與其他Xamarin.Forms View派生詞組合 SKCanvasView canvasView = new SKCanvasView(); canvasView.WidthRequest = 100; canvasView.HeightRequest = 100; canvasView.PaintSurface += OnCanvasViewPaintSurface; Content = canvasView; } /// <summary> /// PaintSurface事件處理程序是您繪制所有圖形的地方。 程序運行時,可以多次調用此方法【例如旋轉屏幕】,因此它應保留重新創建圖形顯示所需的所有信息: /// </summary> /// <param name="sender"></param> /// <param name="args"></param> void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args) { //SKPaintSurfaceEventArgs 有兩個重要的屬性:Info和Surface //SKImageInfo結構包含有關繪圖表面的信息,最重要的是其寬度和高度(以像素為單位)。 //SKSurface對象代表繪圖表面本身。 在此程序中,繪圖表面是視頻顯示,但在其他程序中,SKSurface對象也可以表示您使用SkiaSharp進行繪圖的 Bitmaps位圖。 //SKSurface的最重要屬性是SKCanvas類型的Canvas。此類是用於執行實際繪圖的圖形繪圖上下文,SKCanvas對象封裝了圖形狀態,其中包括圖形轉換和裁剪。 SKImageInfo info = args.Info; SKSurface surface = args.Surface; SKCanvas canvas = surface.Canvas; //Clear方法使用透明顏色清除畫布。 重載使您可以為畫布指定背景色。 canvas.Clear(); //繪制一個半徑為100像素的圓。 圓的輪廓為紅色,圓的內部為藍色。 //因為此特定的圖形圖像包含兩種不同的顏色,所以該工作需要分兩個步驟完成。 第一步是繪制圓的輪廓,要指定線條的顏色和其他特征,請創建並初始化SKPaint對象: SKPaint paint = new SKPaint { Style = SKPaintStyle.Stroke, //划 線 Color = Color.Red.ToSKColor(), StrokeWidth = 50 //線的粗細,50像素 }; //相對於顯示表面的左上角指定坐標。 X坐標向右增加,Y坐標向下增加。 在討論圖形時,通常使用數學符號(x,y)表示一個點。 點(0,0)是顯示表面的左上角,通常稱為原點。 //DrawCircle的前兩個參數指示圓心的X和Y坐標。 將它們分配給顯示表面寬度和高度的一半,以將圓心置於顯示表面的中心。 第三個參數指定圓的半徑,最后一個參數是SKPaint對象。 canvas.DrawCircle(info.Width / 2, info.Height / 2, 200, paint); //要填充圓的內部,可以更改SKPaint對象的兩個屬性,然后再次調用DrawCircle。 paint.Style = SKPaintStyle.Fill; //填充 paint.Color = SKColors.Blue; canvas.DrawCircle(info.Width / 2, info.Height / 2, 170, paint); } }
2、與Xamarin.Forms集成
SkiaSharp圖形可以幾種方式與Xamarin.Forms的其余部分集成。
您可以在同一頁面上組合SkiaSharp畫布和Xamarin.Forms元素,甚至將Xamarin.Forms元素放置在SkiaSharp畫布的頂部:
在Xamarin.Forms中創建交互式SkiaSharp圖形的另一種方法是通過觸摸,通過輕按切換兩種方式繪制一個簡單的圓圈(無填充和有填充)。
示例:TapToggleFillPage類顯示如何響應用戶輸入來更改SkiaSharp圖形。
/// <summary> /// 單擊事件 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void TapGestureRecognizer_Tapped(object sender, EventArgs e) { showFill = !showFill; //通知畫布需要重新繪制自己 //對InvalidateSurface的調用有效地生成了對PaintSurface處理程序的調用 (sender as SKCanvasView).InvalidateSurface(); }
示例:ColorExplorepage 演示了如何將SkiaSharp圖形與其他Xamarin.Forms元素集成在一起【文字位於圖像上】,並演示了兩種在SkiaSharp中定義顏色的替代方法之間的區別。 靜態SKColor.FromHsl方法基於Hue-Saturation-Lightness模型創建一個SKColor值:
public static SKColor FromHsl (Single h, Single s, Single l, Byte a) public static SKColor FromHsv (Single h, Single s, Single v, Byte a)
在這兩種情況下,h參數的范圍都是0到360。s,l和v參數的范圍是0到100。a(字母或不透明度)參數的范圍是0到255。
3、像素和獨立於設備的單位
探索SkiaSharp坐標和Xamarin之間的差異。您可以獲取在兩個坐標系之間進行轉換的信息,還可以繪制填充特定區域的圖形:
默認情況下,SkiaSharp以像素為單位繪制,而Xamarin.Forms則以由基礎平台建立的與設備無關的單位作為坐標和尺寸。 (有關Xamarin.Forms坐標系的更多信息,請參見第5章。)
示例:SurfaceSizePage使用SkiaSharp文本輸出來顯示來自三個不同來源的顯示表面的尺寸:
- SKCanvasView對象的常規Xamarin.Forms Width和Height屬性。
- SKCanvasView對象的CanvasSize屬性。
- SKImageInfo值的Size屬性,該屬性與前兩頁中使用的Width和Height屬性一致。

如您所見,SKCanvasView的CanvasSize屬性和SKImageInfo值的Size屬性在報告像素尺寸方面是一致的。 SKCanvasView的Height和Width屬性是Xamarin.Forms屬性,它們以平台定義的獨立於設備的單位報告視圖的大小。
SurfaceSizePage類顯示如何顯示這些值。 構造函數將SKCanvasView對象保存為字段,因此可以在PaintSurface事件處理程序中對其進行訪問:
DrawText最簡單的一個方法:
public void DrawText (String text, Single x, Single y, SKPaint paint)
您指定文本字符串,文本開始處的X和Y坐標以及SKPaint對象。 X坐標指定文本左側的位置【左下角】,但請注意:Y坐標指定文本基線的位置。 如果您曾經用手寫紙寫過,則基線是字符所在的行,而下降的行(例如字母g,p,q和y的下降行)則位於該行的下方。
SKPaint對象使您可以指定文本的顏色,字體系列和文本大小。 默認情況下,TextSize屬性的值為12,這會在諸如電話之類的高分辨率設備上生成微小的文本。 在除最簡單的應用程序之外的任何應用程序中,您還需要一些有關所顯示文本大小的信息。 SKPaint類定義了FontMetrics屬性和幾個MeasureText方法,但是為了減少花哨的需求,FontSpacing屬性提供了用於間隔連續文本行的建議值(只讀)。
iOS 7模擬器每個獨立於設備的像素有兩個像素,Android Nexus 5每個單位具有三個像素。這就是前面顯示的簡單圓圈在不同平台上具有不同大小的原因。
如果您希望完全以獨立於設備的單位進行工作,可以通過將SKCanvasView的IgnorePixelScaling屬性設置為true來實現。但是,您可能不喜歡這個結果,SkiaSharp將圖形渲染在較小的設備表面上,其像素大小等於獨立於設備的視圖大小。 (例如,SkiaSharp將在Nexus 5上使用360 x 512像素的顯示表面。)然后按比例放大該圖像,從而導致明顯的位圖鋸齒。
為了保持相同的圖像分辨率,更好的解決方案是編寫自己的簡單函數以在兩個坐標系之間進行轉換。
除DrawCircle方法外,SKanvas還定義了兩個繪制橢圓的DrawOval方法。 橢圓由兩個半徑而不是單個半徑定義。 這些被稱為長半徑和短半徑。 DrawOval方法繪制一個橢圓,其兩個半徑平行於X和Y軸。 (如果需要繪制不與X和Y軸平行的軸的橢圓,則可以使用如《旋轉變換》一文中討論的旋轉變換或如《繪制弧的三種方式》中所討論的圖形路徑。 )。 DrawOval方法的重載命名兩個半徑參數rx和ry,以指示它們與X和Y軸平行:
public void DrawOval (Single cx, Single cy, Single rx, Single ry, SKPaint paint)
參數:x、y的中心點,垂直半徑、水平半徑。
是否可以繪制 填充顯示表面的橢圓形?EllipseFillPage演示了如何進行,從xRadius和yRadius值中減去筆觸寬度的一半,以使整個橢圓及其輪廓適合顯示表面:
另一種方式: 繪制橢圓去填充矩形
//SKRect rect = new SKRect(0, 0, info.Width, info.Height); SKRect rect = new SKRect(strokeWidth/2, strokeWidth/2, info.Width-strokeWidth/2, info.Height-strokeWidth/2); canvas.DrawOval(rect, paint);
4、將文本和圖形集成
示例 FramedTextPage 在頁面上居中放置一個短文本字符串,並用由一對圓角矩形組成的框架包圍它。

在SkiaSharp中,您可以使用SKPaint類來設置文本和字體屬性,但也可以使用它來獲取呈現的文本大小。
SKRect對象://存儲一組四個浮點數,這些浮點數代表矩形的左上角和右下角。【使用它來畫邊框】
public partial class FramedTextPage : ContentPage { /// <summary> /// 在頁面上居中放置一個短文本字符串,並用由一對圓角矩形組成的框架包圍它 /// </summary> public FramedTextPage() { InitializeComponent(); Title = "Framed Text"; SKCanvasView canvasView = new SKCanvasView(); canvasView.PaintSurface += OnCanvasViewPaintSurface; Content = canvasView; } private void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args) { SKImageInfo info = args.Info; SKSurface surface = args.Surface; SKCanvas canvas = surface.Canvas; canvas.Clear(); string str = "Hello SkiaSharp!"; // Create an SKPaint object to display the text SKPaint textPaint = new SKPaint { Color = SKColors.Chocolate }; //第一個MeasureText調用具有一個簡單的字符串參數,並根據當前字體屬性返回文本的像素寬度。 //然后,程序將根據渲染的寬度,當前的TextSize屬性和顯示區域的寬度,計算SKPaint對象的新TextSize屬性。 此計算旨在設置TextSize,以使文本字符串以屏幕寬度的90%呈現: float textWidth = textPaint.MeasureText(str); //測量 textPaint.TextSize = 0.9f * info.Width * textPaint.TextSize / textWidth; //如果只需要在屏幕上居中放置一些文本,則可以在不測量文本的情況下大致完成。 而是將SKPaint的TextAlign屬性設置為枚舉成員SKTextAlign.Center。 //然后,您在DrawText方法中指定的X坐標指示文本水平中心的位置。 如果將屏幕的中點傳遞給DrawText方法,則文本將水平居中且幾乎垂直居中,因為基線將垂直居中。 //第二個MeasureText調用具有SKRect參數,因此它同時獲得了呈現文本的寬度和高度。 此SKRect值的Height屬性取決於文本字符串中是否存在大寫字母,升序和降序。 //例如,對於文本字符串“ mom”,“ cat”和“ dog”,報告了不同的Height值。 //如果通過DrawText調用以X和Y位置為0顯示文本,則SKRect結構的Left和Top屬性指示所渲染文本的左上角的坐標。例如,當此程序在iPhone上運行時 在7個模擬器中,作為對MeasureText的第一次調用之后的計算結果,TextSize被分配了值90.6254。 從第二次調用MeasureText獲得的SKRect值具有以下屬性值: SKRect textBounds = new SKRect(); //存儲一組四個浮點數,這些浮點數代表矩形的左上角和右下角。 textPaint.MeasureText(str, ref textBounds); // Calculate offsets to center the text on the screen[以使文本居中顯示在屏幕上] float xText = info.Width / 2 - textBounds.MidX; //MidX和MidY值指示矩形中心的坐標。 float yText = info.Height / 2 - textBounds.MidY; //或者 //float xText = info.Rect.MidX - textBounds.MidX; //float yText = info.Rect.MidY - textBounds.MidY; // And draw the text.. canvas.DrawText(str, xText, yText, textPaint); //對DrawRoundRect的兩個調用,這兩個調用都需要SKRect的參數。 此SKRect值基於從MeasureText方法獲得的SKRect值,但不能相同。 //首先,它需要更大一些,以使圓角矩形不會在文本的邊緣上繪制。 //其次,它需要在空間上移動,以使“左”和“上”值與要放置矩形的左上角相對應。 這兩個作業是通過SKRect定義的Offset和Inflate方法完成的: // Create a new SKRect object for the frame around the text SKRect frameRect = textBounds; frameRect.Offset(xText, yText); frameRect.Inflate(10, 10); //擴大 //為邊框創建另一個SKPaint對象,並兩次調用DrawRoundRect。 //第二個調用使用一個矩形,該矩形再膨脹10個像素。 第一次調用將角半徑指定為20個像素。 第二個的角半徑為30像素,因此它們似乎是平行的: SKPaint framePaint = new SKPaint { Style = SKPaintStyle.Stroke, StrokeWidth = 5, Color = SKColors.Blue }; // Draw one frame。 canvas.DrawRoundRect(frameRect, 20, 20, framePaint); // Inflate the frameRect and draw another frameRect.Inflate(10, 10); framePaint.Color = SKColors.DarkBlue; canvas.DrawRoundRect(frameRect, 30, 30, framePaint); } }
5、Bitmap Basics
1、顯示位圖
加載位圖
從各種來源加載位圖並顯示它們。
SkiaSharp中對位圖的支持非常廣泛。 以下 如何加載位圖以及如何顯示位圖:
SkiaSharp位圖是SKBitmap類型的對象。 創建位圖的方法有很多,但是本文將其自身限制為SKBitmap.Decode方法,該方法從.NET Stream對象加載位圖。
示例:BasicBitmapsPage演示了如何從三個不同的來源加載位圖:
- 通過互聯網
- 從可執行文件中嵌入的資源
- 來自用戶的照片庫
靜態SKBitmap.Decode方法負責解碼位圖文件,它可以處理JPEG,PNG和GIF位圖格式,並將結果以內部SkiaSharp格式存儲。
public partial class BasicBitmapsPage : ContentPage { SKCanvasView canvasView; HttpClient httpClient = new HttpClient(); /// <summary> /// 從Web加載的位圖 /// </summary> SKBitmap webBitmap; SKBitmap resourceBitmap; SKBitmap libraryBitmap; public BasicBitmapsPage() { InitializeComponent(); Title = "Basic Bitmaps"; canvasView = new SKCanvasView(); canvasView.PaintSurface += OnCanvasViewPaintSurface; Content = canvasView; // 加載項目中的位圖資源,圖片屬性設置為 嵌入的資源 string resourceID = "SkiaSharpDemo.Media.monkey.png"; Assembly assembly = GetType().GetTypeInfo().Assembly; using (Stream stream = assembly.GetManifestResourceStream(resourceID)) { resourceBitmap = SKBitmap.Decode(stream); } ////暫不實現 //// Add tap gesture recognizer //TapGestureRecognizer tapRecognizer = new TapGestureRecognizer(); //tapRecognizer.Tapped += async (sender, args) => //{ // // Load bitmap from photo library // IPhotoLibrary photoLibrary = DependencyService.Get<IPhotoLibrary>(); // using (Stream stream = await photoLibrary.PickPhotoAsync()) // { // if (stream != null) // { // libraryBitmap = SKBitmap.Decode(stream); // canvasView.InvalidateSurface(); // } // } //}; //canvasView.GestureRecognizers.Add(tapRecognizer); } private void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args) { SKImageInfo info = args.Info; SKSurface surface = args.Surface; SKCanvas canvas = surface.Canvas; canvas.Clear(); if (webBitmap != null) { float x = (info.Width - webBitmap.Width) / 2; float y = (info.Height / 3 - webBitmap.Height) / 2; canvas.DrawBitmap(webBitmap, x, y); } if (resourceBitmap != null) { canvas.DrawBitmap(resourceBitmap, new SKRect(0, info.Height / 3, info.Width, 2 * info.Height / 3)); } if (libraryBitmap != null) { float scale = Math.Min((float)info.Width / libraryBitmap.Width, info.Height / 3f / libraryBitmap.Height); float left = (info.Width - scale * libraryBitmap.Width) / 2; float top = (info.Height / 3 - scale * libraryBitmap.Height) / 2; float right = left + scale * libraryBitmap.Width; float bottom = top + scale * libraryBitmap.Height; SKRect rect = new SKRect(left, top, right, bottom); rect.Offset(0, 2 * info.Height / 3); canvas.DrawBitmap(libraryBitmap, rect); } else { using (SKPaint paint = new SKPaint()) { paint.Color = SKColors.Blue; paint.TextAlign = SKTextAlign.Center; paint.TextSize = 48; canvas.DrawText("Tap to load bitmap", info.Width / 2, 5 * info.Height / 6, paint); } } } /// <summary> /// 由於將await運算符與HttpClient一起使用最方便,因此無法在BasicBitmapsPage構造函數中執行代碼。 /// 因此放在 OnAppearing 中執行。 此處的URL指向Xamarin網站上帶有一些示例位圖的區域,網站上的一個程序包允許附加一個規范以將位圖調整為特定的寬度: /// </summary> protected override async void OnAppearing() { base.OnAppearing(); // 從Web 加載位圖 string url = "https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=2197602503,3264180697&fm=26&gp=0.jpg?width=480"; try { using (var stream = await httpClient.GetStreamAsync(url)) using (MemoryStream memoryStream = new MemoryStream()) { await stream.CopyToAsync(memoryStream); memoryStream.Seek(0, SeekOrigin.Begin); //當在SKBitmap.Decode方法中使用從GetStreamAsync返回的Stream時,Android操作系統引發異常,因為它在主線程上執行冗長的操作。 //因此,使用CopyToAsync將位圖文件的內容復制到MemoryStream對象。 //靜態SKBitmap.Decode方法負責解碼位圖文件。 它可以處理JPEG,PNG和GIF位圖格式,並將結果存儲在內部SkiaSharp格式中。 //此時,需要使SKCanvasView InvalidateSurface,以允許PaintSurface處理程序更新顯示。 webBitmap = SKBitmap.Decode(memoryStream); canvasView.InvalidateSurface(); } } catch (Exception) { } } }
以像素尺寸顯示
Canvas類定義了四個DrawBitmap方法, 這些方法允許以兩種根本不同的方式顯示位圖:
- 指定SKPoint值(或單獨的x和y值)將以其像素尺寸顯示位圖,位圖的像素直接映射到視頻顯示器的像素。
- 指定矩形會導致位圖拉伸到矩形的大小和形狀。
您可以使用具有SKPoint參數的DrawBitmap或具有單獨的x和y參數的DrawBitmap在其像素尺寸中顯示位圖:
DrawBitmap(SKBitmap bitmap, SKPoint pt, SKPaint paint = null) DrawBitmap(SKBitmap bitmap, float x, float y, SKPaint paint = null)
這兩種方法在功能上是相同的,指定的點指示位圖的左上角相對於畫布的位置。 由於移動設備的像素分辨率很高,因此較小的位圖通常在這些設備上顯得很小。
可選的SKPaint參數允許您使用透明度顯示位圖。 為此,請創建一個SKPaint對象,並將Color屬性設置為alpha通道小於1的任何SKColor值。例如:
paint.Color = new SKColor(0, 0, 0, 0x80);
作為最后一個參數傳遞的0x80表示50%的透明度。 您還可以在一種預定義的顏色上設置Alpha通道:
paint.Color = SKColors.Red.WithAlpha(0x80);
但是,顏色本身無關緊要。 在DrawBitmap調用中使用SKPaint對象時,僅檢查Alpha通道。
使用混合模式或濾鏡效果顯示位圖時,SKPaint對象也起作用。 在 SkiaSharp compositing and blend modes and SkiaSharp image filters.文中對此進行了演示。
示例PixelDimensionsPag顯示的位圖資源寬320像素,高240像素:
PaintSurface處理程序通過基於顯示表面的像素尺寸和位圖的像素尺寸計算x和y值來使位圖居中:
如果應用程序希望在其左上角顯示位圖,則只需傳遞(0,0)坐標。
拉伸以填充矩形
SKCanvas類還定義了將位圖呈現為矩形的DrawBitmap方法,以及將位圖的矩形子集呈現為矩形的另一個DrawBitmap方法:
DrawBitmap(SKBitmap bitmap, SKRect dest, SKPaint paint = null) DrawBitmap(SKBitmap bitmap, SKRect source, SKRect dest, SKPaint paint = null)
在這兩種情況下,位圖都被拉伸以填充名為dest的矩形。
在第二種方法中,源矩形允許您選擇位圖的子集,源矩形是相對於位圖的【想顯示位圖的哪一部分】,dest矩形(目標矩形)相對於輸出設備; 。
FillRectanglePage通過在與畫布大小相同的矩形中顯示先前示例中使用的相同位圖,演示了這兩種方法中的第一種:
拉伸,這通常不是您想要的,通過在水平和垂直方向上不同地拉伸,圖像會失真。 當以除像素大小以外的其他方式顯示位圖時,通常需要保留位圖的原始寬高比。
不過可以 保留 縱橫比 去拉伸。參考
多功能的位圖顯示功能
基於XAML的編程環境(例如UWP和Xamarin.Forms)具有在保留位圖長寬比的同時擴展或縮小位圖大小的功能。 盡管SkiaSharp不包含此功能,但是您可以自己實現。
可以填充顯示、拉伸顯示(保留縱橫比)、對齊顯示(開始、居中、結尾)
2、在SkiaSharp位圖上創建和繪圖
您已經了解了應用程序如何從Web,應用程序資源以及用戶的圖片庫加載位圖。 也可以在您的應用程序中創建新的位圖。 最簡單的方法涉及SKBitmap的構造函數之一:
SKBitmap bitmap = new SKBitmap(width, height);
width和height參數是整數,用於指定位圖的像素尺寸。 此構造函數創建一個全彩色位圖,每個像素有四個字節:紅色,綠色,藍色和alpha(不透明度)分量各一個字節。
創建新的位圖后,需要在位圖的表面上放置一些東西。 通常,您可以通過以下兩種方式之一執行此操作:
- 使用標准的Canvas繪制方法在位圖上進行繪制。
- 直接訪問像素位。
本文演示了第一種方法:在訪問SkiaSharp位圖像素一文中討論了第二種方法。
在位圖上繪圖
位圖表面上的繪制與視頻顯示器上的繪制相同。 要在視頻顯示上進行繪制,請從PaintSurface事件參數獲取SKCanvas對象。
要在位圖上繪制,請使用SKCanvas構造函數創建一個SKCanvas對象:
SKCanvas canvas = new SKCanvas(bitmap);
在位圖上完成繪制后,you can dispose of the SKCanvas object.。 因此,通常在using語句中調用SKCanvas構造函數:
using (SKCanvas canvas = new SKCanvas(bitmap)) { ··· // call drawing function }
然后可以顯示位圖【由另一個SKCanvas調用DrawBitmap 來顯示】。 程序可以基於相同的位圖創建一個新的SKCanvas對象,並在其上進行更多繪制。
示例中 HelloBitmapPage上將寫入文本“ Hello,Bitmap!”。 在位圖上,然后多次顯示該位圖。
HelloBitmapPage的構造函數首先創建一個SKPaint對象以顯示文本。 它確定文本字符串的尺寸,並使用這些尺寸創建位圖。 然后,它基於該位圖創建一個SKCanvas對象,調用Clear,然后調用DrawText。 用新的位圖調用Clear總是一個好主意,因為新創建的位圖可能包含隨機數據。
構造函數通過創建一個SKCanvasView對象以顯示位圖:
canvas.Clear(SKColors.Aqua); //該參數為顯示表面的背景着色:
注:您也可以在現有位圖上繪制,而不必創建新的位圖以在其上進行繪制。
3、將 SkiaSharp 位圖保存到文件
SkiaSharp應用程序創建或修改了位圖后,該應用程序可能希望將位圖保存到用戶的照片庫中。
- 將SkiaSharp位圖轉換為特定文件格式的數據,例如JPEG或PNG。
- 使用平台特定的代碼將結果保存到照片庫。
文件格式和編解碼器
當今大多數流行的位圖文件格式都使用壓縮來減少存儲空間。壓縮技術的兩大類稱為有損和無損。這些術語指示壓縮算法是否導致數據丟失。
最受歡迎的有損格式是由聯合圖像專家組開發的,稱為JPEG。 JPEG壓縮算法使用稱為離散余弦變換的數學工具來分析圖像,並嘗試刪除對保持圖像的視覺保真度不重要的數據。壓縮程度可以通過通常稱為質量的設置來控制。較高的質量設置將導致較大的文件。
相比之下,無損壓縮算法分析圖像的重復和像素模式,可以減少數據但不會導致任何信息丟失的方式進行編碼。原始位圖數據可以完全從壓縮文件中恢復。當前使用的主要無損壓縮文件格式是可移植網絡圖形(PNG)。
通常,JPEG用於照片,而PNG用於手動或算法生成的圖像。任何減少某些文件大小的無損壓縮算法都必須增加其他文件的大小。幸運的是,大小的增加通常僅發生在包含大量隨機(或看似隨機)信息的數據上。
壓縮算法非常復雜,足以保證兩個描述壓縮和解壓縮過程的術語:
- 解碼—讀取位圖文件格式並解壓縮
- 編碼—壓縮位圖並寫入位圖文件格式
SKBitmap類包含幾個名為Decode的方法,這些方法從壓縮源創建SKBitmap,所需要做的就是提供文件名,流或字節數組。解碼器可以確定文件格式,並將其交給適當的內部解碼功能。
另外,SKCodec類有兩個名為Create的方法,可以從壓縮源創建SKCodec對象,並使應用程序更多地參與解碼過程。 (SKCodec類在“動畫SkiaSharp位圖動畫”中與解碼動畫GIF文件有關顯示。)
對位圖進行編碼Encode時,需要更多信息:編碼器必須知道應用程序要使用的特定文件格式(JPEG或PNG或其他格式)。如果需要有損格式,則編碼器還必須知道所需的質量級別。
SKBitmap類使用以下語法定義一個Encode方法:
public Boolean Encode (SKWStream dst, SKEncodedImageFormat format, Int32 quality)
編碼的位圖被寫入可寫流(SKWStream中的“ W”代表“可寫”。)第二和第三個參數指定文件格式,並且(對於有損格式)指定所需的質量,范圍為0到100。
此外,SKImage和SKPixmap類還定義了Encode方法,該方法在某種程度上更加通用,您可能會更喜歡。
您可以使用靜態SKImage.FromBitmap方法從SKBitmap對象輕松創建SKImage對象,您可以使用PeekPixels方法從SKBitmap對象獲取SKPixmap對象。
SKImage定義的沒有參數的Encode方法,並自動保存為PNG格式,該無參數方法非常易於使用。
保存
將SKBitmap對象編碼為特定的文件格式時,通常會留下某種流對象或數據數組。一些Encode方法(包括SKImage定義的無參數的方法)返回一個SKData對象,可以使用ToArray方法將其轉換為字節數組【包含已編碼為特定文件格式,如 JPEG 或 PNG 的位圖的字節數組】,然后將這些數據保存到文件中。
保存到應用程序本地存儲中的文件非常容易,因為您可以為該任務使用標准的System.IO類和方法。結合使Mandelbrot集的一系列位圖動畫化的文章Animating SkiaSharp Bitmaps位圖動畫中對此技術進行了演示。
如果要與其他應用程序共享文件,則必須將其保存到用戶的照片庫中。此任務需要特定於平台的代碼並使用Xamarin.Forms DependencyService。
探索圖像格式
這又是SKImage的Encode方法:
public Boolean Encode (SKWStream dst, SKEncodedImageFormat format, Int32 quality)
SKEncodedImageFormat是一個枚舉,其中的成員引用了11種位圖文件格式,其中某些格式相當晦澀:
Astc-自適應可伸縮紋理壓縮
Bmp-Windows位圖
Dng-Adobe Digital Negative
Gif —圖形交換格式
Ico — Windows圖標圖像
Jpeg —聯合攝影專家組
Ktx — OpenGL的Khronos紋理格式
Pkm —神奇寶貝保存文件
Png —便攜式網絡圖形
Wbmp —無線應用協議位圖格式(每個像素1位)
Webp-Google WebP格式
很快您將看到,SkiaSharp實際上僅支持其中三種文件格式(Jpeg,Png和Webp)。對於所有其他格式,Encode方法不向流中寫入任何內容,結果字節數組為空。
要將名為bitmap的SKBitmap對象保存到用戶的照片庫,您還需要SKEncodedImageFormat枚舉的一個成員,該成員名為imageFormat和(對於有損格式)整數質量變量。 您可以使用以下代碼將該位圖保存到文件夾文件夾中名稱為filename的文件中:
using (MemoryStream memStream = new MemoryStream()) using (SKManagedWStream wstream = new SKManagedWStream(memStream)) { bitmap.Encode(wstream, imageFormat, quality); byte[] data = memStream.ToArray(); // Check the data array for content! bool success = await DependencyService.Get<IPhotoLibrary>().SavePhotoAsync(data, folder, filename); // Check return value for success! }
SKManagedWStream類派生自SKWStream(代表“可寫流”)。 Encode方法將編碼的位圖文件寫入該流。 該代碼中的注釋涉及您可能需要執行的一些錯誤檢查。
“保存文件格式”頁面使用類似的代碼,使您可以嘗試以各種格式保存位圖。
Saving finger-paint art
位圖的一種常見用法是在繪圖程序中,在這里它起着稱為陰影位圖的作用。 所有圖形都保留在位圖上,然后由程序顯示,位圖也很方便保存圖形。
SkiaSharp中的“手指畫”中的文章演示了如何 使用觸摸跟蹤來實現原始的手指畫程序。該程序僅支持一種顏色和一種筆划寬度,但是將整個圖形保留在一組SKPath對象中。
示例中的FingerPaintSavePage還將整個圖形保留在SKPath對象的集合中,但也將圖形呈現在位圖上,然后可以將其保存到您的照片庫中。
該程序大部分與原始的FingerPaint程序相似。 一種增強功能是XAML文件現在實例化了標記為“清除”和“保存”的按鈕:
隱藏代碼的維護着一個名為saveBitmap的SKBitmap類型的字段。 每當顯示表面的大小發生更改時,就會在PaintSurface處理程序中創建或重新創建此位圖。 如果需要重新創建位圖,則將現有位圖的內容復制到新位圖,以便無論顯示表面尺寸如何變化,所有內容都將保留:
三、SkiaSharp 線和路徑
使用SkiaSharp繪制線條和圖形路徑(graphics paths)
上一節演示了SkiaSharp SKCanvas類包括幾種繪制圓形,橢圓形,矩形,圓角矩形,文本和位圖的方法。 本節和后續各節介紹與創建和渲染圖形路徑有關的各種類。
圖形路徑是SkiaSharp中繪制線條和曲線的最通用方法。 本節介紹如何使用SKPath對象繪制直線,並使用一組微小的直線(稱為多段線 polyline)繪制可以通過算法定義的曲線。 關於SkiaSharp曲線和路徑的下一節將討論SKPath支持的各種曲線。
1、Lines and Stroke Caps
線和行程。了解如何使用SkiaSharp繪制具有不同筆划帽的線
在SkiaSharp中,渲染一條直線與渲染一系列連接的直線非常不同。 但是,即使繪制單條線,也經常需要為線賦予特定的筆划寬度。 隨着這些線變寬,線的末端的外觀也變得重要,The appearance of the end of the line is called the stroke cap:

對於繪制單條線,SKanvas定義了一個簡單的DrawLine方法,該方法的參數使用SKPaint對象指示線的起點和終點:
canvas.DrawLine (x0, y0, x1, y1, paint);
默認情況下,新實例化的SKPaint對象的StrokeWidth屬性為0,與呈現1像素粗細的線時,其值為1的效果相同。在諸如電話之類的高分辨率設備上,這看起來很薄,因此您可能需要將StrokeWidth設置為更大的值。
但是一旦開始繪制粗細的線條,就會引出另一個問題:應該如何渲染這些粗線的起點和終點?
線的起點和終點的外觀稱為線帽。在這種情況下,“帽子”一詞指的是一種帽子-位於行尾的東西。您將SKPaint對象的StrokeCap屬性設置為SKStrokeCap枚舉的以下成員之一:
Butt(the default)SquareRound
示例:StrokeCapsPage定義了一個PaintSurface事件處理程序,該處理程序循環遍歷SKStrokeCap枚舉的三個成員,同時顯示枚舉成員的名稱並使用該筆划帽畫一條線。
如您所見,Square和Round筆划帽有效地將線的長度延長到了線的起點和終點的一半。 當需要確定渲染圖形對象的尺寸時,此擴展很重要。
SKCanvas類還包括另一種繪制多條線的方法,該方法有些特殊:
DrawPoints (SKPointMode mode, points, paint)
points參數是SKPoint值的數組,而mode是SKPointMode枚舉的成員,該枚舉具有三個成員:
- Points:渲染單個點
- Lines:連接每對點的線
- Polygon:連接所有連續點的多邊形
2、Path Basics in SkiaSharp
探索SkiaSharp SKPath對象以組合連接的直線和曲線
圖形路徑最重要的功能之一就是能夠定義何時應連接多條線以及何時不應該連接多條線,差異可能是顯着的,以下這兩個三角形的頂部表明:

圖形路徑由SKPath對象封裝。路徑是一個或多個輪廓(contours)的集合。每個輪廓都是連接的直線和曲線的集合。輪廓沒有相互連接,但是它們可能在視覺上重疊。有時,單個輪廓可以重疊。
輪廓通常始於以下SKPath方法的調用:
- MoveTo 開始一個新的輪廓
該方法的參數是單個點,您可以將其表示為SKPoint值或單獨的X和Y坐標。 MoveTo調用在輪廓的起點和初始當前點建立一個點。
您可以調用以下方法,以一條直線或一條曲線從當前點到該方法中指定的點繼續輪廓,然后該輪廓將成為新的當前點:
- LineTo在路徑上添加一條直線
- ArcTo添加圓弧,即圓或橢圓圓周上的一條線
- CubicTo添加三次貝塞爾曲線樣條
- QuadTo添加二次貝塞爾曲線樣條
- ConicTo添加有理二次貝塞爾曲線樣條,可以精確地渲染圓錐截面(橢圓,拋物線和雙曲線)
這五種方法均未包含描述直線或曲線所需的所有信息。
這五個方法中的每一個都與由緊接其前的方法調用建立的當前點一起工作。例如,LineTo方法基於當前點向輪廓添加一條直線,因此LineTo的參數僅是一個點。
SKPath類還定義了與這六個方法同名的方法,但開頭是R:
- RMoveTo
- RLineTo
- RArcTo
- RCubicTo
- RQuadTo
- RConicTo
R代表相對。這些方法與不帶R的相應方法具有相同的語法,但相對於當前點,這些對於在您多次調用的方法中繪制路徑的相似部分非常方便。
輪廓以對MoveTo或RMoveTo的另一個調用結束,該調用開始一個新輪廓,或對Close的調用將閉合該輪廓。
Close方法自動從當前點到輪廓的第一個點追加一條直線,並將該路徑標記為閉合,這意味着將在不使用任何筆划上限的情況下進行渲染。
打開和閉合輪廓之間的差異在TwoTriangleContoursPage中進行了說明,該頁面使用具有兩個輪廓的SKPath對象渲染兩個三角形。第一個輪廓打開,第二個輪廓關閉。
如您所見,第一個輪廓顯然是由三條相連的線組成的序列,但是末端與起點之間沒有連接,兩條線在頂部重疊。
第二個輪廓顯然是閉合的,並且通過減少LineTo調用來完成,因為Close方法自動添加了最后一條線來閉合輪廓。
SKCanvas僅定義一個DrawPath方法,在本示例中,該方法被調用兩次以填充和描邊路徑。
填充了所有輪廓,即使沒有閉合的輪廓也是如此,為了填充未封閉的路徑,假定在輪廓的起點和終點之間存在一條直線。
如果從第一個輪廓刪除最后一個LineTo,或從第二個輪廓刪除Close調用,則每個輪廓將只有兩個側面,但將被填充,就好像它是一個三角形一樣。
public class TwoTriangleContoursPage : ContentPage { public TwoTriangleContoursPage() { Title = "Two Triangle Contours"; SKCanvasView canvasView = new SKCanvasView(); canvasView.PaintSurface += OnCanvasViewPaintSurface; Content = canvasView; } void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args) { SKImageInfo info = args.Info; SKSurface surface = args.Surface; SKCanvas canvas = surface.Canvas; canvas.Clear(); // Create the path SKPath path = new SKPath(); // Define the first contour path.MoveTo(0.5f * info.Width, 0.1f * info.Height); path.LineTo(0.2f * info.Width, 0.4f * info.Height); path.LineTo(0.8f * info.Width, 0.4f * info.Height); path.LineTo(0.5f * info.Width, 0.1f * info.Height); // Define the second contour path.MoveTo(0.5f * info.Width, 0.6f * info.Height); path.LineTo(0.2f * info.Width, 0.9f * info.Height); path.LineTo(0.8f * info.Width, 0.9f * info.Height); path.Close(); // Create two SKPaint objects SKPaint strokePaint = new SKPaint { Style = SKPaintStyle.Stroke, //輪廓 Color = SKColors.Magenta, StrokeWidth = 50 //設置為小點 頂部就看似重合了 }; SKPaint fillPaint = new SKPaint { Style = SKPaintStyle.Fill, //填充 Color = SKColors.Cyan }; // Fill and stroke the path。。填充並描邊 //canvas.DrawPath(path, fillPaint); canvas.DrawPath(path, strokePaint); } }
請記住,SKPath對象僅定義一個幾何圖形:一系列點和連接。 僅當SKPath與SKPaint對象組合時,路徑才會以特定的顏色,筆觸寬度等進行渲染。
另外,請記住,傳遞給DrawPath方法的SKPaint對象定義了整個路徑的特征。 如果要繪制需要幾種顏色的東西,則必須為每種顏色使用單獨的路徑。
正如線條的開始和結尾的外觀是由筆划帽定義的一樣,兩條線條之間的連接的外觀是由筆觸聯接(拐角處)定義的。 您可以通過將SKPaint的StrokeJoin屬性設置為SKStrokeJoin枚舉的成員來指定它:
Miterfor a pointy joinRoundfor a rounded join,圓角連接Bevelfor a chopped-off join
StrokeJoinsPage顯示了這三個筆觸連接,其代碼類似於StrokeCapspage。

3、The Path Fill Types
SkiaSharp路徑填充類型可能帶來的不同效果
路徑中的兩個輪廓可以重疊,組成一個輪廓的線可以重疊。 任何封閉區域都可以填充,但是您可能不想填充所有封閉區域。
填充算法由SKPath的SKFillType屬性控制,您可以將其設置為SKPathFillType枚舉的成員:
Winding, the defaultEvenOddInverseWindingInverseEvenOdd
winding and even-odd算法都基於從該區域繪制到無限遠的假設線來確定是否填充了任何封閉區域。該線與構成路徑的一條或多條邊界線交叉。
在Winding模式下,如果在一個方向上繪制的邊界線的數量與在另一方向上繪制的邊界線的數量平衡,則不會填充該區域。否則,該區域將被填充。如果邊界線的數量為odd,則奇偶算法將填充一個區域。
對於許多常規路徑,Winding算法通常會填充路徑的所有封閉區域。EvenOdd算法通常會產生更有趣的結果。
經典示例是五角星,如FivePointedStarPage實例化兩個Picker視圖,以選擇 路徑填充類型以及是描邊路徑還是填充路徑,或者兩者都是,以及選擇順序:

4、折線和參數方程式
使用SkiaSharp渲染可以用參數方程式定義的任何行
在本指南的“ SkiaSharp曲線和路徑”部分中,您將看到SKPath定義的用於渲染某些類型曲線的各種方法。 但是,有時有必要繪制SKPath不直接支持的曲線。
在這種情況下,可以使用折線(連接的線的集合)來繪制可以數學定義的曲線。 如果使線足夠小且足夠多,結果將看起來像曲線,例如,下面這個螺旋實際上是3600條小線:

有點類似極坐標方程
通常,最好根據一對參數方程式定義曲線。這些是X和Y坐標的等式,它們取決於第三個變量,有時在時間上稱為t。例如,以下參數方程式定義了一個半徑為1的圓,該圓的中心為0到1的t的點(0,0)。
x = cos(2πt)
y = sin(2πt)
5、點和短划線
6、手指畫圖
用手指在畫布上繪畫。
SKPath對象可以不斷更新和顯示。 此功能允許將路徑用於交互式繪圖,例如在手指畫程序中。
Xamarin.Forms中的觸摸支持不允許跟蹤屏幕上的各個手指,因此開發了Xamarin.Forms觸摸跟蹤效果以提供其他觸摸支持。 從效果中調用事件一文中介紹了此效果。
示例程序“ Touch-Tracking Effect Demos”包括兩個使用SkiaSharp的頁面,其中包括一個手指繪畫程序。
.NET Standard庫項目包括TouchEffect類,TouchActionType枚舉,TouchActionEventHandler委托和TouchActionEventArgs類。 每個平台項目都包括該平台的TouchEffect類。 iOS項目還包含一個TouchRecognizer類。
FingerPaintPag是手指畫的簡化實現。 它不允許選擇顏色或筆觸寬度,它無法清除畫布,當然也不能保存您的圖稿。
四、SkiaSharp Transforms
SkiaSharp支持作為SKCanvas對象的方法實現的傳統圖形轉換。 在數學上,變換會更改您在繪制圖形對象時在SKCanvas繪圖函數中指定的坐標和尺寸。 變換通常便於繪制重復的圖形或動畫。 如果不使用轉換,則無法使用某些技術(例如旋轉位圖或文本)。
SkiaSharp轉換支持以下操作:
- 轉換 以將坐標從一個位置移動到另一位置
- 縮放 以增加或減少坐標和大小
- 旋轉 以圍繞點旋轉坐標
- 傾斜 以水平或垂直移動坐標,以使矩形成為平行四邊形
這些被稱為仿射變換( affine transforms)。 仿射變換始終保留平行線,絕不會導致坐標或大小變為無限大。 正方形永遠不會變換為平行四邊形,圓形永遠不會變換為橢圓形。
SkiaSharp還支持基於標准3×3變換矩陣的非仿射變換(也稱為投影變換或透視變換)。 非仿射變換允許將正方形變換為任何凸四邊形,這是一個四邊形,所有內角均小於180度。 非仿射變換可以使坐標或大小變為無限,但是它們對於3D效果至關重要。
SkiaSharp和Xamarin.Forms轉換之間的區別
Xamarin.Forms還支持類似於SkiaSharp中的轉換。 Xamarin.Forms VisualElement類定義以下轉換屬性:
- TranslationX和TranslationY
- Scale
Rotation,RotationX, andRotationY
RotationX和RotationY屬性是透視轉換,可創建准3D效果。
SkiaSharp轉換和Xamarin.Forms轉換之間存在幾個關鍵區別:
1、第一個區別是SkiaSharp轉換應用於整個SKCanvas對象,而Xamarin.Forms轉換應用於單個VisualElement派生對象。
(您可以將Xamarin.Forms轉換應用於SKCanvasView對象本身,因為SKCanvasView是從VisualElement派生的,但是在該SKCanvasView中,SkiaSkarp轉換也適用。)
SkiaSharp變換相對於SKCanvas的左上角,而Xamarin.Forms變換相對於應用它們的VisualElement的左上角。在應用縮放和旋轉變換時,此差異非常重要,因為這些變換始終相對於特定點。
2、真正的最大區別是,SKiaSharp轉換是方法,而Xamarin.Forms轉換是屬性。
這是語義上的區別,而不僅僅是語法上的區別:
SkiaSharp轉換執行操作,而Xamarin.Forms轉換設置狀態。
SkiaSharp變換適用於隨后繪制的圖形對象,但不適用於應用變換之前繪制的圖形對象。相反,設置屬性后,Xamarin.Forms轉換將應用於先前渲染的元素。
SkiaSharp轉換在調用方法時是累積(多次設置平移,坐標會累積計算)的;當屬性設置為另一個值時,將替換Xamarin.Forms轉換。
1、The Translate Transform
了解如何使用平移變換來移動SkiaSharp圖形
SkiaSharp中最簡單的轉換類型是translate。 此變換可在水平和垂直方向上移動圖形對象。 從某種意義上說,平移是最不必要的變換,因為通常可以通過簡單地更改繪圖功能中使用的坐標來達到相同的效果。
但是,在渲染路徑時,所有坐標都封裝在路徑中,因此應用平移變換來移動整個路徑要容易得多。
translate對於動畫和簡單的文字效果也很有用。
SKCanvas中的Translate方法,這些參數導致隨后繪制的圖形對象在水平和垂直方向上移動:
public void Translate (Single dx, Single dy)
public void Translate (SKPoint point)
示例:AccumulatedTranslatePage,演示了Translate方法的多次調用是累積的,它顯示同一矩形的20個版本,每個版本與上一個矩形的偏移量剛好足以使它們沿對角線延伸。
平移對 之后的圖像有影響。即畫完一個 然后在移動坐標,畫下一個。
public class AccumulatedTranslatePage : ContentPage { public AccumulatedTranslatePage() { Title = "Accumulated Translate"; SKCanvasView canvasView = new SKCanvasView(); canvasView.PaintSurface += OnCanvasViewPaintSurface; Content = canvasView; } void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args) { SKImageInfo info = args.Info; SKSurface surface = args.Surface; SKCanvas canvas = surface.Canvas; canvas.Clear(); using (SKPaint strokePaint = new SKPaint()) { strokePaint.Color = SKColors.Black; strokePaint.Style = SKPaintStyle.Stroke; strokePaint.StrokeWidth = 3; int rectangleCount = 20; SKRect rect = new SKRect(0, 0, 250, 250); float xTranslate = (info.Width - rect.Width) / (rectangleCount - 1); //可移動的距離 除以 想移動的次數 float yTranslate = (info.Height - rect.Height) / (rectangleCount - 1); for (int i = 0; i < rectangleCount; i++) { canvas.DrawRect(rect, strokePaint); canvas.Translate(xTranslate, yTranslate);//平移對 之后的圖像有影響。即畫完一個 然后在移動坐標,畫下一個。 } } } }
Translate變換的另一個常見用途是渲染視覺對象,該對象最初是使用方便繪制的坐標創建的。 例如,您可能想指定一個以(0,0)為中心的模擬時鍾的坐標。 然后,您可以使用 轉換在所需位置顯示時鍾。 HendecagramArrayPage中演示了此技術。
動畫通常涉及變換。 Hendecagram動畫頁面將11個點的星星繞一圈移動。 HendecagramAnimationPage類從一些字段開始,並覆蓋OnAppearing和OnDisappearing方法以啟動和停止Xamarin.Forms計時器。
可以將星星 替換為文本。
2、The Scale Transform
translate轉換可以將圖形對象從一個位置移動到另一位置。 相反,比例轉換會更改圖形對象的大小。
3、The Rotate Transform
通過旋轉變換,SkiaSharp圖形對象擺脫了與水平軸和垂直軸對齊的限制
為了繞點(0,0)旋轉圖形對象,SkiaSharp支持RotateDegrees方法(旋轉度數)和RotateRadians(旋轉弧度)方法:
public void RotateDegrees (Single degrees) public Void RotateRadians (Single radians)
360度的圓與兩個π弧度相同,因此很容易在兩個單位之間進行轉換。 .NET Math類中的所有三角函數均使用弧度單位。
順時針旋轉可增加角度。 (盡管按慣例,笛卡爾坐標系上的旋轉是逆時針方向,但順時針旋轉與Y坐標的增加一致,如SkiaSharp中所述。)允許使用負角和大於360度的角度。
旋轉的變換公式比平移和縮放的公式復雜。 對於α角,轉換公式為:
x'= x•cos(α)– y•sin(α)
y` = x•sin(α)+ y•cos(α)
BasicRotatePage演示了RotateDegrees方法。
BasicRotate.xaml.cs文件顯示一些文本,其基線在頁面上居中,並基於Slider(范圍介於–360到360之間)對其進行旋轉。這是PaintSurface處理程序的相關部分:
因為旋轉以畫布的左上角為中心,所以對於該程序中設置的大多數角度,文本在屏幕外旋轉:
通常,您會希望使用以下版本的RotateDegrees和RotateRadians方法來旋轉以指定的樞軸點為中心的內容:
public void RotateDegrees (Single degrees, Single px, Single py) public void RotateRadians (Single radians, Single px, Single py)
示例CenteredRotatePage:將旋轉中心設置為與放置文本相同的點。
canvas.RotateDegrees((float)rotateSlider.Value, info.Width / 2, info.Height / 2); //以中心點來旋轉 canvas.DrawText(Title, info.Width / 2, info.Height / 2, textPaint);
其實 RotateDegrees調用等效於 兩個Translate調用和一個非居中的RotateDegrees:
canvas.Translate(info.Width / 2, info.Height / 2); canvas.RotateDegrees((float)rotateSlider.Value); canvas.Translate(-info.Width / 2, -info.Height / 2); canvas.DrawText(Title, info.Width / 2, info.Height / 2, textPaint);
在特定位置顯示文本的DrawText調用 等效於該位置的Translate調用,后跟(0,0)點的DrawText:
canvas.Translate(info.Width / 2, info.Height / 2); canvas.RotateDegrees((float)rotateSlider.Value); canvas.Translate(-info.Width / 2, -info.Height / 2); canvas.Translate(info.Width / 2, info.Height / 2); canvas.DrawText(Title, 0, 0, textPaint);
其中兩個Translate抵消,所以可以等效為:
canvas.Translate(info.Width / 2, info.Height / 2); canvas.RotateDegrees((float)rotateSlider.Value); canvas.DrawText(Title, 0, 0, textPaint);
從概念上講,這兩種轉換的應用順序與它們在代碼中的顯示方式相反。 DrawText調用在畫布的左上角顯示文本。 RotateDegrees調用相對於左上角旋轉該文本。 然后,Translate調用將文本移動到畫布的中心。
通常有幾種 組合旋轉和平移的方法。 RotatedTextPage創建以下顯示:

通常可以從更多全局轉換開始(循環外邊),然后是更多本地轉換(循環里面)。這通常是組合旋轉和平移的最簡單方法。
例如,假設您要繪制一個圖形對象,該對象圍繞其中心旋轉,就像一個在其軸上旋轉的行星一樣。但是,您還希望該對象圍繞屏幕中心旋轉,就像行星圍繞太陽旋轉一樣。
您可以通過將對象放置在畫布的左上角,然后使用動畫將其圍繞該角(左上角)旋轉來實現。接下來,像軌道半徑一樣水平平移對象。現在,圍繞原點應用第二個動畫旋轉,這使對象繞轉角旋轉,現在平移到畫布的中心。
示例:RotateAndRevolvePage
示例UglyAnalogClock程序 使用旋轉來繪制時鍾的分鍾和小時標記並旋轉指針。 該程序使用基於以點(0,0)為中心,半徑為100的圓的任意坐標系繪制時鍾。它使用平移和縮放在頁面上擴展和居中該圓。
Translate和Scale調用全局應用於時鍾,因此這些是在初始化SKPaint對象之后首先調用的:
必須全天圍成一圈繪制60個具有兩種不同大小的標記。 DrawCircle調用在相對於時鍾中心的點(0,–90)處繪制該圓,對應於12:00。 在每個刻度線之后,RotateDegrees調用將旋轉角度增加6度。 angle變量僅用於確定是繪制大圓還是小圓:
(-90這個數 相當於確定 時鍾的半徑)
更多參考
在.Net Core 2.1下使用SkiaSharp進行圖片處理
