20200103のC#に関する記事は20件です。

C#で画像処理:透明な領域を切り取る

欲しいもの

物体のまわりに透明な領域がある画像から
1.jpg
物体が描かれている領域だけを
2.png
切り出してファイルに保存したい。
3.png

※画像はいらすとやの黒はんぺん

やること

  1. 画像ファイルを読み込む
  2. 透明でない領域を検出する
  3. 画像の一部を切り出す
  4. 画像を保存する

0. 準備

画像処理には System.Drawing 名前空間の API を使うので using しておく。

using System.Drawing;

.NET Framework の場合、System.Drawing アセンブリへの参照を追加する必要あり。

.NET Core の場合、System.Drawing.Common NuGet パッケージのインストールが必要。

dotnet add package System.Drawing.Common

Linux の場合、libgdiplus も必要になるのでインストールしておく。

sudo apt install libgdiplus

1. 画像ファイルを読み込む

画像ファイルを読むには、System.Drawing.Bitmap クラスを使う。

var bmp = new Bitmap(filename);

Bitmapクラスは BMP, GIF, EXIF, JPG, PNG, TIFF 形式をサポートしている。

ただし今回は色に透明(アルファ値:不透明度)を含む画像が必要なので、下記のようなチェックを入れておく。

if (bmp.PixelFormat != PixelFormat.Format32bppArgb)
{
    Console.WriteLine($"Not Format32bppArgb: {bmp.PixelFormat}");
    return;
}

2. 透明でない領域を検出する

Bitmapオブジェクトの透明でない領域の矩形を検出する。

4.png

ピクセルの色の検出は、画像をバイト列に変換して値を参照することでできる。

PixelFormat.Format32bppArgb フォーマットでは1ピクセル4バイト、BGRAの順に1バイトずつ入る。※リトルエンディアンの場合

例えば次のようなサイズ 2x2 の画像があった場合、
6.png
こんなバイト列になる。
7.png

透明でないピクセルの上、下、左、右(y0, y1, x0, x1)の座標を検出し、Rectangleオブジェクトを返すメソッド。

static Rectangle GetRect(Bitmap bmp)
{
    // 画像のピクセルを byte[] にコピーする
    var rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
    var bmpData = bmp.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadOnly, bmp.PixelFormat);
    var bytes = Math.Abs(bmpData.Stride) * bmp.Height;
    var rgbValues = new byte[bytes];
    System.Runtime.InteropServices.Marshal.Copy(bmpData.Scan0, rgbValues, 0, bytes);
    bmp.UnlockBits(bmpData);

    int x0 = bmp.Width;
    int y0 = bmp.Height;
    int x1 = 0;
    int y1 = 0;

    // 透明でないピクセルを探す
    for (int i = 3; i < rgbValues.Length; i += 4)
    {
        // Aの値が0なら透明ピクセル
        if (rgbValues[i] != 0)
        {
            int x = i / 4 % bmp.Width;
            int y = i / 4 / bmp.Width;

            if (x0 > x) x0 = x;
            if (y0 > y) y0 = y;
            if (x1 < x) x1 = x;
            if (y1 < y) y1 = y;
        }
    }

    return new Rectangle(x0, y0, x1 - x0, y1 - y0);
}

ちなみに、Bitmap クラスにはピクセルの色を取得する GetPixel(int x, int y) なんてメソッドも用意されているが、クソ遅いので LockBits を使って高速化している。

3. 画像の一部を切り出す

Bitmap オブジェクトから Rectangle で指定した領域を切り出し、新しい Bitmap オブジェクトを生成するメソッド。

Bitmap Crop(Bitmap bmp, Rectangle rect)
{
    var newbmp = new Bitmap(rect.Width, rect.Height);
    using (var g = Graphics.FromImage(newbmp))
    {
        g.DrawImage(bmp, 0, 0, rect, GraphicsUnit.Pixel);
    }
    return newbmp;
}

4. 画像を保存する

newbmp.Save(filename);

ちょっと分かりづらいが、はんぺんの周りの領域が狭くなっている food_kuro_hanpen2.png が生成された。
5.png

ソースコード全体

GitHub

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

cscの作法 その31

概要

cscの作法、調べてみた。
練習問題やってみた。

練習問題

真理値表を表示せよ。

写真

image

サンプルコード

using System;
using System.Windows.Forms;
using System.Drawing;
using System.Data.OleDb;
using System.Data;
using System.IO;
using System.Text;
using System.Collections.Generic;

class form1: Form {
    form1() {
        Text = "DataGridView";
        ClientSize = new Size(480, 300);
        DataGridView gd0 = new DataGridView();
        gd0.Location = new Point(50, 20);
        gd0.Width = 400;
        Controls.AddRange(new Control[] {
            gd0
        });
        gd0.ColumnCount = 3;
        gd0.Columns[0].HeaderText = "A";
        gd0.Columns[1].HeaderText = "B";
        gd0.Columns[2].HeaderText = "X";
        int a = 0;
        int b = 0;
        int x;
        x = a ^ b;
        gd0.Rows.Add(new object[] {
            a.ToString(),
            b.ToString(),
            x.ToString()
        });
        a = 1;
        x = a ^ b;
        gd0.Rows.Add(new object[] {
            a.ToString(),
            b.ToString(),
            x.ToString()
        });
        a = 0;
        b = 1;
        x = a ^ b;
        gd0.Rows.Add(new object[] {
            a.ToString(),
            b.ToString(),
            x.ToString()
        });
        a = 1;
        x = a ^ b;
        gd0.Rows.Add(new object[] {
            a.ToString(),
            b.ToString(),
            x.ToString()
        });
    }
    [STAThread]
    public static void Main() {
        Application.Run(new form1());
    }
}





以上。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

cscの作法 その30

概要

cscの作法、調べてみた。
練習問題やってみた。

練習問題

datagridのセルクリックでカレンダーを表示せよ。

写真

image

サンプルコード

using System;
using System.Windows.Forms;
using System.Drawing;
using System.Data.OleDb;
using System.Data;
using System.IO;
using System.Text;
using System.Collections.Generic;

class form1: Form {
    MonthCalendar cal1;
    DataGridView gd0;
    int row;
    int coloum;
    form1() {
        Text = "DataGrid";
        ClientSize = new Size(580, 400);
        gd0 = new DataGridView();
        gd0.Location = new Point(50, 20);
        gd0.Width = 500;
        gd0.Height = 300;
        Controls.AddRange(new Control[] {
            gd0
        });
        Button btn1 = new Button();
        btn1.Location = new Point(50, 330);
        btn1.Text = "load";
        btn1.Click += btn1_Click;
        Controls.AddRange(new Control[] {
            btn1
        });
        Button btn2 = new Button();
        btn2.Location = new Point(150, 330);
        btn2.Text = "save";
        btn2.Click += btn2_Click;
        Controls.AddRange(new Control[] {
            btn2
        });
        gd0.CellClick += new DataGridViewCellEventHandler(OnChanged);
    }
    private void OnChanged(object sender, DataGridViewCellEventArgs e) {
        int ri = e.RowIndex;
        int ci = e.ColumnIndex;
        if (ci == 0)
        {
            this.cal1 = new System.Windows.Forms.MonthCalendar();
            this.cal1.DateSelected += new System.Windows.Forms.DateRangeEventHandler(this.cal1_DateSelected);
            gd0.Controls.Add(cal1);
            cal1.Show();
            coloum = e.ColumnIndex;
            row = e.RowIndex;
        }
    }
    private void cal1_DateSelected(object sender, System.Windows.Forms.DateRangeEventArgs e) {
        gd0[coloum, row].Value = e.Start.ToShortDateString();
        (sender as MonthCalendar).Dispose();
    }
    void btn1_Click(object sender, System.EventArgs e) {
        Encoding enc = Encoding.GetEncoding("Shift_JIS");
        string file = "test1.csv";
        StreamReader reader = new StreamReader(file, enc);
        string temp = reader.ReadToEnd();
        string[] lines = temp.Split(new string[] {
            "\r\n"
        }, StringSplitOptions.None);
        string[] spLine;
        spLine = lines[0].Split(new char[] {
            ',',
            '\t'
        }, StringSplitOptions.None);
        gd0.ColumnCount = spLine.Length;
        for (int i = 0; i < spLine.Length; i++)
        {
            gd0.Columns[i].HeaderText = spLine[i].Trim('"');
        }
        DataGridViewRow[] rows = new DataGridViewRow[lines.Length - 1];
        for (int i = 1; i < lines.Length; i++)
        {
            spLine = lines[i].Split(new char[] {
                ',' ,
                '\t'
            }, StringSplitOptions.None);
            for (int j = 0; j < spLine.Length; j++)
            {
                spLine[j] = spLine[j].Trim('"');
            }
            DataGridViewRow row = new DataGridViewRow();
            row.CreateCells(gd0);
            row.SetValues(spLine);
            rows[i - 1] = row;
        }
        gd0.Rows.AddRange(rows);
        MessageBox.Show("ok");
    }
    void btn2_Click(object sender, System.EventArgs e) {
        StreamWriter sw = new StreamWriter("test2.csv", false);
        int rowCount = gd0.Rows.Count;
        if (gd0.AllowUserToAddRows == true)
        {
            rowCount = rowCount - 1;
        }
        for (int i = 0; i < rowCount; i++)
        {
            List<String> strList = new List<String>();
            for (int j = 0; j < gd0.Columns.Count; j++)
            {
                String s1 = (String) gd0[j, i].Value;
                String s2 = Convert.ToString(gd0[j, i].Value);
                strList.Add(s2);
            }
            String[] strArray = strList.ToArray();
            String strCsvData = String.Join(",", strArray);
            sw.WriteLine(strCsvData);
        }
        sw.Close();
        MessageBox.Show("ok");
    }
    [STAThread]
    public static void Main() {
        Application.Run(new form1());
    }
}





以上。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

cscの作法 その29

概要

cscの作法、調べてみた。
練習問題やってみた。

練習問題

datagridをcsvに書き込め。

サンプルコード

using System;
using System.Windows.Forms;
using System.Drawing;
using System.Data.OleDb;
using System.Data;
using System.IO;
using System.Text;
using System.Collections.Generic;

class form1: Form {
    DataGridView gd0;
    form1() {
        Text = "DataGrid";
        ClientSize = new Size(580, 400);
        gd0 = new DataGridView();
        gd0.Location = new Point(50, 20);
        gd0.Width = 500;
        gd0.Height = 300;
        Controls.AddRange(new Control[] {
            gd0
        });
        Button btn1 = new Button();
        btn1.Location = new Point(50, 330);
        btn1.Text = "load";
        btn1.Click += btn1_Click;
        Controls.AddRange(new Control[] {
            btn1
        });
        Button btn2 = new Button();
        btn2.Location = new Point(150, 330);
        btn2.Text = "save";
        btn2.Click += btn2_Click;
        Controls.AddRange(new Control[] {
            btn2
        });
    }
    void btn1_Click(object sender, System.EventArgs e) {
        Encoding enc = Encoding.GetEncoding("Shift_JIS");
        string file = "test1.csv";
        StreamReader reader = new StreamReader(file, enc);
        string temp = reader.ReadToEnd();
        string[] lines = temp.Split(new string[] {
            "\r\n"
        }, StringSplitOptions.None);
        string[] spLine;
        spLine = lines[0].Split(new char[] {
            ',',
            '\t'
        }, StringSplitOptions.None);
        gd0.ColumnCount = spLine.Length;
        for (int i = 0; i < spLine.Length; i++)
        {
            gd0.Columns[i].HeaderText = spLine[i].Trim('"');
        }
        DataGridViewRow[] rows = new DataGridViewRow[lines.Length - 1];
        for (int i = 1; i < lines.Length; i++)
        {
            spLine = lines[i].Split(new char[] {
                ',' ,
                '\t'
            }, StringSplitOptions.None);
            for (int j = 0; j < spLine.Length; j++)
            {
                spLine[j] = spLine[j].Trim('"');
            }
            DataGridViewRow row = new DataGridViewRow();
            row.CreateCells(gd0);
            row.SetValues(spLine);
            rows[i - 1] = row;
        }
        gd0.Rows.AddRange(rows);
        MessageBox.Show("ok");
    }
    void btn2_Click(object sender, System.EventArgs e) {
        StreamWriter sw = new StreamWriter("test2.csv", false);
        int rowCount = gd0.Rows.Count;
        if (gd0.AllowUserToAddRows == true)
        {
            rowCount = rowCount - 1;
        }
        for (int i = 0; i < rowCount; i++)
        {
            List<String> strList = new List<String>();
            for (int j = 0; j < gd0.Columns.Count; j++)
            {
                String s1 = (String) gd0[j, i].Value;
                String s2 = Convert.ToString(gd0[j, i].Value);
                strList.Add(s2);
            }
            String[] strArray = strList.ToArray();
            String strCsvData = String.Join(",", strArray);
            sw.WriteLine(strCsvData);
        }
        sw.Close();
        MessageBox.Show("ok");
    }
    [STAThread]
    public static void Main() {
        Application.Run(new form1());
    }
}






以上。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

cscの作法 その28

概要

cscの作法、調べてみた。
練習問題やってみた。

練習問題

datagridにcsvを読み込め。

写真

image

サンプルコード

using System;
using System.Windows.Forms;
using System.Drawing;
using System.Data.OleDb;
using System.Data;
using System.IO;
using System.Text;
using System.Collections.Generic;

class form1: Form {
    DataGridView gd0;
    form1() {
        Text = "DataGrid";
        ClientSize = new Size(580, 400);
        gd0 = new DataGridView();
        gd0.Location = new Point(50, 20);
        gd0.Width = 500;
        gd0.Height = 300;
        Controls.AddRange(new Control[] {
            gd0
        });
        Button btn1 = new Button();
        btn1.Location = new Point(50, 330);
        btn1.Text = "test";
        btn1.Click += btn1_Click;
        Controls.AddRange(new Control[] {
            btn1
        });
    }
    void btn1_Click(object sender, System.EventArgs e) {
        Encoding enc = Encoding.GetEncoding("Shift_JIS");
        string file = "test1.csv";
        StreamReader reader = new StreamReader(file, enc);
        string temp = reader.ReadToEnd();
        string[] lines = temp.Split(new string[] {
            "\r\n"
        }, StringSplitOptions.None);
        string[] spLine;
        spLine = lines[0].Split(new char[] {
            ',',
            '\t'
        }, StringSplitOptions.None);
        gd0.ColumnCount = spLine.Length;
        for (int i = 0; i < spLine.Length; i++)
        {
            gd0.Columns[i].HeaderText = spLine[i].Trim('"');
        }
        DataGridViewRow[] rows = new DataGridViewRow[lines.Length - 1];
        for (int i = 1; i < lines.Length; i++)
        {
            spLine = lines[i].Split(new char[] {
                ',' ,
                '\t'
            }, StringSplitOptions.None);
            for (int j = 0; j < spLine.Length; j++)
            {
                spLine[j] = spLine[j].Trim('"');
            }
            DataGridViewRow row = new DataGridViewRow();
            row.CreateCells(gd0);
            row.SetValues(spLine);
            rows[i - 1] = row;
        }
        gd0.Rows.AddRange(rows);
        MessageBox.Show("ok");
    }
    [STAThread]
    public static void Main() {
        Application.Run(new form1());
    }
}






以上。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Windows GUIプログラミング入門22 画像ビューア

■はじめに

今回は画像ビューアを作ります。
00.png

キーワード:画像表示, 複数画面表示, ファイル選択ダイアログ, 子画面全終了, マウスホイール

[注意]
これまでの回で説明済みの操作方法等は、説明を省略したり簡略化している場合があります。

■開発環境

  • Windows 10
  • Visual Studio Community 2019 (Version 16.4.2)
  • .NET Framework 4.5.2 / .NET Core 3.1

■作ってみる

◇プロジェクトの作成

WPFアプリの.NET Frameworkまたは.NET Coreを選択してプロジェクトを作成します。
この記事では.NET Frameworkを選択しました。
※.NET Core版でも動作することは確認済みです。

12.png

◇メイン画面のレイアウト作成

メイン画面のレイアウト用コントロールをGridからViewboxに変更します。

01.png

StackPanel, Buttonを配置してプロパティやスタイルの設定をします。

02.png

MainWindow.xaml
<Window x:Class="ImageView.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:ImageView"
        mc:Ignorable="d"
        Title="画像ビューア" 
        Height="150" Width="450" 
        MinHeight="100" MinWidth="150"
        ResizeMode="CanResizeWithGrip">
    <Window.Resources>
        <!-- ボタンの既定スタイル -->
        <Style TargetType="Button">
            <Setter Property="Margin" Value="5"/>
            <Setter Property="Padding" Value="5"/>
        </Style>
    </Window.Resources>
    <Viewbox>
        <StackPanel Orientation="Horizontal">
            <Button Content="画像を開く" IsDefault="True"/>
            <Button Content="画像をすべて閉じる"/>
        </StackPanel>
    </Viewbox>
</Window>

◇画像表示用画面のレイアウト作成

ソリューションエクスプローラーでプロジェクトを右クリックし、ウィンドウを追加します。
名前はImageWindowにします。

03.png

Imageコントロールやボタン等を配置、プロパティ設定をします。
+, -ボタンはリピートボタンを使います。ボタンを押し続けている間、Clickイベントが連続して発生します。
RepeatButtonはツールボックスに無いのでタグを手打ちしてください。

04_.png

ImageWindow.xaml
<Window x:Class="ImageView.ImageWindow"
        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:ImageView"
        mc:Ignorable="d"
        Title="ImageWindow" Height="220" Width="200" 
        MinHeight="100" MinWidth="100"
        ResizeMode="CanResizeWithGrip">
    <Window.Resources>
        <!-- リピートボタンの既定スタイル -->
        <Style TargetType="RepeatButton">
            <Setter Property="Margin" Value="2"/>
            <Setter Property="Width" Value="20"/>
        </Style>
    </Window.Resources>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition/>
        </Grid.RowDefinitions>

        <StackPanel Orientation="Horizontal">
            <RepeatButton x:Name="plusButton" Content="+"/>
            <RepeatButton x:Name="minusButton" Content="-"/>
            <Button x:Name="closeButton" Content="閉じる" Margin="2" IsCancel="True"/>
        </StackPanel>

        <Image x:Name="img" Grid.Row="1"/>
    </Grid>
</Window>

◇画像表示ロジック作成

画像を指定して表示する処理、各ボタンクリック、ウィンドウのマウスホイール回転、ウィンドウのマウス左ボタンダウン処理を書きます。
マウスホイール回転時に拡大または縮小するようにします。
左ボタンダウン時の処理は、タイトルバーだけでなく画像部分をドラッグしてもウィンドウを移動できるようにします。

ImageWindow.xaml.cs
using System;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media.Imaging;

namespace ImageView
{
    /// <summary>
    /// ImageWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class ImageWindow : Window
    {
        /// <summary>
        /// コンストラクタ
        /// </summary>
        public ImageWindow()
        {
            InitializeComponent();
        }

        /// <summary>
        /// 画像設定処理(他の画面からこれを呼ぶ)
        /// </summary>
        /// <param name="filePath">画像ファイルパス</param>
        /// <returns>読み込み成功時true</returns>
        public bool SetImage(string filePath)
        {
            // パスが空
            if (string.IsNullOrEmpty(filePath))
            {
                return false;
            }

            var bmp = new BitmapImage();
            bmp.BeginInit();
            bmp.UriSource = new Uri(filePath);
            bmp.EndInit();
            // 画像設定
            img.Source = bmp;

            // タイトルバーにファイル名設定
            this.Title = System.IO.Path.GetFileName(filePath);

            return true;
        }

        /// <summary>
        /// ウィンドウサイズ拡大・縮小
        /// </summary>
        /// <param name="isZoom">true:拡大, false:縮小</param>
        private void ChangeWindowSize(bool isZoom)
        {
            int plusMinus = isZoom ? 1 : -1;

            // ウィンドウサイズを10%単位で変更
            this.Height += (this.Height * 0.1) * plusMinus;
            this.Width += (this.Width * 0.1) * plusMinus;

            // ウィンドウサイズが小さくなりすぎた場合、最小値に戻す
            if (this.Height < this.MinHeight ||
                this.Width < this.MinWidth)
            {
                this.Height = this.MinHeight;
                this.Width = this.MinWidth;
            }
        }

        /// <summary>
        /// 拡大ボタンクリック時
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void plusButton_Click(object sender, RoutedEventArgs e)
        {
            // ウィンドウサイズ拡大
            ChangeWindowSize(true);
        }

        /// <summary>
        /// 縮小ボタンクリック時
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void minusButton_Click(object sender, RoutedEventArgs e)
        {
            // ウィンドウサイズ縮小
            ChangeWindowSize(false);
        }

        /// <summary>
        /// 閉じるボタンクリック時
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void closeButton_Click(object sender, RoutedEventArgs e)
        {
            this.Close();
        }

        /// <summary>
        /// マウスホイール回転時
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Window_MouseWheel(object sender, MouseWheelEventArgs e)
        {
            // ウィンドウサイズ拡大・縮小
            ChangeWindowSize(e.Delta > 0);
        }

        /// <summary>
        /// マウス左ボタンダウン時の処理
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Window_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            // タイトルバー以外でもウィンドウ移動を可能にする
            DragMove();
        }
    }
}

最終的にXamlは以下のようになりました。

ImageWindow.xaml
<Window x:Class="ImageView.ImageWindow"
        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:ImageView"
        mc:Ignorable="d"
        Title="ImageWindow" Height="220" Width="200" 
        MinHeight="100" MinWidth="100"
        ResizeMode="CanResizeWithGrip"
        MouseWheel="Window_MouseWheel"
        MouseLeftButtonDown="Window_MouseLeftButtonDown">
    <Window.Resources>
        <!-- リピートボタンの既定スタイル -->
        <Style TargetType="RepeatButton">
            <Setter Property="Margin" Value="2"/>
            <Setter Property="Width" Value="20"/>
        </Style>
    </Window.Resources>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition/>
        </Grid.RowDefinitions>

        <StackPanel Orientation="Horizontal">
            <RepeatButton x:Name="plusButton" Content="+" Click="plusButton_Click"/>
            <RepeatButton x:Name="minusButton" Content="-" Click="minusButton_Click"/>
            <Button x:Name="closeButton" Content="閉じる" Margin="2" IsCancel="True" Click="closeButton_Click"/>
        </StackPanel>

        <Image x:Name="img" Grid.Row="1"/>
    </Grid>
</Window>

◇メイン画面ロジック作成

画像表示用画面を使って画像表示する処理、画像表示用画面を閉じる処理を書きます。

MainWindow.xaml.cs
using System;
using System.Windows;

namespace ImageView
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        /// <summary>
        /// コンストラクタ
        /// </summary>
        public MainWindow()
        {
            InitializeComponent();
        }

        /// <summary>
        /// 画像ファイルを選択し、表示
        /// </summary>
        private void OpenImageFile()
        {
            var dlg = new Microsoft.Win32.OpenFileDialog();
            dlg.Title = "画像を選択してください。";
            dlg.Filter = "画像ファイル|*.gif;*.png;*.jpeg;*.jpg;*.bmp|すべてのファイル|*.*";
            if (dlg.ShowDialog() == true)
            {
                // 画像表示画面生成
                var window = new ImageWindow();

                // 画像表示画面に画像設定
                if (window.SetImage(dlg.FileName))
                {
                    // 画面表示
                    window.Show();
                }
            }
        }

        /// <summary>
        /// 画像を開くボタンクリック時
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Button_Click(object sender, RoutedEventArgs e)
        {
            OpenImageFile();
        }

        /// <summary>
        /// 子画面全終了
        /// </summary>
        private void CloseSubWindows()
        {
            foreach (Window w in App.Current.Windows)
            {
                if (w != this)
                {
                    // 自身(メイン画面)以外なら終了させる
                    w.Close();
                }
            }
        }

        /// <summary>
        /// 画像をすべて閉じるボタンクリック時
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Button_Click_1(object sender, RoutedEventArgs e)
        {
            // 子画面全終了
            CloseSubWindows();
        }

        /// <summary>
        /// ウィンドウ終了時
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Window_Closed(object sender, EventArgs e)
        {
            // 開きっぱなしの子画面があったら終了
            CloseSubWindows();
        }
    }
}

最終的にXamlは以下のようになりました。

MainWindow.xaml
<Window x:Class="ImageView.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:ImageView"
        mc:Ignorable="d"
        Title="画像ビューア" 
        Height="150" Width="450" 
        MinHeight="100" MinWidth="150"
        ResizeMode="CanResizeWithGrip"
        Closed="Window_Closed">
    <Window.Resources>
        <!-- ボタンの既定スタイル -->
        <Style TargetType="Button">
            <Setter Property="Margin" Value="5"/>
            <Setter Property="Padding" Value="5"/>
        </Style>
    </Window.Resources>
    <Viewbox>
        <StackPanel Orientation="Horizontal">
            <Button Content="画像を開く" IsDefault="True" Click="Button_Click"/>
            <Button Content="画像をすべて閉じる" Click="Button_Click_1"/>
        </StackPanel>
    </Viewbox>
</Window>

■動かしてみる

05.png

ウィンドウサイズを小さくしてみます。
06.png

ウィンドウサイズを大きくしてみます。
07.png
Viewboxを使っているので拡大縮小してもきれいに表示されます。

「画像を開く」ボタンを押すと、ファイル選択ダイアログが表示されるので、画像を選択します。
08.png

表示されました。
09.png

続けて他にも画像を開いてみます。
10.png
画像の上でマウスホイールを奥に回すと拡大、手前に回すと縮小します。
+ボタン、-ボタンでも拡大、縮小できます。このボタンは長押しに対応しています。

「画像をすべて閉じる」ボタンを押すと、画像ウィンドウが一気に全部閉じます。
11.png

おしまい


<< 最初の記事   < 前の記事   次の記事 >

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

cscの作法 その27

概要

cscの作法、調べてみた。
練習問題やってみた。

練習問題

オセロゲームを作れ。

写真

image.png

サンプルコード

using System;
using System.Windows.Forms;
using System.Drawing;

class form1: Form {
    int[] ban = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 2, 1, 1, 1,
        1, 1, 1, 2, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
    form1() {
        Text = "osero";
        ClientSize = new Size(240, 240);
        this.MouseUp += new MouseEventHandler(mouseUp);
    }
    void mouseUp(object sender, MouseEventArgs e) {
        Text = "mouse: (" + e.X + ", " + e.Y + ")";
        int posx = e.X;
        int posy = e.Y;
        int n = (posx / 30) + (posy / 30) * 8;
        test(n);
        Invalidate();
    }
    int check(int put, int d) {
        int x = put % 8;
        int y = put / 8;
        int res = 0;
        if (x == 0 && (d == -9 || d == -1 || d ==  7)) res = 1;
        if (x == 7 && (d == -7 || d ==  1 || d ==  9)) res = 1;
        if (y == 0 && (d == -9 || d == -8 || d == -7)) res = 1;
        if (y == 7 && (d ==  7 || d ==  8 || d ==  9)) res = 1;
        return res;
    }
    int sasu() {
        int[] suji = {0, 7, 56, 63, 18, 21, 42, 45, 2, 16, 5, 23, 40, 58, 47, 61, 3, 4, 11, 12, 19, 20, 24, 25, 26, 32, 33, 34, 29, 30, 31, 37,
            38, 39, 43, 44, 51, 52, 59, 60, 1, 8, 9, 10, 17, 6, 13, 14, 15, 22, 41, 48, 49, 50, 57, 46, 53, 54, 55, 62};
        int res = -1;
        int all = 0;
        int iro = 2;
        int turn = 0;
        int[] dir = {-9, 9, -7, 7, -1, 1, -8, 8};
        int i;
        int j;
        int put;
        int count;
        int put1 = 0;
        int f1;
        for (j = 0; j < 60; j++)
        {
            put = suji[j];
            if (ban[put] == 1)
            {
                for (i = 0; i < 8; i++)
                {
                    count = 0;
                    if (check(put, dir[i]) == 0)
                    {
                        put1 = put + dir[i];
                        f1 = 0;
                        do
                        {
                            if (ban[put1] == iro)
                            {
                                count++;
                                if (check(put1, dir[i]) == 0)
                                {
                                    put1 += dir[i];
                                }
                                else
                                {
                                    f1 = 1;
                                }
                            }
                            else
                            {
                                f1 = 1;
                            }
                        } while (f1 == 0);
                    }
                    if ((count > 0) && (ban[put1] == turn))
                    {
                        all += count;
                    }
                }
            }
            if (all > 0)
            {
                res = put;
                break;
            }
        }
        return res;
    }
    int oku(int put, int iro) {
        int res = 0;
        int turn = 0;
        if (iro == 0) turn = 2;
        int[] dir = {-9, -8, -7, -1, 1, 7, 8, 9};
        int tugi;
        int i;
        int count;
        if (ban[put] == 1)
        {
            for (i = 0; i < 8; i++)
            {
                count = 0;
                tugi = put;
                do
                {
                    if (check(tugi, dir[i]) == 1) break;
                    count++;
                    tugi += dir[i];
                }
                while (ban[tugi] == turn);
                if ((count > 1) && (ban[tugi] == iro))
                {
                    res = -1;
                    tugi = put;
                    do
                    {
                        ban[tugi] = iro;
                        tugi += dir[i];
                    }
                    while (ban[tugi] == turn);
                }
            }
        }
        return res;
    }
    void test(int n) {
        int put = n;
        int res = oku(put, 2);
        if (res == -1)
        {
            put = sasu();
            res = oku(put, 0);
            int end = -1;
            for (int i = 0; i < 64; i++)
            {
                if (ban[i] == 1) end = 0;
            }
            if (end == -1)
            {
                MessageBox.Show("over!");
            }
        }
    }
    protected override void OnPaint(PaintEventArgs e) {
        Graphics g = e.Graphics;
        g.FillRectangle(Brushes.Green, 0, 0, 240, 240);
        for (int i = 0; i < 9; i++)
        {
            g.DrawLine(Pens.Pink, 0, 30 * i, 240, 30 * i);
            g.DrawLine(Pens.Pink, 30 * i, 0, 30 * i, 240);
        }
        int x;
        int y;
        for (int i = 0; i < 64; i++)
        {
            x = i % 8 * 30 + 5;
            y = i / 8 * 30 + 5;
            if (ban[i] == 0)
            {
                g.FillEllipse(Brushes.White, x, y, 20, 20);
            }
            if (ban[i] == 2)
            {
                g.FillEllipse(Brushes.Black, x, y, 20, 20);
            }
        }
        base.OnPaint(e);
    }
    [STAThread]
    public static void Main() {
        Application.Run(new form1());
    }
}





以上。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

DictionaryでCustom Buttonを作り、MainWindowで使用する方法

やりたい事

1. 自分でボタンをデザインしたい。
2. 詳細デザインをDictionary.xamlに記述し、"CustomButton"をMainWindow.xamlで使用する。MainWindow.xaml内部をすっきりさせたい。

結果

下の画像が結果である。
一番上(Button1)は、Windowsのチュートリアル[1]に記述されていたもの。二番目(Button2)は、参考文献[2]に記述されていたもの。三番目(Button3)は、デフォルトを表わす。
image.png

参考文献

[1] Microsoftのチュートリアル
自分の知識レベルが低いのか? Microsoftのreferenceは分かりにくい。でもこのチュートリアルは分かりやすいです。
[2] XAML UNLEASHED
xamlは、記述方法の慣れが必要で、参考資料が少ないですが、この書籍は分かりやすいと思います。初心者には、こちらを勧めます。しかも、中古本が安い。

詳細

自分でデザインしたボタンをDictionary.xamlに記述します。
ここで、デザインといっても、pngやjpgなどで作った画像データを埋め込むという意味ではありません。

Dictionary1.xaml
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:local="clr-namespace:WpfApp5">
    <GradientStopCollection x:Key="MyGlassGradientStopsResource">
        <GradientStop Color="WhiteSmoke" Offset="0.2" />
        <GradientStop Color="Transparent" Offset="0.4" />
        <GradientStop Color="WhiteSmoke" Offset="0.5" />
        <GradientStop Color="Transparent" Offset="0.75" />
        <GradientStop Color="WhiteSmoke" Offset="0.9" />
        <GradientStop Color="Transparent" Offset="1" />
    </GradientStopCollection>
    <LinearGradientBrush x:Key="MyGlassBrushResource" StartPoint="0,0" EndPoint="1,1" Opacity="0" GradientStops="{StaticResource MyGlassGradientStopsResource}" />
    <LinearGradientBrush x:Key="GrayBlueGradientBrush" StartPoint="0,0" EndPoint="1,1">
        <GradientStop Color="DarkGray" Offset="0" />
        <GradientStop Color="#CCCCFF" Offset="0.5" />
        <GradientStop Color="DarkGray" Offset="1" />
    </LinearGradientBrush>
<!-- Button1の始まり -->
    <Style TargetType="{x:Type Button}" x:Key="ButtonStyle">
        <Setter Property="Background" Value="{StaticResource GrayBlueGradientBrush}" />
        <Setter Property="Width" Value="80" />
        <Setter Property="Margin" Value="10" />
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type Button}">
                    <Grid Width="{TemplateBinding Width}"
                            Height="{TemplateBinding Height}" ClipToBounds="True">
                        <!-- Outer Rectangle with rounded corners. -->
                        <Rectangle x:Name="outerRectangle" HorizontalAlignment="Stretch"
                                    VerticalAlignment="Stretch" Stroke="{TemplateBinding Background}"
                                    RadiusX="20" RadiusY="20" StrokeThickness="5" Fill="Transparent" />

                        <!-- Inner Rectangle with rounded corners. -->
                        <Rectangle x:Name="innerRectangle" HorizontalAlignment="Stretch"
                                    VerticalAlignment="Stretch" Stroke="Transparent"
                                    StrokeThickness="20" Fill="{TemplateBinding Background}" RadiusX="20" RadiusY="20" />
                        <!-- Glass Rectangle -->
                        <Rectangle x:Name="glassCube" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" 
                                       StrokeThickness="2" RadiusX="10" RadiusY="10" Opacity="0" Fill="{StaticResource MyGlassBrushResource}"
                                       RenderTransformOrigin="0.5,0.5">
                            <Rectangle.Stroke>
                                <LinearGradientBrush StartPoint="0.5,0" EndPoint="0.5,1">
                                    <LinearGradientBrush.GradientStops>
                                        <GradientStop Offset="0.0" Color="LightBlue" />
                                        <GradientStop Offset="1.0" Color="Gray" />
                                    </LinearGradientBrush.GradientStops>
                                </LinearGradientBrush>
                            </Rectangle.Stroke>
                            <Rectangle.RenderTransform>
                                <TransformGroup>
                                    <ScaleTransform />
                                    <RotateTransform />
                                </TransformGroup>
                            </Rectangle.RenderTransform>
                            <Rectangle.BitmapEffect>
                                <BevelBitmapEffect />
                            </Rectangle.BitmapEffect>
                        </Rectangle>
                        <!-- Present Text of the button. -->
                        <DockPanel Name="myContentPresenterDockPanel">
                            <ContentPresenter x:Name="myContentPresenter" Margin="20" Content="{TemplateBinding  Content}" TextBlock.Foreground="Black" />
                        </DockPanel>
                    </Grid>
                    <ControlTemplate.Triggers>
                        <Trigger Property="IsMouseOver" Value="True">
                            <Setter Property ="Rectangle.Stroke" TargetName="outerRectangle" Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}" />
                            <!-- Sets the glass opacity to 1, therefore, the           glass "appears" when user mouses over it. -->
                            <Setter Property="Rectangle.Opacity" Value="1" TargetName="glassCube" />
                            <!-- Makes the text slightly blurry as though you           were looking at it through blurry glass. -->
                            <Setter Property="ContentPresenter.BitmapEffect" TargetName="myContentPresenter">
                                <Setter.Value>
                                    <BlurBitmapEffect Radius="1" />
                                </Setter.Value>
                            </Setter>
                        </Trigger>
                        <Trigger Property="IsFocused" Value="true">
                            <Setter Property="Rectangle.Opacity" Value="1" TargetName="glassCube" />
                            <Setter Property="Rectangle.Stroke" TargetName="outerRectangle" Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}" />
                            <Setter Property="Rectangle.Opacity" Value="1" TargetName="glassCube" />
                        </Trigger>
                        <!-- Animations that start when mouse enters and leaves button. -->
                        <EventTrigger RoutedEvent="Mouse.MouseEnter">
                            <EventTrigger.Actions>
                                <BeginStoryboard Name="mouseEnterBeginStoryboard">
                                    <Storyboard>
                                        <!-- This animation makes the glass rectangle shrink in the X direction. -->
                                        <DoubleAnimation Storyboard.TargetName="glassCube" 
                                                             Storyboard.TargetProperty= "(Rectangle.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)" By="-0.1" Duration="0:0:0.5" />
                                        <!-- This animation makes the glass rectangle shrink in the Y direction. -->
                                        <DoubleAnimation Storyboard.TargetName="glassCube"
                                                            Storyboard.TargetProperty="(Rectangle.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleY)"
                                                            By="-0.1" Duration="0:0:0.5" />
                                    </Storyboard>
                                </BeginStoryboard>
                            </EventTrigger.Actions>
                        </EventTrigger>
                        <EventTrigger RoutedEvent="Mouse.MouseLeave">
                            <EventTrigger.Actions>
                                <!-- Stopping the storyboard sets all animated properties back to default. -->
                                <StopStoryboard BeginStoryboardName="mouseEnterBeginStoryboard" />
                            </EventTrigger.Actions>
                        </EventTrigger>
                        <!-- Animation fires when button is clicked, causing glass to spin.  -->
                        <EventTrigger RoutedEvent="Button.Click">
                            <EventTrigger.Actions>
                                <BeginStoryboard>
                                    <Storyboard>
                                        <DoubleAnimation Storyboard.TargetName="glassCube" 
                                                             Storyboard.TargetProperty= "(Rectangle.RenderTransform).(TransformGroup.Children)[1].(RotateTransform.Angle)"
                                                             By="360" Duration="0:0:0.5" />
                                    </Storyboard>
                                </BeginStoryboard>
                            </EventTrigger.Actions>
                        </EventTrigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
<!-- Button1の終わり -->

<!-- Button2の始まり -->
    <Style TargetType="Button" x:Key="CustomButton" >
        <Setter Property="Margin" Value="10" />
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="Button">
                    <Grid Width="{TemplateBinding Width}"
                            Height="{TemplateBinding Height}" ClipToBounds="True">
                        <Ellipse Width="100" Height="100">
                        <Ellipse.Fill>
                            <LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
                                <GradientStop Offset="0" Color="Blue"/>
                                <GradientStop Offset="1" Color="Red"/>
                            </LinearGradientBrush>
                        </Ellipse.Fill>
                    </Ellipse>
                    <!-- Contentの中身をTemplateの中に埋め込むために以下が必要 -->
                        <DockPanel Name="myContentPresenterDockPanel1">
                            <ContentPresenter x:Name="myContentPresenter1" HorizontalAlignment="Center" VerticalAlignment="Center" Content="{TemplateBinding  Content}" TextBlock.Foreground="White" />
                        </DockPanel>
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
<!-- Button2の終わり-->
</ResourceDictionary>

重要なポイント

Dictonary1.xamlでButtonタイプを継承し、新しくButtonSyleを定義します。

<Style TargetType="{x:Type Button}" x:Key="ButtonStyle">

ボタンに機能を持たせるには、ControlTemplateの下に記述する必要があります。

<ControlTemplate>
             <ControlTemplate.Trigger>
                <EventTrigger>
                     -----------
                </EventTrigger>
             </ControlTemplate.Trigger>
</ControlTemplate>

上記のDictionary1.xamlで記述したButtonStyle(Button1用)のデザインを引継ぎ、MainWindow.xamlで利用する。利用するには、Style="{StaticResource ****}" を使う。

<Button Style="{StaticResource ButtonStyle}" Content="Button1"/>
MainWindow.xaml
<Window x:Class="WpfApp5.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:WpfApp5"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800" Background="Black">
    <Window.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="/Dictionary1.xaml"/>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Window.Resources>
    <StackPanel HorizontalAlignment="Left">
        <Button Style="{StaticResource ButtonStyle}" Content="Button1"/>
        <Button Style="{StaticResource CustomButton}" Content="Button2"/>
        <Button Content="Button3"/>
    </StackPanel>
</Window>

最後に

今回の記事では、ボタンのカスタマイズだったので、xamlのみの変更修正です。
コードビハインドであるMainWindow.xaml.csには、デフォルトの内容です。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

WebBrowser コントロールで XML 表示

概要

XML を画面に表示する場合、System.Windows.Forms.WebBrowser コントロールを使用し、DocumentText プロパティに内容をセットすることで実現可能だが、タグ名に head などがある場合に上手く表示されない問題がある。
(head で始まる header 等でもダメ)

表示させるXML
<?xml version="1.0" encoding="utf-8"?>
<doc>
  <header>ヘッダ</header>
  <data>データ</data>
</doc>

IE で表示した場合、下図のようになる。

IE で表示
WebBrowser_IE.png

通常

DocumentText に文字列を設定した場合は IE と同じように表示されない。。
コントロール内部で HTML の HEAD タグと誤認してそう。

WebBrowser で表示 1
WebBrowser_EG1.png
通常コード
this.webBrowser1.DocumentText = xml;

改良

XML 文字列を一旦ファイルに保存し、Navigate メソッドを呼ぶことで、IE と同じような表示となった。

WebBrowser で表示 2
WebBrowser_EG2.png
改良コード
var path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.xml");
File.WriteAllText(path, xml);
this.webBrowser1.Navigate(path);
File.Delete(path);
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

cscの作法 その26

概要

cscの作法、調べてみた。
練習問題やってみた。

練習問題

googlの検索ボタンを押せ。

サンプルコード

using System;
using System.Windows.Forms;
using System.Drawing;

class form1: Form {
    WebBrowser wb0;
    form1() {
        Text = "WebBrowser";
        ClientSize = new Size(600, 800);
        Button btn1 = new Button();
        btn1.Location = new Point(50, 20);
        btn1.Text = "test";
        btn1.Click += btn1_Click;
        Controls.AddRange(new Control[] {
            btn1
        });
        wb0 = new WebBrowser();
        wb0.Location = new Point(50, 50);
        wb0.Width = 500;
        wb0.Height = 700;
        Controls.AddRange(new Control[] {
            wb0
        });
        string url = "http://www.google.co.jp/";
        wb0.Navigate(url);
    }
    void btn1_Click(object sender, System.EventArgs e) {
        wb0.Focus();
        wb0.Document.All.GetElementsByName("q")[0].InnerText = "ohisamallc";
        SendKeys.Send("{tab}");
        SendKeys.Send("{enter}");
    }
    [STAThread]
    public static void Main() {
        Application.Run(new form1());
    }
}




以上。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

cscの作法 その25

概要

cscの作法、調べてみた。
webbrowser使ってみた。

写真

image.png

サンプルコード

using System;
using System.Windows.Forms;
using System.Drawing;

class form1: Form {
    WebBrowser wb0;
    form1() {
        Text = "WebBrowser";
        ClientSize = new Size(300, 300);
        wb0 = new WebBrowser();
        wb0.Location = new Point(50, 50);
        Controls.AddRange(new Control[] {
            wb0
        });
        string url = "http://www.google.co.jp/";
        wb0.Navigate(url);
    }
    [STAThread]
    public static void Main() {
        Application.Run(new form1());
    }
}

以上。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

cscの作法 その24

概要

cscの作法、調べてみた。
練習問題やってみた。

練習問題

pdfダウンローダーを作れ。

サンプルコード

using System;
using System.Windows.Forms;
using System.Drawing;
using System.IO;
using System.Net;
using System.Text;

class form1: Form {
    TextBox bo1;
    form1() {
        Text = "download";
        ClientSize = new Size(580, 200);
        bo1 = new TextBox();
        bo1.Location = new Point(50, 50);
        bo1.Width = 500;
        bo1.Text = "https://www.jitec.ipa.go.jp/1_04hanni_sukiru/mondai_kaitou_2015h27_1/2015h27h_fe_am_qs.pdf";
        Controls.AddRange(new Control[] {
            bo1
        });
        Button btn1 = new Button();
        btn1.Location = new Point(50, 90);
        btn1.Text = "test";
        btn1.Click += btn1_Click;
        Controls.AddRange(new Control[] {
            btn1
        });
    }
    void btn1_Click(object sender, System.EventArgs e) {
        string url = bo1.Text;
        string filename = DateTime.Now.ToString("HH-mm-ss") + ".pdf";
        WebRequest req = WebRequest.Create(url);
        WebResponse res = req.GetResponse();
        Stream st = res.GetResponseStream();
        byte[] buf = new byte[32768];
        MemoryStream ms = new MemoryStream();
        while (true)
        {
            int read = st.Read(buf, 0, buf.Length);
            if (read > 0)
            {
                ms.Write(buf, 0, read);
            }
            else
            {
                break;
            }
        }
        FileStream fs = new FileStream(filename, FileMode.Create);
        byte[] wbuf = new byte[ms.Length];
        ms.Seek(0, SeekOrigin.Begin);
        ms.Read(wbuf, 0, wbuf.Length);
        fs.Write(wbuf, 0, wbuf.Length);
        fs.Close();
        MessageBox.Show(filename);
    }
    [STAThread]
    public static void Main() {
        Application.Run(new form1());
    }
}





以上。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

cscの作法 その23

概要

cscの作法、調べてみた。
練習問題やってみた。

練習問題

コッホ曲線を表示せよ。

写真

image.png

サンプルコード

using System;
using System.Windows.Forms;
using System.Drawing;

class form1: Form {
    double ANGLE = 0;
    double LX = 20.0;
    double LY = 150.0;
    Graphics g;
    form1() {
        Text = "koch";
        ClientSize = new Size(450, 200);
    }
    protected override void OnPaint(PaintEventArgs e) {
        g = e.Graphics;
        koch(5, 60);
        base.OnPaint(e);
    }
    double fmod(double a, double b) {
        double x = Math.Floor(a / b);
        return a - b * x;
    }
    void turn(double a) {
        ANGLE = fmod(ANGLE + a, 360.0);
    }
    void gmove(double l) {
        Pen pen = new Pen(Color.Red);
        double rd = 3.14159265359 / 180.0;
        double x = l * Math.Cos(rd * ANGLE);
        double y = -l * Math.Sin(rd * ANGLE);
        int px = (int) LX;
        int py = (int) LY;
        LX += x;
        LY += y;
        g.DrawLine(pen, px, py, (int) LX, (int) LY);
    }
    void koch(int i, int j) {
        if (i <= 1)
        {
            gmove(5.0);
            return;
        }
        koch(i - 1, j);
        turn(j);
        koch(i - 1, j);
        turn(-2 * j);
        koch(i - 1, j);
        turn(j);
        koch(i - 1, j);
    }
    [STAThread]
    public static void Main() {
        Application.Run(new form1());
    }
}





以上。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

cscの作法 その22

概要

cscの作法、調べてみた。
練習問題やってみた。

練習問題

レイトレースを表示せよ。

参考にしたページ

https://www.codeproject.com/Articles/19732/Simple-Ray-Tracing-in-C

写真

image.png

サンプルコード

using System;
using System.Windows.Forms;
using System.Drawing;

class form1: Form {
    form1() {
        Text = "ray";
        ClientSize = new Size(300, 300);
    }
    protected override void OnPaint(PaintEventArgs e) {
        Graphics g = e.Graphics;
        Color clrBackground = Color.Black;
        g.FillRectangle(new SolidBrush(clrBackground), new Rectangle(0, 0, 200, 200));
        Rectangle rect = new Rectangle(0, 0, 200, 200);
        System.Collections.ArrayList obj3dArrayList;
        obj3dArrayList = new System.Collections.ArrayList();
        obj3dArrayList.Add(new Sphere(0.0, 0.0, 90.0, 100.0, 0.0, 0.0, 255.0));
        obj3dArrayList.Add(new Sphere(-180.0, -130.0, -110.0, 15.0, 255.0, 0.0, 0.0));
        obj3dArrayList.Add(new Sphere(-140.0, -140.0, -150.0, 20.0, 255.0, 200.0, 0.0));
        Graphics graphics = g;
        double px = (double) 0,
            py = (double) 0,
            pz = (double) 500;
        double lpx = (double) 200,
            lpy = (double) 200,
            lpz = (double) 200;
        double lvx = (double) -1.0,
            lvy = (double) -1.0,
            lvz = (double) -1.0;
        double fMax = 200.0;
        for (int i = rect.Left; i <= rect.Right; i++)
        {
            double x = Sphere.GetCoord(rect.Left, rect.Right, -fMax, fMax, i);
            for (int j = rect.Top; j <= rect.Bottom; j++)
            {
                double y = Sphere.GetCoord(rect.Top, rect.Bottom, fMax, -fMax, j);
                double t = 1.0E10;
                double vx = x - px, vy = y - py, vz = -pz;
                double mod_v = Sphere.modv(vx, vy, vz);
                vx = vx / mod_v;
                vy = vy / mod_v;
                vz = vz / mod_v;
                bool bShadow = false;
                Sphere spherehit = null;
                for (int k = 0; k < (int) obj3dArrayList.Count; k++)
                {
                    Sphere sphn = (Sphere) obj3dArrayList[k];
                    double taux = Sphere.GetSphereIntersec(sphn.cx, sphn.cy, sphn.cz, sphn.radius, px, py, pz, vx, vy, vz);
                    if (taux < 0) continue;
                    if (taux > 0 && taux < t)
                    {
                        t = taux;
                        spherehit = sphn;
                    }
                }
                Color color = Color.FromArgb(10, 20, 10);
                if (spherehit != null)
                {
                    double itx = px + t * vx, ity = py + t * vy, itz = pz + t * vz;
                    double tauxla = Sphere.GetSphereIntersec(spherehit.cx, spherehit.cy, spherehit.cz, spherehit.radius, lpx, lpy, lpz, itx - lpx, ity - lpy, itz - lpz);
                    for (int k = 0; k < (int)obj3dArrayList.Count; k++)
                    {
                        Sphere sphnb = (Sphere)(obj3dArrayList[k]);
                        if (sphnb != spherehit)
                        {
                            double tauxlb = Sphere.GetSphereIntersec(sphnb.cx, sphnb.cy, sphnb.cz, sphnb.radius, lpx, lpy, lpz, itx - lpx, ity - lpy, itz - lpz);
                            if (tauxlb > 0 && tauxla < tauxlb)
                            {
                                bShadow = true;
                                break;
                            }
                        }
                    }
                    double cost = Sphere.GetCosAngleV1V2(lvx, lvy, lvz, itx - spherehit.cx, ity - spherehit.cy, itz - spherehit.cz);
                    if (cost < 0) cost = 0;
                    double fact = 1.0;
                    if (bShadow == true) fact = 0.5; else fact = 1.0;
                    double rgbR = spherehit.clR * cost * fact;
                    double rgbG = spherehit.clG * cost * fact;
                    double rgbB = spherehit.clB * cost * fact;
                    color = Color.FromArgb((int)rgbR, (int)rgbG, (int)rgbB);
                    Pen pen = new Pen(color);
                }
                Brush brs = new SolidBrush(color);
                graphics.FillRectangle(brs, i, j, 1, 1);
                brs.Dispose();
            }
        }
        base.OnPaint(e);
    }
    class Sphere {
    public Sphere(double x, double y, double z, double r, double clr, double clg, double clb) {
        cx = x;
        cy = y;
        cz = z;
        radius = r;
        clR = clr;
        clG = clg;
        clB = clb;
    }
    public static double GetCoord(double i1, double i2, double w1, double w2, double p) {
        return ((p - i1) / (i2 - i1)) * (w2 - w1) + w1;
    }
    public static double modv(double vx, double vy, double vz) {
        return System.Math.Sqrt(vx * vx + vy * vy + vz * vz);
    }
    void Move(double vx, double vy, double vz) {
        cx += vx;
        cy += vy;
        cz += vz;
    }
    void MoveTo(double vx, double vy, double vz) {
        cx = vx;
        cy = vy;
        cz = vz;
    }
    void RotX(double angle) {
        double y = cy * System.Math.Cos(angle) - cz * System.Math.Sin(angle);
        double z = cy * System.Math.Sin(angle) + cz * System.Math.Cos(angle);
        cy = y;
        cz = z;
    }
    void RotY(double angle) {
        double x = cx * System.Math.Cos(angle) - cz * System.Math.Sin(angle);
        double z = cx * System.Math.Sin(angle) + cz * System.Math.Cos(angle);
        cx = x;
        cz = z;
    }
    public static double GetSphereIntersec(double cx, double cy, double cz, double radius, double px, double py, double pz, double vx, double vy, double vz) {
        double A = (vx * vx + vy * vy + vz * vz);
        double B = 2.0 * (px * vx + py * vy + pz * vz - vx * cx - vy * cy - vz * cz);
        double C = px * px - 2 * px * cx + cx * cx + py * py - 2 * py * cy + cy * cy + pz * pz - 2 * pz * cz + cz * cz - radius * radius;
        double D = B * B - 4 * A * C;
        double t = -1.0;
        if (D >= 0)
        {
            double t1 = (-B - System.Math.Sqrt(D)) / (2.0 * A);
            double t2 = (-B + System.Math.Sqrt(D)) / (2.0 * A);
            if (t1 > t2) t = t1; else t = t2;
        }
        return t;
    }
    public static double GetCosAngleV1V2(double v1x, double v1y, double v1z, double v2x, double v2y, double v2z) {
        return (v1x * v2x + v1y * v2y + v1z * v2z) / (modv(v1x, v1y, v1z) * modv(v2x, v2y, v2z));
    }
    public double cx,
        cy,
        cz,
        radius,
        clR,
        clG,
        clB;
    }
    [STAThread]
    public static void Main() {
        Application.Run(new form1());
    }
}





以上。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

cscの作法 その21

概要

cscの作法、調べてみた。
練習問題やってみた。

練習問題

マンデルブロ集合を表示せよ。

参考にしたページ

http://ou812.web.fc2.com/CsTips/AnalysisMandelbrot.html

写真

image.png

サンプルコード

using System;
using System.Windows.Forms;
using System.Drawing;

class form1: Form {
    form1() {
        Text = "mandel";
        ClientSize = new Size(500, 500);
    }
    protected override void OnPaint(PaintEventArgs e) {
        Graphics g = e.Graphics;
        Complex c;
        Complex z;
        int nCount;
        int nCountMax = 64;
        int nWidth = 500;
        int nHeight = 500;
        Pen pen = new Pen(Color.Red);
        for (int x = 0; x < nWidth; x++)
        {
            for (int y = 0; y < nHeight; y++)
            {
                c = new Complex(-this.Width / 2 + x, this.Height / 2 - y);
                c *= 0.00625;
                nCount = 0;
                z = new Complex();
                while(true)
                {
                    z = z * z + c;
                    if (z.GetSquareModulus() >= 4.0) break;
                    nCount++;
                    if (nCount > nCountMax)
                    {
                        nCount = -1;
                        break;
                    }
                }
                pen.Color = GetColor(nCount);
                g.DrawLine(pen, x, y, x, y + 1);
            }
        }
        pen.Dispose();
        base.OnPaint(e);
    }
    private Color GetColor(int nCount) {
        int d,
            r,
            g,
            b;
        if (nCount < 0) return Color.Black;
        d = nCount % 16;
        d *= 256 / 16;
        int m = (int) (d / 42.667);
        switch(m)
        {
        case 0:
            r = 0;
            g = 6 * d;
            b = 255;
        break;
        case 1:
            r = 0;
            g = 255;
            b = 255 - 6 * (d - 43);
        break;
        case 2:
            r = 6 * (d - 86);
            g = 255;
            b = 0;
        break;
        case 3:
            r = 255;
            g = 255 - 6 * (d - 129);
            b = 0;
        break;
        case 4:
            r = 255;
            g = 0;
            b = 6 * (d - 171);
        break;
        case 5:
            r = 255 - 6 * (d - 214);
            g = 0;
            b = 255;
        break;
        default:
            r = 0;
            g = 0;
            b = 0;
        break;
        }
        return Color.FromArgb(r, g, b);
    }
    class Complex {
    public double Real {
        get
        {
            return _dReal;
        }
        set
        {
            _dReal = value;
        }
    }
    public double Imaginary {
        get
        {
            return _dImaginary;
        }
        set
        {
            _dImaginary = value;
        }
    }
    private double _dReal;
    private double _dImaginary;
    public Complex() {
        _dReal = 0.0;
        _dImaginary = 0.0;
    }
    public Complex(double dReal, double dImaginary) {
        _dReal = dReal;
        _dImaginary = dImaginary;
    }
    public virtual Complex Clone() {
        return (Complex)this.MemberwiseClone();
    }
    public override int GetHashCode() {
        return (_dReal.GetHashCode() ^ _dImaginary.GetHashCode());
    }
    public override string ToString() {
        if (_dImaginary >= 0)
        {
            return "(" + _dReal.ToString() + ", i" + _dImaginary.ToString() + ")";
        }
        else
        {
            return "(" + _dReal.ToString() + ", -i" + ((-1) * _dImaginary).ToString() + ")";
        }
    }
    public override bool Equals(object o) {
        if (o is Complex)
        {
            Complex c = (Complex)o;
            return (this == c);
        }
        return false;
    }
    public double GetArgument() {
        return (double)Math.Atan2(_dReal, _dImaginary);
    }
    public Complex GetConjugate() {
        return new Complex(_dReal, - _dImaginary);
    }
    public Complex GetSquare() {
        return new Complex(_dReal * _dReal - _dImaginary * _dImaginary, 2 * _dReal * _dImaginary);
    }
    public double GetModulus() {
        return Math.Sqrt(GetSquareModulus());
    }
    public double GetSquareModulus() {
        return _dReal * _dReal + _dImaginary * _dImaginary;
    }
    public Complex GetNormalize() {
        double dModulus = this.GetModulus();
        if (dModulus == 0)
        {
            return null;
        }
        return new Complex(_dReal / dModulus, _dImaginary / dModulus);
    }
    public bool Normalize() {
        double dModulus = this.GetModulus();
        if (dModulus == 0)
        {
            return false;
        }
        _dReal /= dModulus;
        _dImaginary /= dModulus;
        return true;
    }
    public static Complex operator + (Complex dat1, Complex dat2) {
        Complex ret = (Complex) dat1.Clone();
        ret.Real += dat2.Real;
        ret.Imaginary += dat2.Imaginary;
        return ret;
    }
    public static Complex operator + (Complex dat1, double dat2) {
        Complex ret = (Complex) dat1.Clone();
        ret.Real += dat2;
        ret.Imaginary += dat2;
        return ret;
    }
    public static Complex operator - (Complex dat1, Complex dat2) {
        Complex ret = (Complex) dat1.Clone();
        ret.Real -= dat2.Real;
        ret.Imaginary -= dat2.Imaginary;
        return ret;
    }
    public static Complex operator - (Complex dat1, double dat2) {
        Complex ret = (Complex) dat1.Clone();
        ret.Real -= dat2;
        ret.Imaginary -= dat2;
        return ret;
    }
    public static Complex operator * (Complex dat1, Complex dat2) {
        double dReal = dat1.Real * dat2.Real - dat1.Imaginary * dat2.Imaginary;
        double dImaginary = dat1.Real * dat2.Imaginary + dat1.Imaginary * dat2.Real;
        return new Complex(dReal, dImaginary);
    }
    public static Complex operator * (Complex dat1, double dat2) {
        Complex ret = (Complex) dat1.Clone();
        ret.Real *= dat2;
        ret.Imaginary *= dat2;
        return ret;
    }
    public static Complex operator / (Complex dat1, double dat2) {
        Complex ret = (Complex) dat1.Clone();
        ret.Real /= dat2;
        ret.Imaginary /= dat2;
        return ret;
    }
    public static bool operator == (Complex dat1, Complex dat2) {
        return (dat1.Real == dat2.Real) && (dat1.Imaginary == dat2.Imaginary);
    }
    public static bool operator !=(Complex dat1, Complex dat2) {
        return !(dat1 == dat2);
    }
    }
    [STAThread]
    public static void Main() {
        Application.Run(new form1());
    }
}





以上。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Unity(C#)】ハンドトラッキングで簡易版VRお絵かきアプリ

ハンドトラッキング

OculusQuestにとんでもないテクノロジーがやってきました。(今更)
まだベータ版みたいですが、ハンドトラッキングがアップデートで実装されました。(今更)

お勉強がてら中身をいじってみようと思います。
今回は下記記事を参考に、簡易版お絵かきアプリを実装してみようと思います。

【参考リンク】:OculusQuest ハンドトラッキングSDKから、指Boneの情報を取得し分析する

デモ

なかなかのクオリティですがやりたいことができました。
DrawDemo.gif

実装方法としては、
人差し指を完全に伸ばした状態のときにTrailRendererEmittingtrueに、
それ以外の時はfalseにしているだけです。

コード

using UnityEngine;
/// <summary>
/// 適当にオブジェクト作ってアタッチ
/// </summary>
[RequireComponent(typeof(TrailRenderer))]
public class DrawFunction : MonoBehaviour
{
    [SerializeField]
    OVRHand m_oVRHand;

    [SerializeField]
    OVRSkeleton m_ovrSkeleton;

    [SerializeField]
    OVRHand.HandFinger m_handFingerType;

    TrailRenderer m_tr;

    void Reset()
    {
        m_tr = this.gameObject.GetComponent<TrailRenderer>();
        m_tr.time = Mathf.Infinity;
        m_tr.widthMultiplier = 0.01f;
        m_tr.minVertexDistance = 0.01f;
    }

    void Start()
    {
        m_tr = this.gameObject.GetComponent<TrailRenderer>();
    }

    void Update()
    {
        Vector3 indexTipPos = m_ovrSkeleton.Bones[(int)OVRSkeleton.BoneId.Hand_IndexTip].Transform.position;
        this.gameObject.transform.position = indexTipPos;

        if (m_oVRHand.GetFingerPinchStrength(m_handFingerType) == 0)
        {
            m_tr.emitting = true;
        }
        else
        {
            m_tr.emitting = false;
        }
    }
}

TrailRenderer

Unity様が用意した神コンポーネントのおかげでお絵かき機能自体は
ノーコーディングで実装できています。

細かい設定はResetメソッドの中で行っています。
RequireComponentで勝手に引っ付いてくるようにして、
Resetで初期設定を行う王道スタイルです。

    TrailRenderer m_tr;

    void Reset()
    {
        m_tr = this.gameObject.GetComponent<TrailRenderer>();
        m_tr.time = Mathf.Infinity; //消失時間の設定 Infで無限に存在(消失しない)
        m_tr.widthMultiplier = 0.01f; //線の太さ
        m_tr.minVertexDistance = 0.01f; //頂点間の距離 曲線の滑らかさに起因
    }

BoneId

お絵かきするためのTrailRendererの位置を人差し指の指先に合わせます。
先述の参考リンクにもあるように、下記でそれぞれの関節の位置を取得できます。

人差し指の先端場合
Vector3 indexTipPos = m_ovrSkeleton.Bones[(int)OVRSkeleton.BoneId.Hand_IndexTip].Transform.position;

Editor上でPlayModeにした場合(LINKなど無しで)、
ArgumentOutOfRangeExceptionが出ます。気にせずビルドしたらいけました。

GetFingerPinchStrength

今回は指を伸ばした状態でのみ、お絵かきを可能にしました。
最初はGetFingerIsPinchingで引数にIndexを指定していましたが、
指を曲げている、曲げていない の判定が少し厳しかったので断念しました。

なので、指を完全に伸ばした状態(GetFingerPinchStrength == 0)を判定に用いることにしました。

思いのほかうまくいったので今回のお遊びはここまでとしました。

その他詰まった箇所

ビルド後立ち上げたアプリで"ハンドトラッキング未対応"みたいなのが出る

画像の箇所をHands Only or Controllers And Handsに直せばOKです。
HandOnly.PNG

ビルドできない

Android SDKなどのエラーが出てたので
Unityの再インストール(Android SDKなども合わせて)で直りました。
LTSの最新版入れました。

まとめ

今回は超簡易版として作りましたので、
既存のコンポーネントであるTraiRendererを使用しています。

色を変えたりは簡単に実装できそうですが、
消しゴム機能を実装する際にTraiRendererで可能なのか?
というのが懸念の一つです。

消しゴム機能で好きな箇所だけ自由に消したい...となると
お絵かき機能自体を自前で用意しないと厳しいかもしれません。

何か良い方法あればアドバイスください。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Unity(C#)】ハンドトラッキングで簡単版VRお絵かきアプリ

ハンドトラッキング

OculusQuestにとんでもないテクノロジーがやってきました。(今更)
まだベータ版みたいですが、ハンドトラッキングがアップデートで実装されました。(今更)

お勉強がてら中身をいじってみようと思います。
今回は下記記事を参考に、簡易版お絵かきアプリを実装してみようと思います。

【参考リンク】:OculusQuest ハンドトラッキングSDKから、指Boneの情報を取得し分析する

デモ

なかなかのクオリティですがやりたいことができました。
DrawDemo.gif

実装方法としては、
人差し指を完全に伸ばした状態のときにTrailRendererEmittingtrueに、
それ以外の時はfalseにしているだけです。

コード

using UnityEngine;
/// <summary>
/// 適当にオブジェクト作ってアタッチ
/// </summary>
[RequireComponent(typeof(TrailRenderer))]
public class DrawFunction : MonoBehaviour
{
    [SerializeField]
    OVRHand m_oVRHand;

    [SerializeField]
    OVRSkeleton m_ovrSkeleton;

    [SerializeField]
    OVRHand.HandFinger m_handFingerType;

    TrailRenderer m_tr;

    void Reset()
    {
        m_tr = this.gameObject.GetComponent<TrailRenderer>();
        m_tr.time = Mathf.Infinity;
        m_tr.widthMultiplier = 0.01f;
        m_tr.minVertexDistance = 0.01f;
    }

    void Start()
    {
        m_tr = this.gameObject.GetComponent<TrailRenderer>();
    }

    void Update()
    {
        Vector3 indexTipPos = m_ovrSkeleton.Bones[(int)OVRSkeleton.BoneId.Hand_IndexTip].Transform.position;
        this.gameObject.transform.position = indexTipPos;

        if (m_oVRHand.GetFingerPinchStrength(m_handFingerType) == 0)
        {
            m_tr.emitting = true;
        }
        else
        {
            m_tr.emitting = false;
        }
    }
}

TrailRenderer

Unity様が用意した神コンポーネントのおかげでお絵かき機能自体は
ノーコーディングで実装できています。

細かい設定はResetメソッドの中で行っています。
RequireComponentで勝手に引っ付いてくるようにして、
Resetで初期設定を行う王道スタイルです。

    TrailRenderer m_tr;

    void Reset()
    {
        m_tr = this.gameObject.GetComponent<TrailRenderer>();
        m_tr.time = Mathf.Infinity; //消失時間の設定 Infで無限に存在(消失しない)
        m_tr.widthMultiplier = 0.01f; //線の太さ
        m_tr.minVertexDistance = 0.01f; //頂点間の距離 曲線の滑らかさに起因
    }

BoneId

お絵かきするためのTrailRendererの位置を人差し指の指先に合わせます。
先述の参考リンクにもあるように、下記でそれぞれの関節の位置を取得できます。

人差し指の先端場合
Vector3 indexTipPos = m_ovrSkeleton.Bones[(int)OVRSkeleton.BoneId.Hand_IndexTip].Transform.position;

Editor上でPlayModeにした場合(LINKなど無しで)、
ArgumentOutOfRangeExceptionが出ます。気にせずビルドしたらいけました。

GetFingerPinchStrength

今回は指を伸ばした状態でのみ、お絵かきを可能にしました。
最初はGetFingerIsPinchingで引数にIndexを指定していましたが、
指を曲げている、曲げていない の判定が少し厳しかったので断念しました。

なので、指を完全に伸ばした状態(GetFingerPinchStrength == 0)を判定に用いることにしました。

思いのほかうまくいったので今回のお遊びはここまでとしました。

その他詰まった箇所

ビルド後立ち上げたアプリで"ハンドトラッキング未対応"みたいなのが出る

画像の箇所をHands Only or Controllers And Handsに直せばOKです。
HandOnly.PNG

ビルドできない

Android SDKなどのエラーが出てたので
Unityの再インストール(Android SDKなども合わせて)で直りました。
LTSの最新版入れました。

まとめ

今回は超簡易版として作りましたので、
既存のコンポーネントであるTraiRendererを使用しています。

色を変えたりは簡単に実装できそうですが、
消しゴム機能を実装する際にTraiRendererで可能なのか?
というのが懸念の一つです。

消しゴム機能で好きな箇所だけ自由に消したい...となると
お絵かき機能自体を自前で用意しないと厳しいかもしれません。

何か良い方法あればアドバイスください。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

SharePoint の権限を CSOM のオブジェクト視点で見る

SharePoint のコンテンツは大別すると、サイト、リスト、アイテムの3種類に分けられます。
そして、それらのコンテンツには、ユーザー、または、グループ毎に権限が割り当てられています。

本投稿では、サイトの権限を例に挙げて、SharePoint の権限と CSOM のオブジェクトを紐づけて、CSOM のオブジェクトへの理解を深めることを目的とします。

環境

  • .NET Framework 4.6.1
  • Microsoft.SharePointOnline.CSOM 16.1.19515.12000
  • SharePointPnPCoreOnline 3.16.1912

サンプルコード

サイトの権限

[サイトのコンテンツ] > [サイトの設定] > [サイトの権限] を開くと、以下のような画面が表示されます。サイトの権限画面に表示される内容を CSOM のオブジェクトに置き換えると以下の通りです。
image.png

  • RoleAssignmentCollection … サイトの権限を割り当てられたユーザー(グループ)の一覧
  • RoleAssignment … サイトの権限を割り当てられたユーザー(グループ)の単位
  • Principal … ユーザー(グループ)のこと
  • RoleDefinitionBindingCollection … ユーザー(グループ)に割り当てられたアクセス許可レベルの一覧
  • RoleDefinition … アクセス許可レベルの単位

アクセス許可レベル

サイトの権限画面の [権限] タブから [アクセス許可レベル] をクリックすると、以下のような画面が表示されます。アクセス許可レベル画面に表示される内容を CSOM のオブジェクトに置き換えると以下の通りです。
image.png
image.png

  • RoleDefinitionCollection … アクセス許可レベル一覧
  • RoleDefinition … アクセス許可レベルの単位
  • RoleType … アクセス許可レベルの種別(既定のアクセス許可レベル以外は None)
  • BasePermissions … アクセス許可レベルに含まれる権限一覧(PermissionKind の論理OR演算結果)
  • PermissionKind … アクセス許可レベルに含める権限の単位

CSOM でサイトの権限を出力

SharePoint コンテンツに割り当てられた権限情報を出力するサンプルコードです。
SecurableObject クラスを継承するサイト(Web)、リスト(List)、アイテム(ListItem)クラスで流用することができます。

securableObject.EnsureProperties(
    // オブジェクトの権限を読み込む
    w => w.RoleAssignments.Include(
        r => r.Member.Title,
        r => r.Member.PrincipalType,
        // 対象オブジェクトの権限を与えられたユーザー or SPグループのアクセス許可レベルを読み込む
        r => r.RoleDefinitionBindings.Include(
            d => d.BasePermissions,
            d => d.Name,
            d => d.RoleTypeKind)));

// 対象オブジェクトの権限を出力
foreach (var roleAssignment in securableObject.RoleAssignments)
{
    foreach (var roleDefinition in roleAssignment.RoleDefinitionBindings)
    {
        Console.WriteLine($"{roleAssignment.Member.Title} " +
                          $"| {roleAssignment.Member.PrincipalType} " +
                          $"| {roleDefinition.RoleTypeKind} " +
                          $"| {roleDefinition.BasePermissions.GetHashCode()} " +
                          $"| {roleDefinition.Name}");
    }
}

// 出力結果
// ContosoCommunication 所有者 | SharePointGroup | Administrator | 2147483646 | フル コントロール
// ContosoCommunication 所有者 | SharePointGroup | WebDesigner | 1012866479 | デザイン
// ContosoCommunication 閲覧者 | SharePointGroup | Reader | 138613009 | 閲覧
// ContosoCommunication メンバー | SharePointGroup | Editor | 1011031199 | 編集
// user 100 | User | None | 1011029151 | 投稿コピー

CSOM でサイトのアクセス許可レベルを出力

サイトのアクセス許可レベルを出力するサンプルコードです。
アクセス許可レベルはトップサイトで定義されています。参照するだけであれば何れかのサイト(Web)クラスのオブジェクトから取得できます。

web.EnsureProperties(
    // サイトのアクセス許可レベルを読み込む
    w => w.RoleDefinitions.Include(
        r => r.RoleTypeKind,
        r => r.BasePermissions,
        r => r.Name,
        r => r.Description));

// サイトのアクセス許可レベルを出力
foreach (var roleDefinition in web.RoleDefinitions)
{
    Console.WriteLine($"{roleDefinition.RoleTypeKind} " +
                      $"| {roleDefinition.BasePermissions.GetHashCode()} " +
                      $"| {roleDefinition.Name} |" +
                      $"| {roleDefinition.Description}");
}

// 出力結果
// Administrator | 2147483646 | フル コントロール || 完全な制御が可能です。
// WebDesigner | 1012866479 | デザイン || 表示、追加、更新、削除、承認、カスタマイズができます。
// Editor | 1011031199 | 編集 || リストを追加、編集、削除できます。リスト アイテムとドキュメントを表示、追加、更新、削除できます。
// Contributor | 1011029151 | 投稿 || リスト アイテムとドキュメントを表示、追加、更新、および削除できます。
// Reader | 138613009 | 閲覧 || ページとリスト アイテム の表示、およびドキュメントのダウンロードができます。
// Guest | 134287408 | 制限付きアクセス || 権限を与えられている場合は、特定のリスト、ドキュメント ライブラリ、リスト アイテム、フォルダー、またはドキュメントを表示できます。
// None | 1011029151 | 投稿コピー ||

参照

SharePoint の権限レベルについて | Microsoft Docs

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

C#で外部ライブラリに頼らず三平方の定理(ピタゴラスの定理)を計算してみた

前置き

「もっと良い求め方ありますよ!」
「もっと良いソースありますよ!」
といったご指摘もあるかと思います。
当記事はあくまで「正しい答えをプログラミングで導く」ことを目的としたものです。
どうか、暖かな目でご覧いただけますと幸いです。

本題

三平方の定理とは...

image.png

画像のような直角三角形において、

\begin{equation}a^2+b^2=c^2\end{equation}

が成り立つというものです。

これを、C#でMathクラスに頼って実装すると...

//c = a^2 + b^2 である.
c = Math.Sqrt(Math.Pow(a, 2) + Math.Pow(b, 2));

上記のように、簡単に実装することができます。
PowPowerのことで、ある値の累乗を求める関数です。
今回の場合、変数a,bの2乗を求めています。
そして、SqrtSquare root(平方根)のことで、ある数の平方根を求めます。

これらの必要な処理を独自で実装してみます。

Power関数

//<summary>
//ある値の累乗を求める関数
//<param name="v1">求めたい値</param>
//<param name="accuracy">指定する累乗</param>
//</summary>
public static double Power(double v1, Int32 v2)
{
    if (v2 < 1) return 0;

    Int32 counter;
    double result = 1;
    Boolean isNegative = v2 < 0;

    //変数が負の値であることも想定
    //nn = v2 < 0 ? (-1) * v2 : v2;と同上
    if (isNegative)
    {
        counter = (-1) * v2;
    }
    else
    {
        counter = v2;
    }


    for (Int32 j = 0; j < counter; j++)
    {
        result *= v1;
    }

    if (isNegative)
    {
        result = (1 / result);
    }

    return result;
}

SquareRoot関数

//<summary>
//平方根を求める関数
//<param name="v1">平方根を求めたい値</param>
//<param name="accuracy">精度。9~10が正確。初期値10</param>
//</summary>
public static double SquareRoot(double v1, Int32 accuracy = 10)
{
    if (accuracy < 1) return 0;

    double result = v1;
    for(int x = 0; x < accuracy; x++)
    {
        //result = (result + v1 / result) / 2.0 と同上
        var n1 = v1 / result;
        var n2 = result + n1;
        result = n2 / 2.0;
    }
    return result;
}

実行結果

確認用として、本家のものと比較してみました。

ソースコード:

Program.cs
using System;

namespace MathSqrt
{
    class MainClass
    {
        //a^2 + b^2 = c^2
        static double a = 12;
        static double b = 5;
        static double c = 0;

        public static void Main(string[] args)
        {
            Console.WriteLine("Hello Math!");

            var na = MyMath.Power(a, 2);
            var nb = MyMath.Power(b, 2);
            c = MyMath.SquareRoot(na+nb);

            Console.WriteLine("三平方の定理(本家Math): {0}", Math.Sqrt(Math.Pow(a, 2) + Math.Pow(b, 2)));
            Console.WriteLine("三平方の定理(自作Math): {0}", c);

            Console.ReadKey();
        }
    }
}

image.png

最後に

最後までご覧いただきありがとうございました。
誤字脱字/間違い等ございましたら、コメントにてご指摘いただけますと幸いです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ASP.NET Core のWebアプリケーションを docker-compose で Dockerアプリケーション として構築する

概要

ASP.NET CoreのサンプルWebアプリケーションを題材として、docker-compose による Dockerアプリケーション の動作環境を構築する例を紹介します。

環境

  • Windows10
  • Visual Studio 2019
  • ASP.NET Core 3.1
  • Docker for Windows

サンプルアプリケーション

題材にしたサンプルアプリケーションです。
https://github.com/tYoshiyuki/dotnet-core-web-sample

単純なToDoのWebアプリケーションです。トランザクションデータはデータベース (SQL Server) に保存しています。
これを Docker を利用して コンテナ化 してみましょう。

システム構成

今回は、Docker for Windows を利用して、ローカルPC上にDocker環境を構築します。ローカル開発時は、IIS Express と LocalDB を利用して開発を行いましたが、これを変更します。フロントエンドは Nginx、データベースは SQL Express の Dockerイメージを利用し、アプリケーションのコンテナを含めて3つのコンテナで構築します。構成イメージは下記の通りです。

image.png

通常、ASP.NET Core アプリケーションを IIS でホストする場合には、ASP.NET Coreのミドルウェアにより、アプリケーションはIISと統合的に動作します。Nginxをリバースプロキシとしてフロントエンドに構築する場合は、ASP.NET Core アプリケーションは Kestrel で動作させておき、そこに対してHTTP通信を転送します。

Dockerfile

1. ASP.NET Core アプリケーション

まずは、ASP.NET Core アプリケーションをコンテナ化するために、Dockerfileを作成します。Visual Studio > プロジェクト > 右クリック Dockerサポート > ターゲットOS Linux から追加できる Dockerfile をベースにして編集を行います。

Dockerfile
FROM mcr.microsoft.com/dotnet/core/aspnet:3.1-buster-slim AS base
WORKDIR /app

FROM mcr.microsoft.com/dotnet/core/sdk:3.1-buster AS build
WORKDIR /src
COPY ["DotNetCoreWebSample.Web.csproj", "./"]
RUN dotnet restore "DotNetCoreWebSample.Web.csproj"
COPY . .
WORKDIR "/src/."
RUN dotnet build "DotNetCoreWebSample.Web.csproj" -c Release -o /app/build

FROM build AS publish
RUN dotnet publish "DotNetCoreWebSample.Web.csproj" -c Release -o /app/publish

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENV ASPNETCORE_URLS http://*:5000
ENTRYPOINT ["dotnet", "DotNetCoreWebSample.Web.dll"]

Dockerfileの解説についてはここでは省略しますが、デフォルトでマルチステージビルドを行い、Dockerイメージサイズを削減するような設定になっています。ポイントとしては [ENV ASPNETCORE_URLS http://*:5000] で、Kestrelが待ち受けを行うポート番号を指定している部分です。後々、Nginxでリクエストを転送する際における、送信先ポート番号になります。

また、ソースコードも公式ドキュメントに従い、若干ですが変更を行います。
https://docs.microsoft.com/ja-jp/aspnet/core/host-and-deploy/linux-nginx?view=aspnetcore-3.1

Startup.cs
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            // ・・・一部省略・・・

            app.UseForwardedHeaders(new ForwardedHeadersOptions
            {
                ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
            });

            // ・・・一部省略・・・

リバースプロキシを経由した際における、X-Forwarded-For・X-Forwarded-Protoヘッダの転送を行うようにします。
これは、IIS上でASP.NET Coreアプリケーションを動作させた場合は、既定で有効になっているので注意が必要です。

公式ドキュメントにも記載があるため、目を通しておくことをお勧めします。
https://docs.microsoft.com/ja-jp/aspnet/core/host-and-deploy/proxy-load-balancer?view=aspnetcore-3.1

2. Nginx

次にNginx用の設定を作成します。

Dockerfile
FROM nginx:latest

ENV ENTRYKIT_VERSION 0.4.0

RUN apt-get update && apt-get install -y wget \
  && wget https://github.com/progrium/entrykit/releases/download/v${ENTRYKIT_VERSION}/entrykit_${ENTRYKIT_VERSION}_Linux_x86_64.tgz \
  && tar -xvzf entrykit_${ENTRYKIT_VERSION}_Linux_x86_64.tgz \
  && rm entrykit_${ENTRYKIT_VERSION}_Linux_x86_64.tgz \
  && mv entrykit /usr/local/bin/ \
  && entrykit --symlink

COPY ./nginx.conf.tmpl /etc/nginx/nginx.conf.tmpl

ENTRYPOINT ["render", "/etc/nginx/nginx.conf", "--"]

CMD [ "nginx", "-g", "daemon off;" ]

nginx.conf.tmpl
worker_processes 1;

events { worker_connections 1024; }

http {

    sendfile on;

    upstream web-app {
        server {{ var "BACKEND_HOST" | default "localhost:5000" }};
    }

    server {
        listen 80;
        server_name $hostname;
        location / {
            proxy_pass         http://web-app;
            proxy_redirect     off;
            proxy_http_version 1.1;
            proxy_cache_bypass $http_upgrade;
            proxy_set_header   Upgrade $http_upgrade;
            proxy_set_header   Connection keep-alive;
            proxy_set_header   Host $host;
            proxy_set_header   X-Real-IP $remote_addr;
            proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header   X-Forwarded-Proto $scheme;
            proxy_set_header   X-Forwarded-Host $server_name;
        }
    }
}

ポイントとしてはEntrykitを利用して、アプリケーションサーバの宛先 (BACKEND_HOST) を外部注入可能にしておきます。これに関しては、後ほど docker-compose.yml で解説を行います。

3. SQL Server Express

最後に SQL Server Express です。サンプルアプリケーションの機能としてはLocalDBで十分なのですが、公式にDockerイメージが存在しないようなので、SQL Server Expressを利用します。尚、実際にはDBデータを永続化する設定が必要ですが、今回は省略しています。

Dockerfile
FROM mcr.microsoft.com/mssql/server:2017-latest-ubuntu

docker-compose.yml

作成した各コンテナを docker-compose を利用して起動してみましょう。

docker-compose.yml
version: '3.4'

services:
  app:
    build:
      context: ./DotNetCoreWebSample.Web
      dockerfile: Dockerfile
    environment:
      ConnectionStrings__DefaultConnection: "Server=sqlexpress;Database=master;User ID=sa;Password=P@ssw0rd;initial catalog=dotnetcorewebsample;MultipleActiveResultSets=True;App=EntityFramework;"
    expose:
      - 5000
    depends_on:
      - sqlexpress

  sqlexpress:
    build:
      context: ./docker/sqlexpress
      dockerfile: Dockerfile
    environment:
      MSSQL_PID: "Express"
      ACCEPT_EULA: "Y"
      SA_PASSWORD: "P@ssw0rd"
    ports:
      - 1433:1433

  web:
    build:
      context: ./docker/nginx
    environment:
      BACKEND_HOST: "app:5000"  
    ports: 
      - 80:80

記載が長いですが、何点かポイントを解説します。

まず、 ConnectionStrings__DefaultConnection: "Server=sqlexpress;Database=master;User ID=sa;Password=P@ssw0rd;initial catalog=dotnetcorewebsample;MultipleActiveResultSets=True;App=EntityFramework;" の部分は、SQL Server Express への接続先情報を設定しています。コンテナ起動時に appsettings.json で設定している内容を上書きします。ネストされたJSONの要素 (ConnectionStrings > DefaultConnection) を表現する場合に、アンダーバー2つ (__) を使用します。

BACKEND_HOST: "app:5000" の部分で、ASP.NET Core アプリケーションへの送信先を設定しています。docker-composeでは、サービス名で各コンテナの宛先を解決出来るため、上記のような設定になります。

docker-composeコマンドを実行し、各コンテナを起動します。

docker-compose up -d

実行後 http://localhost でアクセスし、動作を確認します。

image.png

まとめ

docker-composeを用いて、Dockerアプリケーションを構築する例を紹介させていただきました。
クロスプラットフォームを特徴とする .NET Core なので、Dockerを用いた開発も盛り上がると良いなと思います。

ASP.NET Core と Azure で開発する場合、Web Apps や SQL Database といった優れた PaaS を利用することで、手軽にスケーラブルなアプリケーションを構築することが出来ます。そのため、Docker のようなコンテナ技術を利用するシーンは、あまり多く無いかも知れません。

ですが、ASP.NET Coreのアプリケーションをコンテナ化することで、AWS や GCP といった別クラウドサービスへの転用や、Kubernetesに代表される コンテナ オーケストレーション の恩恵を受けることが出来るようになります。システムアーキテクチャの選択肢として、考慮する価値は十分にあるのではないのでしょうか。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む