CG Library SkiaSharp「トライアングル」


<FONT COLOR="BLUE">

### 二次元CGライブラリSkiaSharp デモ

</FONT>

::: note
- アナログ時計見てて、ふと全部に線引いたらどうなるんだろうと思い作ってみました。
- 円周上に定間隔でポイントを打ち、他のポイントまで線を引く、というものです。
- WPFはハードウェアアクセラレートされたDirect3Dで描画します。この2DデモもDirect3Dで描画されてます。その為、GDI/GDI+に比べてMPUの負荷が少ないです。
- 動作不良があったので直しました。
:::

角度15度、24角、総線数276本
![1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2821882/e45a158f-98be-f148-0b69-87b7bb937cd2.jpeg)

角度5度、75角、総線数2775本
![1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2821882/b412f2f5-664c-4ace-aba6-d16c8154d1fa.jpeg)



- 左上に角度、画数、描画している本数が表示されます
- 20度なら18角数になります。(360割る20で18)
- 直線の総本数は18画数なら-1して17x18割る2で153本となります
- 円描画は下の正方形リサイズチェックボックスが入ってないと描画しません
(正方形リサイズチェックボックスはリサイズした時に正方形を維持します)

<font COLOR="blue" >

### 1.動画 WQHD+WUXGA,Core i5 7600K(自作機),Win10</FONT>
<iframe width="560" height="315" src="https://www.youtube.com/embed/sIPhYnoj2ls?si=7VwW27ngY-4nsNmO" 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>

<iframe width="560" height="315" src="https://www.youtube.com/embed/dYGBHb66i3U?si=oZ_ZIp0ID8KIFoBb" 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>

参考:SkiaSharp導入はこちら参照

https://qiita.com/ger531/items/e6cc6d8590b929f5e54e

<font COLOR="blue" >

### 2.WPF C# Code</FONT>
```c#
//////////////////////////////////////////////
// SkiaSharp Triangle Demo R1.00
// (c) inf102 2024.
// WPF And DotNet Framework. 
//////////////////////////////////////////////

using SkiaSharp;
using SkiaSharp.Views.Desktop;
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media.Animation;

namespace SkiaSharpSample {
    
    public partial class MainWindow : Window	{
    
        // NoA >= NoB にする事
        const int NoA=1200; //　初期値1200 [1200～200]  1200==三角形1つ　/ 小さい値にすると最収束時に細かい図形になる
        const int NoB=30;   //    初期値30   [30～1200]   小さいほど細かくなる

        const int ANG=2;    // 初期値2    [角度増加数 1～5]  角度の増え方　2==2,4,6,8,10度....と増える. 値が大きいとあっという間に最大の細かさになってしまう
        static int R=NoA;   //  始動時の細かさ  1200の場合、3600/1200=3 で最初に3角形1つが表示される /内部で可変される

        struct POINT_XY{
            public int x,y;
        }

        static int LINE; // 表示本数カウンタ
        static bool FLG=false;
        Random rnd = new Random();
        static List<POINT_XY> LISTXY = new List<POINT_XY>();

        //////////////////////////////////////////////////////////////////////////////////////////////////
        // 直線用
        static SKPaint skPaintLine = new SKPaint{
	        Style = SKPaintStyle.Stroke,
	        IsAntialias = true,
	    };

        // 円用
        static SKPaint skPaintCircle = new SKPaint{
	        Style = SKPaintStyle.Stroke,
	        IsAntialias = true,
	        StrokeWidth = (float)2,
            Color=new SKColor( (byte)00,(byte)200,(byte)000)
        };

        // 円周上の赤丸
        static SKPaint skPaintCirclePoint = new SKPaint() {
            FilterQuality = SKFilterQuality.Low,
            IsAntialias = false,
            Color = SKColors.Red
        };

        SKPaint skPaintTxt = new SKPaint() {
            FilterQuality = SKFilterQuality.High,
            IsAntialias = true,
            TextSize = 20f,
            Color = SKColors.Green

        };

        ///////////////////////////////////////////////////////////////////////////////////////////////////

		public MainWindow()		{
			InitializeComponent();
        
            // MAIN LOOP
            _=MainLoop();
        }

        // 円周上の角のXY値を算出
        void GetXY (SKPaintSurfaceEventArgs args){
                    
            // 構造体配列
            POINT_XY A = new POINT_XY();
            
            LISTXY.Clear();

            double x,y;
            for (int v=0;v<3600;v=v+R){
                x = RetX(v);
                y = RetY(v);

                // 円周上の角マーク(●)
                if (RA_S.Value >=3) args.Surface.Canvas.DrawCircle( (float)x,(float)y, (int)RA_S.Value,skPaintCirclePoint);

                A.x=(int)x;
                A.y=(int)y;

                LISTXY.Add(A);
            }
            
            ////////////////////////////////////////////////////////////////////////////
            double RetX(int RS){
                return ( (skiaCanvas.CanvasSize.Width  /2) + (skiaCanvas.CanvasSize.Width/2-10) * Math.Sin(RS*3.14/1800) );
            }

            double RetY(int RS){
                return ( (skiaCanvas.CanvasSize.Height /2) - (skiaCanvas.CanvasSize.Height /2-10) * Math.Cos(RS*3.14/1800) );

            }            
            ////////////////////////////////////////////////////////////////////////////

        }
        
        // MAIN LOOP
        public async Task<AsyncVoidMethodBuilder> MainLoop() {

            while (true){
                await Task.Delay((int)SPEED_S.Value);

                if ((string)ENABLE_BTN.Content ==(string)"停止中(_E)") continue;

                skiaCanvas.InvalidateVisual(); 
                Title="角度 "+ (R/10).ToString() + "度  " +  LISTXY.Count.ToString() +"角数 描画数 " + LINE.ToString()+"本";

                if (FLG ==false ){
                    R += ANG;
                    if ( R>NoA ) FLG =!FLG;
                }
                
                if (FLG ==true) {
                    R -= ANG;
                    if ( R<NoB ) FLG=!FLG;
                }
            }
        }

        // 直線描画
		void PaintSurface(object sender, SKPaintSurfaceEventArgs args){

           // SKImageInfo info = args.Info;
            SKSurface surface = args.Surface;
            SKCanvas canvas = surface.Canvas;

            // 画面クリア
           canvas.Clear(SKColors.Black);
            
            // LINE 太さ
            skPaintLine.StrokeWidth=(float)F_S.Value;

            args.Surface.Canvas.DrawText("Degree ="+ (R/10).ToString(), 10, 25, skPaintTxt);
            args.Surface.Canvas.DrawText("Corner  ="+ LISTXY.Count.ToString(), 10, 45, skPaintTxt);
            args.Surface.Canvas.DrawText("Line      ="+  (LINE/1).ToString(), 10, 65, skPaintTxt);

            // 円周上の角XY取得
            GetXY (args);
           
            // 円描画
            if ( CIRCLE_ON.IsChecked == true && SQU_ON.IsChecked == true){
                canvas.DrawCircle ( (float)skiaCanvas.CanvasSize.Width/2,skiaCanvas.CanvasSize.Height/2,skiaCanvas.CanvasSize.Width/2-10,skPaintCircle);
            }

            LINE=0;
            for (int vx=0;vx<LISTXY.Count-1;vx++){
                if (AUTO_CHK2.IsChecked == true) skPaintLine.Color=new SKColor( (byte)rnd.Next(0, 255),(byte)rnd.Next(0, 255),(byte)rnd.Next(0, 255));              
                for (int dx=vx+1;dx< LISTXY.Count;dx++){
                    if (AUTO_CHK1.IsChecked == true) {
                        skPaintLine.Color=new SKColor( (byte)rnd.Next(0, 255),(byte)rnd.Next(0, 255),(byte)rnd.Next(0, 255));
                    }
                    else{ 
                        if (AUTO_CHK3.IsChecked == true) skPaintLine.Color=new SKColor( (byte)R_S.Value,(byte)G_S.Value,(byte)B_S.Value);
                    }

                    canvas.DrawLine (LISTXY[vx].x,LISTXY[vx].y ,LISTXY[dx].x,LISTXY[dx].y,skPaintLine);
                    LINE++; 
                }   
            }
        }

        private void Button_Click2(object sender, RoutedEventArgs e) {
            if ((string)ENABLE_BTN.Content ==(string)"稼働中(_E)" ) {
                ENABLE_BTN.Content =(string)"停止中(_E)";
                return;
            }
            
            if ((string)ENABLE_BTN.Content ==(string)"停止中(_E)" ) {
                ENABLE_BTN.Content =(string)"稼働中(_E)";
                return;
            }
        }

        private void SPEED_S_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e) {
            SPEED_T.Text= "速度 " + ((int)SPEED_S.Value).ToString();
        }

        private void R_S_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e) {
            R_TEXT.Text= "R " + ((int)R_S.Value).ToString();
        }

        private void G_S_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e) {
            G_TEXT.Text= "G " + ((int)G_S.Value).ToString();
        }

        private void B_S_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e) {
            B_TEXT.Text= "B " + ((int)B_S.Value).ToString();
        }

        private void F_S_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e) {
            F_TEXT.Text= "太さ " + ((int)F_S.Value).ToString();
        }

        private void skiaCanvas_SizeChanged(object sender, SizeChangedEventArgs e) {
            if (SQU_ON.IsChecked == true) {
                Height=Width-130;
            }
        }
    }
}
```

<font COLOR="blue" >

### 3.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="SkiaSharp" Loaded="Window_Loaded" Height="780" Width="910" >

    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="195*"/>
            <ColumnDefinition Width="150"/>
        </Grid.ColumnDefinitions>

        <StackPanel  Grid.Column="1"  Grid.Row="0" Orientation="Vertical" Margin="0,0,0,0" Background="DarkCyan">

            <StackPanel Orientation="Horizontal" Margin="45,20,2,0" >
                <TextBlock Text="色指定" FontSize="16" Foreground="White"  />

            </StackPanel>

            <StackPanel Orientation="Horizontal" >

                <RadioButton x:Name="AUTO_CHK1" Content="自動可変[A]" FontSize="15" Foreground="White" Margin="8,10,0,0" IsChecked="True" GroupName="COLOR"/>
            </StackPanel>

            <StackPanel Orientation="Horizontal" >

                <RadioButton  x:Name="AUTO_CHK2" Content="自動可変[B}" FontSize="15" Foreground="White" Margin="8,5,0,0" GroupName="COLOR"/>
            </StackPanel>

            <StackPanel Orientation="Horizontal" >

                <RadioButton  x:Name="AUTO_CHK3" Content="色手動設定" FontSize="15" Foreground="White" Margin="8,5,0,0" GroupName="COLOR"/>
            </StackPanel>

            <StackPanel Orientation="Horizontal" Margin="10,0,2,2" >
                <TextBlock x:Name="R_TEXT" Text="R" FontSize="16" HorizontalAlignment="Left" Foreground="White"/>
            </StackPanel>

            <StackPanel Orientation="Horizontal" >
                <Slider x:Name="R_S" Width="141" Maximum="255" Minimum="0" SmallChange="1" Value="40" Margin="7,0,0,0" ValueChanged="R_S_ValueChanged">
                </Slider>
            </StackPanel>

            <StackPanel Orientation="Horizontal" Margin="10,4,2,2">
                <TextBlock x:Name="G_TEXT" Text="G" FontSize="16" Foreground="White"/>
            </StackPanel>

            <StackPanel Orientation="Horizontal" >
                <Slider x:Name="G_S" Width="141" Maximum="255" Minimum="0" SmallChange="1" Value="90" Margin="5,0,0,0" ValueChanged="G_S_ValueChanged">

                </Slider>
            </StackPanel>

            <StackPanel Orientation="Horizontal" Margin="10,4,2,2">
                <TextBlock x:Name="B_TEXT" Text="B" FontSize="16" Foreground="White"/>
            </StackPanel>

            <StackPanel Orientation="Horizontal" >
                <Slider x:Name="B_S" Width="141" Maximum="255" Minimum="0" SmallChange="1" Value="240" Margin="5,0,0,0" ValueChanged="B_S_ValueChanged" >
                </Slider>
            </StackPanel>
            
            <StackPanel Orientation="Horizontal" Margin="10,10,2,2">
                <TextBlock x:Name="RA_TEcvXT" Text="----------------------------------------" FontSize="8" Foreground="White" FontWeight="Bold"/>
            </StackPanel>

            <StackPanel Orientation="Horizontal" Margin="10,15,2,2">
                <TextBlock x:Name="RA_TEXT" Text="角表示大きさ" FontSize="16" Foreground="White"/>
            </StackPanel>

            <StackPanel Orientation="Horizontal" >
                <Slider x:Name="RA_S" Width="141" Maximum="6" Minimum="0" Value="2" Margin="7,0,0,0" TickPlacement="TopLeft">
                </Slider>
            </StackPanel>

            <StackPanel Orientation="Horizontal" Margin="10,15,2,2">
                <TextBlock x:Name="F_TEXT" Text="太さ" FontSize="16" Foreground="White"/>
            </StackPanel>

            <StackPanel Orientation="Horizontal" >
                <Slider x:Name="F_S" Width="141" Maximum="5" Minimum="1" SmallChange="0.5" Value="1" Margin="7,0,0,0" LargeChange="0.5" ValueChanged="F_S_ValueChanged" TickPlacement="TopLeft">

                </Slider>
            </StackPanel>

            <StackPanel Orientation="Horizontal" Margin="10,20,2,2">
                <TextBlock x:Name="SPEED_T" Text="速度" FontSize="16" Foreground="White"/>
            </StackPanel>

            <StackPanel Orientation="Horizontal" >
                <Slider x:Name="SPEED_S" Width="141" Maximum="180" Minimum="3" SmallChange="1" Value="3" Margin="7,0,0,0" ValueChanged="SPEED_S_ValueChanged">
                </Slider>
            </StackPanel>

            <StackPanel Orientation="Horizontal" >
                <CheckBox x:Name="CIRCLE_ON" Content="円描画" FontSize="15" Foreground="White" Margin="8,30,0,0" />
            </StackPanel>

            <StackPanel Orientation="Horizontal" >
                <CheckBox x:Name="SQU_ON" Content="正方形ﾘｻｲｽﾞ" FontSize="15" Foreground="White" Margin="8,30,0,0" IsChecked="True"  />
            </StackPanel>

            <StackPanel Orientation="Vertical" Margin="-9,20,0,0" Width="101">
                <Button x:Name="ENABLE_BTN" Width="100" Height="40" Content="稼働中(_E)" FontSize="17" Click="Button_Click2"></Button>
            </StackPanel>
        </StackPanel>
        <wpf:SKElement x:Name="skiaCanvas" PaintSurface="PaintSurface" Grid.Column="0" Grid.Row="0" SizeChanged="skiaCanvas_SizeChanged" />
    </Grid>
</Window>

```