CG Library SkiaSharp「xeyes」

<FONT COLOR="BLUE">

### XWindowSystemクライアント xeyesのWindows版</FONT>
<font color="GREEN">

- マウスのある方向に瞳が向く古参クライアントの一つ、xeyesのWindows版。<BR>
- 同心円状に回転させる為サイズによっては瞳が目からはみ出る事がある。<br>
- 他システムを開発するにあたり必要な手法を確認する為に試作。xeyes風にしたもの。<br>
- (もっとまともなWin版が既に公開されている様です)<BR><FONT COLOR="GREEN">
  
</FONT>
<B>

#### ・xeyes (wikipediaより)</B>
![X11_ssh_tunnelling.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2821882/b3973abc-487c-1ae1-1183-d47c87837d61.png)

<B>

#### ・weyes (27インチ WQHD 2560x1440,自作機 i5-7600K 3.80GHz)</B>
<iframe width="560" height="315" src="https://www.youtube.com/embed/s_UczRvpQkI?si=3uozUwFMDZ8k57wQ" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
<FONT COLOR="BLUE"><BR>

#### 1.眼の中心スクリーン座標(モニタ左上を0,0とした絶対値)XY値と角度取得が必要<br></FONT>
1.Appのスクリーン座標取得 (タイトルバー除く左上)
```c#
LOCAL_W=this.PointToScreen(new System.Windows.Point(0, 0));
```
2.クライアント座標、左4/1が左の眼のX値、2/1がY値
  クライアント領域の特定の位置の算出は色々工夫する必要がある。
```c#           
int XA=(int)this.Width/4;
int YA=(int)this.Height/2;
```

3.左目の中心は1.と2.で取得した値を元に算出(-6,-25は補正値)
```c#
LX=(int)(LOCAL_W.X+XA-6);
LY=(int)(LOCAL_W.Y+YA-25);
``` 

4.同様に右目の中心X値は、Appの横幅 4/1の3倍+スクリーン座標のAppの位置
```c#
RX=(int)(LOCAL_W.X+(int) (this.Width/4)*3-27);
RY=(int)(LOCAL_W.Y+YA-16);
```

5.中心位置が出た後は、目の中心からマウスまでの角度を算出
(逆正弦atan2（アークタンジェント2）によりベクトルの角度(θ)を求める)
```c#
protected double getR(double x, double y, double x2, double y2) {
    double ra = Math.Atan2(y2 - y,x2 - x);
    return ra*180d/3.14;
}
```

6.中心位置と角度取得後、cos,sinで瞳のXY値を算出 (RAは5.で求めた角度)
```c#
double XX=(float)(this.Width /4-7 + (this.Width/8)* Math.Sin ((RA-270) * Math.PI/180));
double YY=(float)(this.Height/2-7 - (this.Width/8)* Math.Cos ((RA-270) * Math.PI/180));
```


<FONT COLOR="BLUE">

### 2.WPF(C#)</FONT>

```c#
///////////////////////////////////////
// weyes (c)inf102 2024
// WPF+SkiaSharp
//////////////////////////////////////

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

namespace SF {

    public partial class MainWindow : Window {
       
        // マウス位置
        static int MX,MY;

        // 目の中心
        static int LX,LY,RX,RY;

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

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

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

            // 自ウィンドウのワールド座標 左上(タイトルバー除く)
            System.Windows.Point LOCAL_W=this.PointToScreen(new System.Windows.Point(0, 0));

            while (true){
                
                try {
                    LOCAL_W=this.PointToScreen(new System.Windows.Point(0, 0));
                } catch { };

                int XA=(int)this.Width/4;
                int YA=(int)this.Height/2;
              
                // ワールドスクリーン マウス位置
                System.Drawing.Point p = System.Windows.Forms.Cursor.Position;
                MX=p.X-5;
                MY=p.Y-15;

                // 目の中心
                double  RA=getR(LX,LY,MX,MY);
                LX=(int)(LOCAL_W.X+XA-6);
                LY=(int)(LOCAL_W.Y+YA-25);
            
                RX=(int)(LOCAL_W.X+(int) (this.Width/4)*3-27);
                RY=(int)(LOCAL_W.Y+YA-16); //25

                Title ="weyes LX"+ LX.ToString()+" LY" + LY.ToString()+" RX"+RX.ToString()+" RY"+RY.ToString()+" LOCAL-MX"+ MX.ToString()+" LOCAL-MY"+MY.ToString()+" RA="+RA.ToString();
                
                Sf2.InvalidateVisual();
                
                await Task.Delay(5);

            }
        }

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

        }

        // 目の輪郭(枠)　 リサイズイベントハンドラで呼び出した方がいいかも
        void SfPaintSurface(object sender, SKPaintSurfaceEventArgs args) {
  
            SKSurface surface = args.Surface;
            SKCanvas canvas = surface.Canvas;

            SKPaint skPaint = new SKPaint(){
	            Style = SKPaintStyle.Stroke,
	            Color = SKColors.Black,
	            StrokeWidth = 10,
	            IsAntialias = true,
            };

            SKRect skRectangle = new SKRect();
            skRectangle.Size = new SKSize( (float)(this.Width*0.4),(float)(this.Height*0.66));
            
            int a= ((int)this.Height- (int)skRectangle.Height)/2;
            int b= ((int)this.Width - (int)skRectangle.Width) /8-10;
                       
            skRectangle.Location = new SKPoint((b),(float)(a-20));
            canvas.DrawOval(skRectangle, skPaint);
         
            skRectangle.Size = new SKSize( (float)(this.Width*0.4),(float)(this.Height*0.66));
            a= ((int)this.Height- (int)skRectangle.Height)/2;
           
            skRectangle.Location = new SKPoint ((float)this.Width/2,(float)(a-20));
            canvas.DrawOval(skRectangle, skPaint);
     
        }

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

            SKPaint skPaint = new SKPaint(){
	            Style = SKPaintStyle.Stroke,
	            Color = SKColors.Black,
	            StrokeWidth = 15,
	            IsAntialias = true,
            };

           canvas.Clear();

            ///////////////////////////////////////////////////////////////////////////////
            // L          
            double RA=getR(LX,LY,MX+18,MY-3);
            double XX=(float)(this.Width /4-7 + (this.Width/8)* Math.Sin ((RA-270) * Math.PI/180));
            double YY=(float)(this.Height/2-7 - (this.Width/8)* Math.Cos ((RA-270) * Math.PI/180));
            canvas.DrawCircle( (float)XX,(float)YY,10,skPaint);

            ///////////////////////////////////////////////////////////////////////////////
            // R
            RA=getR(RX,RY,MX-17,MY+7);
            XX=(float)((this.Width /4*3-7) + (this.Width/8)* Math.Sin ((RA-270) * Math.PI/180));
            YY=(float)((this.Height/2-7)   - (this.Width/8)* Math.Cos ((RA-270) * Math.PI/180));
            canvas.DrawCircle( (float)XX,(float)YY,10,skPaint);
                
            ///////////////////////////////////////////////////////////////////////////////

        }
    }
}

```

<FONT COLOR="BLUE">

### 3.WPF (XAML)</FONT>

```xml
<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="" Height="255" Width="255" Loaded="Window_Loaded" 
	WindowState="Normal"  >

    <Grid>
        <wpf:SKElement x:Name="Sf"    PaintSurface="SfPaintSurface"  />
        <wpf:SKElement x:Name="Sf2"    PaintSurface="SfPaintSurface2"/>
    </Grid>
</Window>
```