LoginSignup
2
3

[開発] SkiaSharp 導入と各種サンプル (WPF+SkiaSharp)

Last updated at Posted at 2023-11-06


SkiaSharpはGoogle のオープンソース 2Dグラフィックライブラリです。

サポートされているプラットフォームは、

  • .NET Core / .NET Standard 1.3 /Linux
    Xamarin.Android Xamarin.iOS Xamarin.tvOS Xamarin.Mac
    Windows Classic Desktop (Windows.Forms / WPF)
    Windows UWP (Desktop / Mobile / Xbox / HoloLens) 等多岐にわたります。
  • ザマリンが推奨しているライブラリでC++で作られています。

非常に多くのメソッド、プロパティがあります。
詳しくは公式サイト

Nuget でSkiaSharpとSkiaSharp.View.WPFを入れます。
※ここに掲載したXAMLは共通です。

  • InvalidateVisual()を呼ぶとPaintSurface()が呼ばれ、その中で描画します。
  • PaintSurface()内でTask.Delay()は使用できません。
  • 日本語解説サイトが皆無です。有ったとしても殆どがザマリン用。
  • アニメーション機能は特に無いが工夫次第である程度の事はできる。

1.線

OP3.jpg

// SkiaSharp Test / inf102
using SkiaSharp;
using SkiaSharp.Views.Desktop;
using System;
using System.Threading.Tasks;
using System.Windows;

namespace SkiaSharpSample {

    public partial class MainWindow : Window{

        static int r=0;

	    public MainWindow()		{
		    InitializeComponent();
            this.WindowState = WindowState.Maximized;
		}

        private async  void Window_Loaded(object sender, RoutedEventArgs e) {

            while (true){
                skiaCanvas2.InvalidateVisual(); 
                
                await Task.Delay(10); 
                r+=1;
                               
            }
        }

        void PaintSurface(object sender, SKPaintSurfaceEventArgs args){

            SKImageInfo info = args.Info;
            SKSurface surface = args.Surface;
            SKCanvas canvas = surface.Canvas;
                          
            // 背景色
           canvas.Clear(SKColors.Black);
            
            SKPaint B_LINE= new SKPaint{
	            Style =  SKPaintStyle.StrokeAndFill,
			    Color = new SKColor(0, 255, 255),
                StrokeCap = (SKStrokeCap)3,
                IsAntialias=true,
                StrokeWidth = 3

            };
            canvas.DrawLine(0,0,r,1200,B_LINE);
		}
	}
}
  • canvas.Clear(SKColors.Black) を消すと塗りつぶされていきます。

2.文字

右から左に文字が移動して行きます。

// SkiaSharp Test

using SkiaSharp;
using SkiaSharp.Views.Desktop;
using System;
using System.Threading.Tasks;
using System.Windows;

namespace SkiaSharpSample {

    public partial class MainWindow : Window	{

        static int r=1200;

		public MainWindow()		{
			InitializeComponent();
            this.WindowState = WindowState.Maximized;
		}

        private async  void Window_Loaded(object sender, RoutedEventArgs e) {

            while (true){
                skiaCanvas2.InvalidateVisual(); 
                
                await Task.Delay(10); 
                r--;
                               
            }
        }

		void PaintSurface(object sender, SKPaintSurfaceEventArgs args){

            SKImageInfo info = args.Info;
            SKSurface surface = args.Surface;
            SKCanvas canvas = surface.Canvas;
               
            // 背景色
            canvas.Clear(SKColors.Black);
            
            var skPaint = new SKPaint() {
                TextSize=45,
                TextAlign= (SKTextAlign)SKTextAlign.Center
               // Typeface = SKTypeface.FromFamilyName("LINE Seed JP_OTF")

            };
            skPaint.Color=SKColors.Red;
            canvas.DrawText("SkiaSharp",r,200,skPaint);
		}
	}
}

3.日本語を扱う場合は別途、LINE Seed JP_OTF等をインストール


4.応用 円で渦巻模様を描く

// SkiaSharp Test (c)inf102 2023.

using SkiaSharp;
using SkiaSharp.Views.Desktop;
using System;
using System.Threading.Tasks;
using System.Windows;

namespace SkiaSharpSample {

    public partial class MainWindow : Window	{

        static int R=0;

		public MainWindow()		{
			InitializeComponent();
            this.WindowState = WindowState.Maximized;
		}

        private async  void Window_Loaded(object sender, RoutedEventArgs e) {

            while (true){
                skiaCanvas2.InvalidateVisual(); 
                
                await Task.Delay(5); 
                R+=1;
                               
            }
        }

		void PaintSurface(object sender, SKPaintSurfaceEventArgs args){

            SKImageInfo info = args.Info;
            SKSurface surface = args.Surface;
            SKCanvas canvas = surface.Canvas;
           
           // 背景色
           // canvas.Clear(SKColors.Black);
            
            SKPaint B_LINE= new SKPaint{
	            Style =  SKPaintStyle.StrokeAndFill,
			    Color = new SKColor(0, 255, 0),
                StrokeCap = (SKStrokeCap)3,
                IsAntialias=true,
                StrokeWidth = 20

            };
            
            double x,y;
            x=skiaCanvas2.CanvasSize.Width  / 2 + R*Math.Sin (R*3.14/30);
            y=skiaCanvas2.CanvasSize.Height / 2 - R*Math.Cos (R*3.14/30);
            canvas.DrawCircle((float)x,(float) y ,1,B_LINE);
		}
	}
}

上記、canvas.Clear(SKColors.Black)を実行するよう // を外すと円が動きます。

5.罫線

・単なる線ではなく点線にすることも可能。
・タスクマネージャのパフォーマンスタブ風。

// SkiaSharp Test / (c)inf102 2023.
using SkiaSharp;
using SkiaSharp.Views.Desktop;
using System;
using System.Threading.Tasks;
using System.Windows;

namespace SkiaSharpSample {

    public partial class MainWindow : Window	{

		public MainWindow()		{
			InitializeComponent();

            // 罫線パターン
            dashArray[0] = 3.5f;  //0.21 2.5 2.5
            dashArray[1] = 2.0f;
            //dashArray[2] = 2.0f;
		}
        static int XSC=500;

        // 罫線パターン
        static readonly float[] dashArray = new float[4];

        private async  void Window_Loaded(object sender, RoutedEventArgs e) {
            
            Height=247;
            Width=650;
            
            while (true){
                skiaCanvas2.InvalidateVisual(); 
                await Task.Delay(10);
                 
            }
        }


		void PaintSurface(object sender, SKPaintSurfaceEventArgs args){

            args.Surface.Canvas.Clear(SKColors.Black);

            SKPaint XY_LINE1= new SKPaint{
	            Style =  SKPaintStyle.StrokeAndFill,
                Color = new SKColor(0, 191, 200),
                IsAntialias=true,
                PathEffect = SKPathEffect.CreateDash(dashArray, 10),
                StrokeWidth =1

            };

            SKPaint XY_LINE2= new SKPaint{
	            Style =  SKPaintStyle.StrokeAndFill,
			    Color = new SKColor(00, 191, 200),
                IsAntialias=true,
                PathEffect = SKPathEffect.CreateDash(dashArray, 10),
                StrokeWidth = 1

            };

            // X
            for(int g = XSC; g > 0; g -= (int)20) {
               args.Surface.Canvas.DrawLine(g, 0, g, 200, XY_LINE1);
            }
            
            XSC-=1;

            if(XSC <= 634) XSC = 654;

            // Y
            for(int g = 201; g >= 2; g -= (int)20) {
                args.Surface.Canvas.DrawLine(0, g, 650, g, XY_LINE2);
            }
        }
	}
}

6.JPG表示

void SfPaintSurface(object sender, SKPaintSurfaceEventArgs args) {
  
    SKSurface surface = args.Surface;
    SKCanvas canvas = surface.Canvas;

    canvas.Clear(SKColors.Black);
    var bitmap = SKBitmap.Decode("C:\\OP1.JPG");
    canvas.DrawBitmap(bitmap,10,10);

}

7.非アフィン変換

3Dのグラフは計算が面倒なので2Dで書いた後、JPG変換と非アフィン変換を連続的にやってアニメーション化しようと思ったが現状ジャギーが目立つ。改善策は未確認。

123.jpg
↓ 非アフィン変換
ORG.jpg

void SfPaintSurface(object sender, SKPaintSurfaceEventArgs args) {
  
    var bitmap = SKBitmap.Decode("C:\\123.JPG");
    SKSurface surface = args.Surface;
    SKCanvas canvas = surface.Canvas;
    canvas.Clear(SKColors.Black);
  
    SKMatrix perspectiveMatrix = SKMatrix.MakeIdentity();
    perspectiveMatrix.Persp0 = (float)0.00152;
    perspectiveMatrix.Persp1 = (float)0.0000012;

    float xCenter = 400;
    float yCenter = 600;
           
    SKMatrix matrix = SKMatrix.MakeTranslation(-xCenter, -yCenter);
    SKMatrix.PostConcat(ref matrix, perspectiveMatrix);
    SKMatrix.PostConcat(ref matrix, SKMatrix.MakeTranslation(xCenter, yCenter));

    float x = xCenter - bitmap.Width / 2;
    float y = yCenter - bitmap.Height / 2;

    canvas.SetMatrix(matrix);
    canvas.DrawBitmap(bitmap,x,y);
}

XAML(共通)

<Window x:Class="SkiaSharpSample.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:SkiaSharpSample" xmlns:skia="clr-namespace:SkiaSharp.Views.WPF;assembly=SkiaSharp.Views.WPF" 
        xmlns:wpf="clr-namespace:SkiaSharp.Views.WPF;assembly=SkiaSharp.Views.WPF" mc:Ignorable="d"
        Title="SkiaSharp" Loaded="Window_Loaded">

    <Grid  >
        <wpf:SKElement x:Name="skiaCanvas2" PaintSurface="PaintSurface"  />
    </Grid>

</Window>

8.ミサイルコマンドの作りかけ(サンプル)

ミサイルの発射はできたが、当たり判定、敵ミサイルの非同期、誘爆の処理が予想以上に大変で放置。
マウス左で発射。いつか完成させたい。非同期の複数ミサイルの処理が必要で発射だけでも手間がかかった。
自ミサイルの爆発コードも入ってるが不具合の為、呼び出しは行っていません。

using Microsoft.Win32.SafeHandles;
using SkiaSharp;
using SkiaSharp.Views.Desktop;
using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;

namespace SF {

    public partial class MainWindow : Window {
       
        // 広がる時間
        const int BaRate=5;

        // 広がる広さ
        const int BaArea=40;

        // マウス位置
        static int MX,MY;
        static int GX;
        static bool FIRST;

        // 標準レティクル
        static int [] Tx = new int [4];
        static int [] Ty = new int [4];

        static int BA;

        static int[,,] a = new int[1000, 200,100];

        public void msg(string ss) {
            MessageBox.Show(ss);
        }

        //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        public  MainWindow() {
            InitializeComponent();

        }

        private  async void Window_Loaded(object sender, RoutedEventArgs e) {
        
            while (true){
        
                await Task.Delay(TimeSpan.FromSeconds(1.0 / 500));
        
                for (int cz=0;cz<1000;cz++){ 
                    if (a[cz,0,0] !=0) a[cz,0,4]++;
                }

                for (int cz=0;cz<1000;cz++){ 
                    if (a[cz,0,0]!=0){
                        if (a[cz,0,4] > a[cz,0,2]/20.0) { //[1]
                            a[cz,0,3]=a[cz,0,4]; // [2]
                            a[cz,0,0]=999; // LINE消去用値
                            if (a[0,0,7]!=999){
                                 _=Task.Run ( ()=>{
                                   //  await Task.Delay(TimeSpan.FromSeconds(1.0 / 500));
                                     for (int t=0;t<40;t++){
                                        Dispatcher.Invoke((Action)(()=>{
                                          //  Sf2.InvalidateVisual();
                                        }));
                                        a[0,0,7]++;
                                     }
                                 });
                                 a[0,0,7]=999;
                            }
                         break;
                        }
                    }
                }
                // 本線
                Sf.InvalidateVisual();
            }
        }   

        void SfPaintSurface2(object sender, SKPaintSurfaceEventArgs args) {
          
            SKSurface surface = args.Surface;
            SKCanvas canvas = surface.Canvas;
          
            SKPaint A_LINE = new SKPaint {
                StrokeWidth=1,
                IsAntialias = true,
             
            };

            // 拡大
            A_LINE.Color = new SKColor(255,255,255,255);
            A_LINE.Style = SKPaintStyle.Fill;

            if (a[0,0,7]==999){
            
                canvas.Clear();
                return;
            }

            canvas.DrawCircle(a[0, 0, 5], a[0, 0, 6], a[0,0,7], A_LINE);
               
        }

        // 角度
        protected int getRadian(double x, double y, double x2, double y2) {
            double radian = Math.Atan2(y2 - y,x2 - x);
            return (int)(radian*180d/3.14);
        }

        protected int getDistance(double x, double y, double x2, double y2) {
            double distance = Math.Sqrt((x2 - x) * (x2 - x) + (y2 - y) * (y2 - y));
            return (int) distance;
        }

        private void Window_PreviewMouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e) {
       
            System.Windows.Point point = e.GetPosition(this);     
            MX=(int)point.X;
            MY=(int)point.Y;

            // 標準レティクル
            Tx[0]=(int)(MX + (10)* Math.Sin ((-45-270) * Math.PI/180)); Ty[0]=(int)(MY - (10)* Math.Cos ((-45-270) * Math.PI/180));
            Tx[1]=(int)(MX + (10)* Math.Sin ((45-270) * Math.PI/180));  Ty[1]=(int)(MY - (10)* Math.Cos ((45-270) * Math.PI/180));
            Tx[2]=(int)(MX + (10)* Math.Sin ((135-270) * Math.PI/180)); Ty[2]=(int)(MY - (10)* Math.Cos ((135-270) * Math.PI/180));
            Tx[3]=(int)(MX + (10)* Math.Sin ((225-270) * Math.PI/180)); Ty[3]=(int)(MY - (10)* Math.Cos ((225-270) * Math.PI/180));

            // 最大長
            int LL=getDistance (MX,MY,959,1147);

            // 角度
            int RA=getRadian (959,1147,MX,MY);
        
            // 本数毎に長さ保存 
            a[GX,0,2]=LL;

            // 標準レティクル表示用マウス位置
            a[GX,0,5]=MX;
            a[GX,0,6]=MY;

            int vz=0;
            for (int w=0;w<100;w++){
                a[GX,w,0]=(int)(959 +  (vz)* Math.Sin ((RA-270.7) * Math.PI/180));
                a[GX,w,1]=(int)(1147 - (vz)* Math.Cos ((RA-270.7) * Math.PI/180));
                vz+=20;
            }
            GX++; // 本数
        }

        // 本線
        void SfPaintSurface(object sender, SKPaintSurfaceEventArgs args) {

            SKSurface surface = args.Surface;
            SKCanvas canvas = surface.Canvas;
         
            if (FIRST==false){
                canvas.Clear();
                canvas.Clear(SKColors.Black);
                FIRST=true;
            }

            SKPaint A_LINE= new SKPaint{
	            IsAntialias=true,
               
            };
          
            // 標準レティクル
            A_LINE.StrokeWidth = 2;
            A_LINE.Color = new SKColor(255,255,255,100);

            for (int cz=0;cz<1000;cz++){ 
                if (a[cz,0,0]!=0) for (int tx=0;tx<4;tx++) canvas.DrawLine(MX,MY,Tx[tx],Ty[tx],A_LINE);
            }

            // LUNCH CLEAR
            for (int cz=0;cz<1000;cz++){ 
                if (a[cz,0,0]==999){
                    a[cz,0,0]=0;
                    A_LINE.StrokeWidth = 5;
                    A_LINE.Color = new SKColor(0,0,0,255);
                    int ma=a[cz,0,3];
                    canvas.DrawLine(959,1147,(float)a[cz,ma,0],(float)a[cz,ma,1],A_LINE);
                }
            }
            //////////////////////////////////////////////////////////////////////////
            // 発射
            //////////////////////////////////////////////////////////////////////////
            A_LINE.Color = new SKColor(0,255,0,255);
            int CrentLL;

            for (int cz=0;cz<1000;cz++){ 
                if(a[cz, 0, 0] != 0 ) {
                    if (a[cz,0,0] ==999) continue;
                    A_LINE.StrokeWidth = 2;
                    CrentLL=a[cz,0,4];
                    canvas.DrawLine(959, 1147, (float)a[cz, CrentLL, 0], (float)a[cz, CrentLL, 1], A_LINE);
                }
            }
        }
    }
}
<Window x:Class="SF.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:wpf="clr-namespace:SkiaSharp.Views.WPF;assembly=SkiaSharp.Views.WPF"
        xmlns:local="clr-namespace:SF" mc:Ignorable="d" 
        Title="StatField" Height="1200" Width="1920" Loaded="Window_Loaded" 
	WindowState="Maximized" PreviewMouseLeftButtonDown="Window_PreviewMouseLeftButtonDown"  >
  
    <Grid>
        <wpf:SKElement x:Name="Sf"    PaintSurface="SfPaintSurface" />
        <wpf:SKElement x:Name="Sf2"    PaintSurface="SfPaintSurface2" />
    </Grid>
</Window>
2
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
3