CG Library SkiaSharp「Mystify」


::: note
・<a href="https://www.google.com/search?client=firefox-b-d&sca_esv=865d6c36e0c51f09&q=Mystify%E3%80%80%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%BB%E3%83%BC%E3%83%90&udm=7&fbs=AEQNm0ClaE9X54c1nhJ33ImXyCmebxM16lV52Cec4L2m46dATMzBnNeFBXlBFPp4Wpan5mpxtfFNFZwHBeo8Y7_N1Sbtgj89TyYbMT8tR-mm_0eUvQA6hLitbnBGctXN2nsTmyIY5X9wPW3z0oWQbdYNWbWJdSoB_Q-oCQ3f1rv0BkDJFQ7mHCiPKRNu2w3eEK7M9Owfe9rFi_ASR-uIZxHQNYv2TsaCCfgJwwDMwVOOtqgKQYeRYjE4rSG3I2AFcF8uegXeQcM0&sa=X&ved=2ahUKEwi4ptXQm_uKAxUtrlYBHf8bNGUQtKgLegQIEhAB&biw=1187&bih=793&dpr=1.5#ip=1">M.S.社のMystifyスクリーンセーバ</A>のSkiaSharp WPF版。
・だんだん崩れるので展開後、指定秒後に再描画してます。

:::
<font COLOR="blue" >

#### 1.キー操作説明</FONT>

- <B><font COLOR="RED" >[F]</B>...................フルスクリーン表示。(起動直後にFキー)

- <B><font COLOR="RED" >[PageUp]</B>.....間隔増　一辺が10の線で構成されているがその線同士の間隔可変
- <B><font COLOR="RED" >[PageDw]</B>.....間隔減
- <B><font COLOR="RED" >[マウス左]</B>....再描画(リセット)
- <B><font COLOR="RED" >[マウス右]</B>....終了


<font COLOR="blue" >

#### 2.動画 YouTube</FONT>
<iframe width="560" height="315" src="https://www.youtube.com/embed/JroqLnyub7k?si=jysdXCyft-o8Ba0w" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

<font COLOR="blue" >

#### 3.コードについて</FONT>
・非同期で表示本数を可変させてます。
( _=Task.RUN() MainPaintSurface()内の表示For値)
・意外と簡単に出来ました。他にも色んな手法があるので作ってみて下さい。
 
<font COLOR="blue" >

#### 4.WPF Sourcecode</FONT>

```c#

/////////////////////////////////////////////////////////////////////////////////////
// Mystify LineDemo WPF+SkiaSharp (skiasharp from nuget install)
// MOUSE_RIGHT...Exit,LEFT...RESTART / [PageUp,Down]...Width CHG / [F]...FullSCREEN.
// (c)inf102 S.H. 2024.
/////////////////////////////////////////////////////////////////////////////////////

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

namespace SkiaSharpSample {

    public partial class MainWindow : Window	{

        ///////////////////////////////////////////////////////////////////
        const   int WAIT=15;                        // 画面更新間隔(mmSec) 2以上
        static  int L_WIDTH=1;                      // 線と線の幅 PgUP/DOWN で可変
        const   int EXT=6;                          // 線の伸び　 一回の更新で伸ばすドット数　少ないと滑らかに増えるが遅くなる
        const   int THICK=1;                        // 線の太さ 2以上は重くなる
        const   int MAINTAIN = 10000;               // 10本引かれた後、その状態を維持する時間(mmSec)
        const   int INC_DEC=140;                    // 1～10本の線を引く間隔(mmSec)
        ///////////////////////////////////////////////////////////////////
        
        const  int MAX_LINE=40;
        static int LD1=0;
        static int LD2=10;
        static int LD3=20;
        static int LDFLG=0;

        // SKIA_LINEDATA
        static SKPaint A_LINE= new SKPaint{
	        Style =  SKPaintStyle.Stroke,
			IsAntialias=true,
            StrokeWidth = THICK
        };
               
        static bool FULL_SCREEN=false;

        // キャンパスサイズ
        static int CAX,CAY;
                   
        // 表示データ クラス
        DSet[] Line_data= new DSet[2];  
        
		public MainWindow()		{
			InitializeComponent();
            
            // 四角
            Line_data[0]=new DSet();
                            
            // 三角
            Line_data[1]=new DSet();
            
        }

        // 表示データ格納クラス
        public class DSet {

            public struct POINT_XY{
                public int START_X,START_Y; // 開始位置
                public int L;               // 開始位置からの半径
                public int RAG;             // 開始位置からの角度
                public int CUR_X,CUR_Y;     // 現在位置
            }
            public  POINT_XY[] XYDATA= new POINT_XY[MAX_LINE];

            public void Dx(){

                System.Threading.Thread.Sleep(1);
                Random rnd = new Random(DateTime.Now.Millisecond);

                int XY,RA;

                ///////////////////////////////////
                RA=rnd.Next(0,360);
 
                if (CAY<CAX ) XY=rnd.Next(CAY/2,CAX/2);
                else XY=rnd.Next(CAX/2,CAY/2);

                for (int d=0;d<10;d++){
                    XYDATA[d].L=1;
                    XYDATA[d].RAG = RA++;
                    XYDATA[d].START_X = XY-25;
                    XYDATA[d].START_Y = XY-110;
                }

                ///////////////////////////////////
                RA=rnd.Next(0,360);
                
                if (CAY<CAX ) XY=rnd.Next(CAY/2,CAX/2);
                else XY=rnd.Next(CAX/2,CAY/2);
                              
                for (int d=10;d<20;d++){
                    XYDATA[d].L=1;
                    XYDATA[d].RAG = RA++;
                    XYDATA[d].START_X = XY-25;
                    XYDATA[d].START_Y = XY-110;
                }
                
                //////////////////////////////////
                RA=rnd.Next(0,360);
            
                if (CAY<CAX ) XY=rnd.Next(CAY/2,CAX/2);
                else XY=rnd.Next(CAX/2,CAY/2);
            
                for (int d=20;d<30;d++){
                    XYDATA[d].L=1;
                    XYDATA[d].RAG = RA++;
                    XYDATA[d].START_X = XY-25;
                    XYDATA[d].START_Y = XY-110;
                }
   
                ////////////////////////////////
                RA=rnd.Next(0,360);
               
                if (CAY<CAX ) XY=rnd.Next(CAY/2,CAX/2);
                else XY=rnd.Next(CAX/2,CAY/2);
               
                for (int d=30;d<40;d++){
                    XYDATA[d].L=1;
                    XYDATA[d].RAG = RA++;
                    XYDATA[d].START_X = XY-25;
                    XYDATA[d].START_Y = XY-110;
                }
                ////////////////////////////////
            }
        }

        private async void Window_Loaded(object sender, RoutedEventArgs e) {
            _=Task.Run ( ()=> {
                while (true){
                    System.Threading.Thread.Sleep (INC_DEC);
                    if (LDFLG == 0){
                        LD1++;
                        LD2++;
                        LD3++;
                    }
                    if (LDFLG == 2) {
                        LD1--;
                        LD2--;
                        LD3--;
                    }

                    // 0本になった
                    if(LD1<=0 && LDFLG == 2) {
                        LD1=0;
                        LD2=10;
                        LD3=20;
                        LDFLG=0;
                        Line_data[0].Dx();
                        Line_data[1].Dx();
                    }

                    // 10本引かれた
                    if(LD1>=10 && LDFLG == 0) {
                        System.Threading.Thread.Sleep (MAINTAIN);
                        LD1=10;
                        LD2=20;
                        LD3=30;
                        LDFLG=2;
                    }
                }
            });

            // MAIN_LOOP
            while(true){
                await Task.Delay (WAIT);
               
                // 表示データ更新、衝突判定
                Col();

                // 描画
                MainCanvas.InvalidateVisual(); 
            }
        }

        // 衝突判定
        void Col(){
            // 次の表示位置算出
            for (int x=0;x<MAX_LINE;x++){        
                for (int m=0;m<2;m++){
                    Line_data[m].XYDATA[x].CUR_X= (int)(Line_data[m].XYDATA[x].START_X+ Line_data[m].XYDATA[x].L*Math.Sin(Line_data[m].XYDATA[x].RAG*Math.PI/180));
                    Line_data[m].XYDATA[x].CUR_Y= (int)(Line_data[m].XYDATA[x].START_Y- Line_data[m].XYDATA[x].L*Math.Cos(Line_data[m].XYDATA[x].RAG*Math.PI/180));
                }

            }

            // 壁衝突判定 衝突の場合は新たな反射角等を設定
            for (int x=0;x<MAX_LINE;x++){
                for (int m=0;m<2;m++){
                    // 上 up
                    if (Line_data[m].XYDATA[x].CUR_Y <= 0){
                        Line_data[m].XYDATA[x].L=1;
                        Line_data[m].XYDATA[x].RAG=180-Line_data[m].XYDATA[x].RAG;
                        Line_data[m].XYDATA[x].START_Y=Line_data[m].XYDATA[x].CUR_Y;
                        Line_data[m].XYDATA[x].START_X=Line_data[m].XYDATA[x].CUR_X;            
                    }
               
                    // 下 down
                    if (Line_data[m].XYDATA[x].CUR_Y >= CAY){
                        Line_data[m].XYDATA[x].L=1;
                        Line_data[m].XYDATA[x].RAG=180-Line_data[m].XYDATA[x].RAG;
                        Line_data[m].XYDATA[x].START_Y=Line_data[m].XYDATA[x].CUR_Y;
                        Line_data[m].XYDATA[x].START_X=Line_data[m].XYDATA[x].CUR_X;
                    }
                    
                    // 右 right
                    if (Line_data[m].XYDATA[x].CUR_X >= CAX){
                        Line_data[m].XYDATA[x].L=1;
                        Line_data[m].XYDATA[x].RAG=360-Line_data[m].XYDATA[x].RAG;
                        Line_data[m].XYDATA[x].START_Y=Line_data[m].XYDATA[x].CUR_Y;
                        Line_data[m].XYDATA[x].START_X=Line_data[m].XYDATA[x].CUR_X;
                    }
                      
                    // 左 left
                    if (Line_data[m].XYDATA[x].CUR_X <= 0){
                        Line_data[m].XYDATA[x].L=1;
                        Line_data[m].XYDATA[x].RAG=360-Line_data[m].XYDATA[x].RAG;
                        Line_data[m].XYDATA[x].START_Y=Line_data[m].XYDATA[x].CUR_Y;
                        Line_data[m].XYDATA[x].START_X=Line_data[m].XYDATA[x].CUR_X;
                    }

                    // 線を伸ばす
                    Line_data[m].XYDATA[x].L+=EXT;

                }
            }
        }
        
        // WindowSize CHG
        private void skiaCanvas_SizeChanged(object sender, SizeChangedEventArgs e) {
            Line_data[0].Dx();
            Line_data[1].Dx();
        }

        // MOUSE L-BUTTON. RESET
        private void MainCanvas_PreviewMouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e) {
            Line_data[0].Dx();
            Line_data[1].Dx();
        }
         
        // MOUSE R-BUTTON. EXIT
        private void MainCanvas_PreviewMouseRightButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e) {
            Application.Current.Shutdown();
        }

        private void Window_KeyDown(object sender, System.Windows.Input.KeyEventArgs e) {

            // FULL SCREEN
            if (e.Key == System.Windows.Input.Key.F) {
                if (FULL_SCREEN == false){
                    FULL_SCREEN=!FULL_SCREEN;
                    this.WindowStyle = WindowStyle.None;
                    this.WindowState = WindowState.Maximized;
                    Line_data[0].Dx();
                    Line_data[1].Dx();
                    return;
                }
                else {
                    FULL_SCREEN=!FULL_SCREEN;
                    this.WindowStyle = WindowStyle.SingleBorderWindow;
                    this.WindowState = WindowState.Normal;
                    this.Height=600;
                    this.Width=1000;
                    Line_data[0].Dx();
                    Line_data[1].Dx();
                }
            }

            if (e.Key == System.Windows.Input.Key.PageUp) {
                L_WIDTH++;
                Line_data[0].Dx();
                Line_data[1].Dx();
            }

            if (e.Key == System.Windows.Input.Key.PageDown) {
                L_WIDTH--;
                if (L_WIDTH <=0 ) L_WIDTH=1;
                Line_data[0].Dx();
                Line_data[1].Dx();
            }
        }
        
        // 描画
        void MainPaintSurface(object sender, SKPaintSurfaceEventArgs args){
            CAX=args.Info.Width;
            CAY=args.Info.Height;

            Random rnd = new System.Random();
            args.Surface.Canvas.Clear(SKColors.Black);
            
            for (int x=0;x<MAX_LINE;x++) {
                // 四角
                A_LINE.Color=new SKColor((byte)rnd.Next(180,255),(byte)rnd.Next(100,200),(byte)rnd.Next(100,200));
                for(int xx = 0; xx < LD1;xx++)  
                    args.Surface.Canvas.DrawLine(Line_data[0].XYDATA[xx].CUR_X, Line_data[0].XYDATA[xx].CUR_Y, Line_data[0].XYDATA[xx+10].CUR_X, Line_data[0].XYDATA[xx+10].CUR_Y, A_LINE);
               
                A_LINE.Color=new SKColor((byte)rnd.Next(100,200),(byte)rnd.Next(180,255),(byte)rnd.Next(100,200));
                for(int xx = 10; xx < LD2; xx++) 
                    args.Surface.Canvas.DrawLine(Line_data[0].XYDATA[xx].CUR_X, Line_data[0].XYDATA[xx].CUR_Y, Line_data[0].XYDATA[xx+10].CUR_X, Line_data[0].XYDATA[xx+10].CUR_Y, A_LINE);
                                
                A_LINE.Color=new SKColor((byte)rnd.Next(100,200),(byte)rnd.Next(100,200),(byte)rnd.Next(180,255));
                for(int xx = 0; xx < LD1; xx++)
                    args.Surface.Canvas.DrawLine(Line_data[0].XYDATA[xx].CUR_X, Line_data[0].XYDATA[xx].CUR_Y, Line_data[0].XYDATA[xx+30].CUR_X, Line_data[0].XYDATA[xx+30].CUR_Y, A_LINE);
          
                A_LINE.Color=new SKColor((byte)rnd.Next(50,255),(byte)rnd.Next(50,255),(byte)rnd.Next(50,255));
                for(int xx = 20; xx < LD3; xx++)
                    args.Surface.Canvas.DrawLine(Line_data[0].XYDATA[xx].CUR_X, Line_data[0].XYDATA[xx].CUR_Y, Line_data[0].XYDATA[xx+10].CUR_X, Line_data[0].XYDATA[xx+10].CUR_Y, A_LINE);

               
                // 三角
                A_LINE.Color=new SKColor((byte)rnd.Next(00,255),(byte)rnd.Next(00,255),(byte)rnd.Next(0,255));
                for(int xx = 0; xx < LD1; xx++)
                    args.Surface.Canvas.DrawLine(Line_data[1].XYDATA[xx].CUR_X, Line_data[1].XYDATA[xx].CUR_Y,Line_data[1].XYDATA[xx+10].CUR_X, Line_data[1].XYDATA[xx+10].CUR_Y, A_LINE);
               
                A_LINE.Color=new SKColor((byte)rnd.Next(00,255),(byte)rnd.Next(0,255),(byte)rnd.Next(00,255));
                for(int xx = 10; xx < LD2; xx++)
                    args.Surface.Canvas.DrawLine(Line_data[1].XYDATA[xx].CUR_X, Line_data[1].XYDATA[xx].CUR_Y,Line_data[1].XYDATA[xx+10].CUR_X, Line_data[1].XYDATA[xx+10].CUR_Y, A_LINE);                             
              
                A_LINE.Color=new SKColor((byte)rnd.Next(00,255),(byte)rnd.Next(00,255),(byte)rnd.Next(0,255));
                for(int xx = 0; xx < LD1; xx++)
                    args.Surface.Canvas.DrawLine(Line_data[1].XYDATA[xx].CUR_X, Line_data[1].XYDATA[xx].CUR_Y,Line_data[1].XYDATA[xx+20].CUR_X, Line_data[1].XYDATA[xx+20].CUR_Y, A_LINE);
                

            }
        }
    }
}
          
```

<font COLOR="blue" >

#### 5.WPF XAML</FONT>

```xml
<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="LineDemo WPF+SkiaSharp. PageUp,Down=SpaceChenage , MOUSE-LEFT=Resart , RIGHT=Shitdown ,F=FullScreen" Loaded="Window_Loaded" Height="800" Width="1200"  KeyDown="Window_KeyDown" >

    <Grid  >
        <wpf:SKElement x:Name="MainCanvas" PaintSurface="MainPaintSurface" Grid.Column="0" Grid.Row="0" SizeChanged="skiaCanvas_SizeChanged" PreviewMouseLeftButtonDown="MainCanvas_PreviewMouseLeftButtonDown" PreviewMouseRightButtonDown="MainCanvas_PreviewMouseRightButtonDown" />
    </Grid>

</Window>
```