- 投稿日:2020-01-03T23:01:29+09:00
C#で画像処理:透明な領域を切り取る
欲しいもの
物体のまわりに透明な領域がある画像から
物体が描かれている領域だけを
切り出してファイルに保存したい。
※画像はいらすとやの黒はんぺん
やること
- 画像ファイルを読み込む
- 透明でない領域を検出する
- 画像の一部を切り出す
- 画像を保存する
0. 準備
画像処理には
System.Drawing名前空間の API を使うので using しておく。using System.Drawing;.NET Framework の場合、
System.Drawingアセンブリへの参照を追加する必要あり。.NET Core の場合、
System.Drawing.CommonNuGet パッケージのインストールが必要。dotnet add package System.Drawing.CommonLinux の場合、
libgdiplusも必要になるのでインストールしておく。sudo apt install libgdiplus1. 画像ファイルを読み込む
画像ファイルを読むには、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オブジェクトの透明でない領域の矩形を検出する。
ピクセルの色の検出は、画像をバイト列に変換して値を参照することでできる。
PixelFormat.Format32bppArgbフォーマットでは1ピクセル4バイト、BGRAの順に1バイトずつ入る。※リトルエンディアンの場合例えば次のようなサイズ 2x2 の画像があった場合、
こんなバイト列になる。
透明でないピクセルの上、下、左、右(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 が生成された。
ソースコード全体
参考
- 投稿日:2020-01-03T22:15:34+09:00
cscの作法 その31
概要
cscの作法、調べてみた。
練習問題やってみた。練習問題
真理値表を表示せよ。
写真
サンプルコード
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()); } }以上。
- 投稿日:2020-01-03T19:20:51+09:00
cscの作法 その30
概要
cscの作法、調べてみた。
練習問題やってみた。練習問題
datagridのセルクリックでカレンダーを表示せよ。
写真
サンプルコード
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()); } }以上。
- 投稿日:2020-01-03T19:15:15+09:00
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()); } }以上。
- 投稿日:2020-01-03T18:30:54+09:00
cscの作法 その28
概要
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 = "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()); } }以上。
- 投稿日:2020-01-03T14:52:05+09:00
Windows GUIプログラミング入門22 画像ビューア
■はじめに
キーワード:画像表示, 複数画面表示, ファイル選択ダイアログ, 子画面全終了, マウスホイール
[注意]
これまでの回で説明済みの操作方法等は、説明を省略したり簡略化している場合があります。■開発環境
- 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版でも動作することは確認済みです。◇メイン画面のレイアウト作成
メイン画面のレイアウト用コントロールを
GridからViewboxに変更します。
StackPanel,Buttonを配置してプロパティやスタイルの設定をします。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にします。
Imageコントロールやボタン等を配置、プロパティ設定をします。
+,-ボタンはリピートボタンを使います。ボタンを押し続けている間、Clickイベントが連続して発生します。
RepeatButtonはツールボックスに無いのでタグを手打ちしてください。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.csusing 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.csusing 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>■動かしてみる
ウィンドウサイズを大きくしてみます。
Viewboxを使っているので拡大縮小してもきれいに表示されます。「画像を開く」ボタンを押すと、ファイル選択ダイアログが表示されるので、画像を選択します。
続けて他にも画像を開いてみます。
画像の上でマウスホイールを奥に回すと拡大、手前に回すと縮小します。
+ボタン、-ボタンでも拡大、縮小できます。このボタンは長押しに対応しています。「画像をすべて閉じる」ボタンを押すと、画像ウィンドウが一気に全部閉じます。
おしまい
- 投稿日:2020-01-03T13:07:03+09:00
cscの作法 その27
概要
cscの作法、調べてみた。
練習問題やってみた。練習問題
オセロゲームを作れ。
写真
サンプルコード
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()); } }以上。
- 投稿日:2020-01-03T12:23:58+09:00
DictionaryでCustom Buttonを作り、MainWindowで使用する方法
やりたい事
1. 自分でボタンをデザインしたい。
2. 詳細デザインをDictionary.xamlに記述し、"CustomButton"をMainWindow.xamlで使用する。MainWindow.xaml内部をすっきりさせたい。
結果
下の画像が結果である。
一番上(Button1)は、Windowsのチュートリアル[1]に記述されていたもの。二番目(Button2)は、参考文献[2]に記述されていたもの。三番目(Button3)は、デフォルトを表わす。
参考文献
[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には、デフォルトの内容です。
- 投稿日:2020-01-03T12:12:12+09:00
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 で表示 通常
DocumentText に文字列を設定した場合は IE と同じように表示されない。。
コントロール内部で HTML の HEAD タグと誤認してそう。
WebBrowser で表示 1 通常コードthis.webBrowser1.DocumentText = xml;改良
XML 文字列を一旦ファイルに保存し、Navigate メソッドを呼ぶことで、IE と同じような表示となった。
WebBrowser で表示 2 改良コードvar path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.xml"); File.WriteAllText(path, xml); this.webBrowser1.Navigate(path); File.Delete(path);
- 投稿日:2020-01-03T10:59:43+09:00
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()); } }以上。
- 投稿日:2020-01-03T10:54:41+09:00
cscの作法 その25
概要
cscの作法、調べてみた。
webbrowser使ってみた。写真
サンプルコード
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()); } }以上。
- 投稿日:2020-01-03T10:45:40+09:00
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()); } }以上。
- 投稿日:2020-01-03T09:49:00+09:00
cscの作法 その23
概要
cscの作法、調べてみた。
練習問題やってみた。練習問題
コッホ曲線を表示せよ。
写真
サンプルコード
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()); } }以上。
- 投稿日:2020-01-03T09:13:23+09:00
cscの作法 その22
概要
cscの作法、調べてみた。
練習問題やってみた。練習問題
レイトレースを表示せよ。
参考にしたページ
https://www.codeproject.com/Articles/19732/Simple-Ray-Tracing-in-C
写真
サンプルコード
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()); } }以上。
- 投稿日:2020-01-03T09:06:38+09:00
cscの作法 その21
概要
cscの作法、調べてみた。
練習問題やってみた。練習問題
マンデルブロ集合を表示せよ。
参考にしたページ
http://ou812.web.fc2.com/CsTips/AnalysisMandelbrot.html
写真
サンプルコード
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()); } }以上。
- 投稿日:2020-01-03T04:03:05+09:00
【Unity(C#)】ハンドトラッキングで簡易版VRお絵かきアプリ
ハンドトラッキング
OculusQuestにとんでもないテクノロジーがやってきました。(今更)
まだベータ版みたいですが、ハンドトラッキングがアップデートで実装されました。(今更)お勉強がてら中身をいじってみようと思います。
今回は下記記事を参考に、簡易版お絵かきアプリを実装してみようと思います。【参考リンク】:OculusQuest ハンドトラッキングSDKから、指Boneの情報を取得し分析する
デモ
実装方法としては、
人差し指を完全に伸ばした状態のときにTrailRendererのEmittingをtrueに、
それ以外の時は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 OnlyorControllers And Handsに直せばOKです。
ビルドできない
Android SDKなどのエラーが出てたので
Unityの再インストール(Android SDKなども合わせて)で直りました。
LTSの最新版入れました。まとめ
今回は超簡易版として作りましたので、
既存のコンポーネントであるTraiRendererを使用しています。色を変えたりは簡単に実装できそうですが、
消しゴム機能を実装する際にTraiRendererで可能なのか?
というのが懸念の一つです。消しゴム機能で好きな箇所だけ自由に消したい...となると
お絵かき機能自体を自前で用意しないと厳しいかもしれません。何か良い方法あればアドバイスください。
- 投稿日:2020-01-03T04:03:05+09:00
【Unity(C#)】ハンドトラッキングで簡単版VRお絵かきアプリ
ハンドトラッキング
OculusQuestにとんでもないテクノロジーがやってきました。(今更)
まだベータ版みたいですが、ハンドトラッキングがアップデートで実装されました。(今更)お勉強がてら中身をいじってみようと思います。
今回は下記記事を参考に、簡易版お絵かきアプリを実装してみようと思います。【参考リンク】:OculusQuest ハンドトラッキングSDKから、指Boneの情報を取得し分析する
デモ
実装方法としては、
人差し指を完全に伸ばした状態のときにTrailRendererのEmittingをtrueに、
それ以外の時は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 OnlyorControllers And Handsに直せばOKです。
ビルドできない
Android SDKなどのエラーが出てたので
Unityの再インストール(Android SDKなども合わせて)で直りました。
LTSの最新版入れました。まとめ
今回は超簡易版として作りましたので、
既存のコンポーネントであるTraiRendererを使用しています。色を変えたりは簡単に実装できそうですが、
消しゴム機能を実装する際にTraiRendererで可能なのか?
というのが懸念の一つです。消しゴム機能で好きな箇所だけ自由に消したい...となると
お絵かき機能自体を自前で用意しないと厳しいかもしれません。何か良い方法あればアドバイスください。
- 投稿日:2020-01-03T02:37:54+09:00
SharePoint の権限を CSOM のオブジェクト視点で見る
SharePoint のコンテンツは大別すると、サイト、リスト、アイテムの3種類に分けられます。
そして、それらのコンテンツには、ユーザー、または、グループ毎に権限が割り当てられています。本投稿では、サイトの権限を例に挙げて、SharePoint の権限と CSOM のオブジェクトを紐づけて、CSOM のオブジェクトへの理解を深めることを目的とします。
環境
- .NET Framework 4.6.1
- Microsoft.SharePointOnline.CSOM 16.1.19515.12000
- SharePointPnPCoreOnline 3.16.1912
サンプルコード
サイトの権限
[サイトのコンテンツ] > [サイトの設定] > [サイトの権限] を開くと、以下のような画面が表示されます。サイトの権限画面に表示される内容を CSOM のオブジェクトに置き換えると以下の通りです。
- RoleAssignmentCollection … サイトの権限を割り当てられたユーザー(グループ)の一覧
- RoleAssignment … サイトの権限を割り当てられたユーザー(グループ)の単位
- Principal … ユーザー(グループ)のこと
- RoleDefinitionBindingCollection … ユーザー(グループ)に割り当てられたアクセス許可レベルの一覧
- RoleDefinition … アクセス許可レベルの単位
アクセス許可レベル
サイトの権限画面の [権限] タブから [アクセス許可レベル] をクリックすると、以下のような画面が表示されます。アクセス許可レベル画面に表示される内容を CSOM のオブジェクトに置き換えると以下の通りです。
- 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 | 投稿コピー ||参照
- 投稿日:2020-01-03T01:46:33+09:00
C#で外部ライブラリに頼らず三平方の定理(ピタゴラスの定理)を計算してみた
前置き
「もっと良い求め方ありますよ!」
「もっと良いソースありますよ!」
といったご指摘もあるかと思います。
当記事はあくまで「正しい答えをプログラミングで導く」ことを目的としたものです。
どうか、暖かな目でご覧いただけますと幸いです。本題
三平方の定理とは...
画像のような直角三角形において、
\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));上記のように、簡単に実装することができます。
PowはPowerのことで、ある値の累乗を求める関数です。
今回の場合、変数a,bの2乗を求めています。
そして、SqrtはSquare 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.csusing 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(); } } }最後に
最後までご覧いただきありがとうございました。
誤字脱字/間違い等ございましたら、コメントにてご指摘いただけますと幸いです。
- 投稿日:2020-01-03T01:16:00+09:00
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つのコンテナで構築します。構成イメージは下記の通りです。
通常、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 をベースにして編集を行います。DockerfileFROM 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.1Startup.cspublic 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.12. Nginx
次にNginx用の設定を作成します。
DockerfileFROM 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.tmplworker_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データを永続化する設定が必要ですが、今回は省略しています。
DockerfileFROM mcr.microsoft.com/mssql/server:2017-latest-ubuntudocker-compose.yml
作成した各コンテナを docker-compose を利用して起動してみましょう。
docker-compose.ymlversion: '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でアクセスし、動作を確認します。まとめ
docker-composeを用いて、Dockerアプリケーションを構築する例を紹介させていただきました。
クロスプラットフォームを特徴とする .NET Core なので、Dockerを用いた開発も盛り上がると良いなと思います。ASP.NET Core と Azure で開発する場合、Web Apps や SQL Database といった優れた PaaS を利用することで、手軽にスケーラブルなアプリケーションを構築することが出来ます。そのため、Docker のようなコンテナ技術を利用するシーンは、あまり多く無いかも知れません。
ですが、ASP.NET Coreのアプリケーションをコンテナ化することで、AWS や GCP といった別クラウドサービスへの転用や、Kubernetesに代表される コンテナ オーケストレーション の恩恵を受けることが出来るようになります。システムアーキテクチャの選択肢として、考慮する価値は十分にあるのではないのでしょうか。








































