- 投稿日:2020-06-27T23:51:38+09:00
ANTLR4をC#から使ってみる #07 Visitorのスタックトレースを出してみる
ANTLR4をC#から使ってみる #05 Visitorで計算機を作るための試行錯誤で出したスタックトレースの図が良かったので、Listenerでも出した。
プログラムは、ANTLR4をC#から使ってみる #03 Listenerでの計算機作成のための試行錯誤の試しに色々出してみるのもの。
- 投稿日:2020-06-27T22:53:30+09:00
C# Visual Studio 2019 .NET Core 3.1 クラスライブラリプロジェクトでWindows依存機能を使う
Visual Studio 2019の.NET Core 3.1 クラスライブラリプロジェクトは標準ではWindows依存の機能を使用できませんが、プロジェクト ファイルを編集することでWindows依存の機能を使用できます。
手順
- ソリューションエクスプローラーでクラスライブラリプロジェクトの名前を右クリックしてメニューを表示する。
- 「プロジェクト ファイルの編集」をクリックする。
- 内容を次の通り書き換える。具体的にはProjectのSdkを「Microsoft.NET.Sdk.WindowsDesktop」へ変更して、
<UseWindowsForms>true</UseWindowsForms>
を追加しています。<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop"> <PropertyGroup> <TargetFramework>netcoreapp3.1</TargetFramework> <UseWindowsForms>true</UseWindowsForms> </PropertyGroup> </Project>
- プロジェクトの設定を変更している場合はその他の要素が表示されますが、変更する部分はProjectのSdkと
<UseWindowsForms>true</UseWindowsForms>
の追加です。
- 投稿日:2020-06-27T22:00:59+09:00
C# 構造体・クラスのデバッグ表示をDebuggerDisplay属性で制御する
構造体・クラスは変数ウィンドウで
{ConsoleApp1.TestClass}
のように表示されますが、DebuggerDisplay
属性やDebuggerTypeProxy
属性により表示を変更できます。ここでは
DebuggerDisplay
属性のよく使うパターンを紹介します。以下、i
はint
型、f
はfloat
またはdouble
型の変数またはプロパティです。
表記 概要 [DebuggerDisplay("{X}")]
X
の値を二重引用符ありで表示する。[DebuggerDisplay("{X,nq}")]
X
の値を二重引用符なしで表示する。[DebuggerDisplay("X:{X}")]
「X:」に続けて X
の値を二重引用符ありで表示する。[DebuggerDisplay("X:{X,nq}")]
「X:」に続けて X
の値を二重引用符なしで表示する。[DebuggerDisplay("{i.ToString(\"D10\")}")]
i
の値を10桁以上になるまで0で埋めて二重引用符ありで表示する。[DebuggerDisplay("{X.ToString(\"D10\"),nq}")]
i
の値を10桁以上になるまで0で埋めて二重引用符なしで表示する。[DebuggerDisplay("{X.ToString(\"X10\",nq)}]")
i
の大文字16進数表現を10桁以上になるまで0で埋めて二重引用符なしで表示する。[DebuggerDisplay("{f.ToString(\"F2\")}")]
f
の値を小数点以下2桁まで二重引用符ありで表示する。[DebuggerDisplay("{f.ToString(\"F2\"),nq}]")
f
の値を小数点以下2桁まで二重引用符なしで表示する。
- 整数や小数の書式化については以下を参照できます。
Math.Round
なども使えます。
- 投稿日:2020-06-27T16:27:32+09:00
[Unity初心者Tips]FindとGetComponentを実行しエラーにどこから呼び出されたかを出す便利メソッド
なにをやるのか
Unityで、FindしたやつからGetComponentするという処理は、プログラムの設計によってシングルトンでやってないやつだとよくやります。それの便利版です。説明要らない方は最後へジャンプ。
GameObject gameObject = GameObject.Find("HOGEgo"); HogeComponent hogeComponent = gameObject.GetComponent<HogeComponent>();親切にエラーを出してあげる
この時、それぞれでnullの可能性があるので、その場合にはエラーを吐いた方が
うるさいゲームデザイナーの相手をしなくていい親切に何がおかしいか判った方が効率が好いので、以下のコードになります。HogeComponent hogeComponent; GameObject gameObject = GameObject.Find("HOGEgo"); if(gameObject == null){ Debug.LogError("HOGEgo is not found"); } else{ hogeComponent = gameObject.GetComponent<HogeComponent>(); if(gameObject == null){ Debug.LogError("HogeComponent is not found"); } }共用できるclassのメソッドにする
折角なので、適当なclassで何処でも使えるようにジェネリック化したstaticのメソッドにしましょう。
using System.Collections; using System.Collections.Generic; using UnityEngine; namespace CommonTools { public class Tools : MonoBehaviour { public static T GetComponentInObject<T>(string objectName) { T c = default(T); GameObject gameObject = GameObject.Find(objectName); if (gameObject == null) { Debug.LogError(objectName + " is not found"); } else { c = gameObject.GetComponent<T>(); if (hogeComponent == null) { Debug.LogError( nameof(T)+ " is not found"); } } return c; } } }まあまあ良い感じですね!ここまではありがちです。
どこでやったか判るようにする
上のままでも問題ないんですけど、色々なところから呼び出されるのでエラーを見ても呼び出し元が判らないと修正に困るという問題があります。
うるさいゲームデザイナーの相手をしなくていい親切に何がおかしいか判った方が効率が好いので、ここで呼び出し元が判るようにします。それには以下の仕組みを使います。呼び出し元はstackから辿れる
アセンブラの知識がある方はご存知の通り、メソッドの呼び出し毎に戻りアドレスがpushされてstackに格納されて積まれます。そこを辿れば、どこから呼び出されているのか判るわけです。
参考:StackFrame クラス
https://docs.microsoft.com/ja-jp/dotnet/api/system.diagnostics.stackframe?view=netcore-3.1リフレクションで名前を取得する
C#のリフレクションを利用すると、呼び出し元の名前が取得できます。
参考:MemberInfo.ReflectedType プロパティ
https://docs.microsoft.com/ja-jp/dotnet/api/system.reflection.memberinfo.reflectedtype?view=netcore-3.1エラーで呼び出し元を出す完成版
上記を組み合わせてエラーのためのメソッドを
Find
とGetComponent
で共有化した完成例がこちらです。
https://gist.github.com/JunShimura/866cfe8736e4b40f35bf50113748cdd0using System.Collections; using System.Collections.Generic; using UnityEngine; namespace CommonTools { public class Tools : MonoBehaviour { public static T GetComponentInObject<T>(string objectName) { T c = default(T); GameObject go = GameObject.Find(objectName); if (go == null) { LogError(objectName); } else { c = go.GetComponent<T>(); if (c == null) { LogError(objectName + "." + nameof(T)); } } return c; } static private void LogError(string s) { // StackFrameクラスでstackを2階層戻る System.Diagnostics.StackFrame objStackFrame = new System.Diagnostics.StackFrame(2); string methodName = objStackFrame.GetMethod().Name; Debug.LogError(s + " is not found at " + objStackFrame.GetMethod().ReflectedType.FullName + "." + objStackFrame.GetMethod().Name); } } }こうすると、
class GameDirector{ void Start() { // hpゲージを取得 hpGaugeImage = Tools.GetComponentInObject<Image>("hogeGauge");
うるさいゲームデザイナーの相手をしなくていい親切に何がおかしいか判った方が効率が好いですね!
- 投稿日:2020-06-27T11:29:56+09:00
ドローン(Tello)を自作アプリで操縦する 1
最近ドローンを買いました。
DJI Mavic miniとTelloです。
なぜ2つ買ったかというと、性能的にはMavic miniが上ですが、Telloはこんな感じで自作プログラムで飛ばすことができます。面白そう。ただ、公式で用意されているEDUはブロックプログラミングみたい。
これはこれで教育的には非常にいいものかもしれませんが、個人的には何ならCで組み込みドローン開発したい勢いなので、もっとガンガンやりたい気分でした。そこで、いい感じのを見つけました。
TELLO SDK
SDKと言っているのでライブラリとかかなあと思ったら、素のUDP通信リファレンスでした。(Pythonライブラリはあるみたいですが)
TELLOがルーター&アクセスポイントになって、UDPでコマンド送ると色々できるみたいです。
ということで、中身を見ていきます。'2. ARCHITECTURE
Use Wi-Fi to establish communication between Tello and PC, Mac or Mobile deviceSend Command & Receive Response
Tello IP: 192.168.10.1 UDP PORT:8889 <<- ->> PC/Mac/Mobile
Remark1: Set up a UDP client on PC, Mac or Mobile device to send and receive message
from Tello via the same port.
Remark2: Send “command” command to Tello via UDP PORT 8889 to initiate Tello’s SDK
mode, before sending all other commands.Receive Tello State
Tello IP: 192.168.10.1 ->> PC/Mac/Mobile UDP Server: 0.0.0.0 UDP PORT:8890
Remark3: Set up a UDP server on PC, Mac or Mobile device and listen the message from
IP 0.0.0.0 via UDP PORT 8890. Do Remark2 to start receiving state data if you haven’t.Receive Tello Video Stream
Tello IP: 192.168.10.1 ->> PC/Mac/Mobile UDP Server: 0.0.0.0 UDP PORT:11111
Remark4: Set up a UDP server on PC, Mac or Mobile device and listen the message from
IP 0.0.0.0 via UDP PORT 11111.
Remark5: Do Remark2 if you haven’t. Then send “streamon” command to Tello via UDP
PORT 8889 to start the streaming.UDPの経路は3通りで、ポート番号で役割が分かれています。
- コマンド送ってレスポンスを受ける8889番ポート
- Telloのステータスが常に送られてくる11111番ポート
- 映像が送られてくる11111ポート
これだけあれば映像&センサー情報を受けながらある程度自律運転できそう。
とりあえずコマンド送ってみる
C#でUDPを送ってみます。
using System; using System.Net; using System.Net.Sockets; using System.Text; namespace ConsoleTest { class Program { static void Main(string[] args) { UdpClient udpClient = new UdpClient(50000); udpClient.Connect("192.168.10.1", 8889); while (true) { Byte[] sendBytes = Encoding.ASCII.GetBytes(Console.ReadLine()); udpClient.Send(sendBytes, sendBytes.Length); IPEndPoint RemoteIpEndPoint = new IPEndPoint(IPAddress.Any, 0); Byte[] receiveBytes = udpClient.Receive(ref RemoteIpEndPoint); string returnData = Encoding.ASCII.GetString(receiveBytes); Console.WriteLine(returnData.ToString()); } } } }適当。
command
"command"文字列を送ると"ok"が返ってきました。これでSDK modeになるみたいです。
takeoff
"takeoff"を送ると、離陸しました。
前後左右移動
"forward","back","right","left"で高度と向きを保ったまま前後左右移動できます。
例えば"forward 100"で100cm前進します。前進動作が終わったら"ok"が返ってくるので、その間は何も送らないほうがいいみたいです。上下移動
"up","down"で高度が変わります。
"up 50"で上方向に50cm上昇しました。これから
基本、Tello側は機体制御に集中して、色々操縦はPCとかスマホ側でやる仕組みみたいです。
なので、これから2つくらいやってみます。
- M5Stack or M5Stick Cでコントローラ作る
- IMUで直感的に操作できそう
- 映像から顔認識して顔を追う
- OpenCVとかでできるかなぁと
- まぁ、それっぽいのやってみます
空き時間にちょろちょろとやっていきますー
- 投稿日:2020-06-27T09:55:28+09:00
C# - お手軽Windowsアイコン作成ツールをつくってみた
文字と図形(とりあえず4種類)からなるアイコンをサクッと作れるツールを作ってみた。
キャプチャ
アイコンの保存は「エクスポート」から。
設定項目は「テンプレート」から保存/読みだしできます。注意
- フォントには著作権があるので、作成したアイコンファイルを公開したりすることは問題があるかもしれません。
- アイコンのキャッシュのリフレッシュ処理が環境依存な都合で、Windows10向けになっています。 Windows7の場合は、ソースコード内の
ICON_CACHE_CLEAR_EXE_OPTION
の値を"-ClearIconCache"
に変更すればOK。コンパイル方法
csc /unsafe IconMaker.cs IconUtility.csソースコード - IconMaker.cs
IconMaker.csusing System; using System.Collections; using System.Collections.Generic; using System.Data; using System.Drawing; using System.Drawing.Drawing2D; using System.Drawing.Imaging; using System.IO; using System.Reflection; using System.Runtime.InteropServices; using System.Windows.Forms; using IconUtility; [Serializable] public class SettingsClass { public byte fg1Red; public byte fg1Green; public byte fg1Blue; public byte fg2Red; public byte fg2Green; public byte fg2Blue; public byte bg1Red; public byte bg1Green; public byte bg1Blue; public byte bg2Red; public byte bg2Green; public byte bg2Blue; public bool gradiation; public string fontName; public string textLine1; public string textLine2; public Decimal fontSizeLine1; public Decimal fontSizeLine2; public int yOffsetLine1; public int yOffsetLine2; public string bgFigureTypeName; public int iconSize; } class IconMaker:Form { PictureBox pct; //int iconSize = 64; static readonly int DEFAULT_FONT_SIZE = 14; static readonly int CANVAS_SIZE = 256; static readonly string ICON_CACHE_CLEAR_EXE_NAME = "ie4uinit.exe"; static readonly string ICON_CACHE_CLEAR_EXE_OPTION = "-show"; // windows10 //Windows 10では「ie4uinit.exe -show」、 //それ以外のバージョンでは「ie4uinit.exe -ClearIconCache」 // const int ZOOM = 2; NumericUpDown nudSizeOfIcon; ComboBox cmbBgFigureType; TextBox txtLine1; TextBox txtLine2; NumericUpDown nudFontSizeOfLine1; NumericUpDown nudFontSizeOfLine2; NumericUpDown nudOffsetYLine1; NumericUpDown nudOffsetYLine2; TextBox txtFontName; Button btnColorForeground1; Button btnColorForeground2; Button btnColorBackground1; Button btnColorBackground2; CheckBox chkUseGradation; Bitmap bmpToBeSaved; Font curFontSizeIndependent; int curFontStyle; Color colorForeground1; Color colorForeground2; Color colorBackground1; Color colorBackground2; Pen curPen; Font curFont; Brush curBrush; // RedrawPreview呼びたびに更新 PointF curPoint; GraphicsPath curPath; Graphics curG; bool resumeRedraw; IconMaker() { Text = "Icon Maker"; resumeRedraw = false; colorForeground1 = Color.Blue; colorForeground2 = Color.Black; colorBackground1 = Color.Red; colorBackground2 = Color.Yellow; curFontSizeIndependent = new Font("メイリオ", (float)DEFAULT_FONT_SIZE); curFontStyle = (int)FontStyle.Regular; // Bold , Italic , Strikeout , Underline の bitOR を設定可能 var menuStrip1 = new MenuStrip(); // https://dobon.net/vb/dotnet/control/menustrip.html SuspendLayout(); menuStrip1.SuspendLayout(); var templateMenuItem = new ToolStripMenuItem(){ Text = "テンプレート(&T)"}; var iconMenuItem = new ToolStripMenuItem(){ Text = "エクスポート(&X)"}; menuStrip1.Items.Add(templateMenuItem); menuStrip1.Items.Add(iconMenuItem); templateMenuItem.DropDownItems.Add( new ToolStripMenuItem("開く(&O)...", null, (s,e)=>{OpenTemplateWithDialog();}, Keys.Control | Keys.O) ); templateMenuItem.DropDownItems.Add( new ToolStripMenuItem("保存(&S)...", null, (s,e)=>{SaveTemplateWithDialog();}, Keys.Control | Keys.S) ); iconMenuItem.DropDownItems.Add( new ToolStripMenuItem("アイコン(.ico)として保存(&I)...", null, (s,e)=>{SaveImageWithDialog("ico");}, Keys.Control | Keys.I) ); iconMenuItem.DropDownItems.Add( new ToolStripMenuItem("画像(.png)として保存(&P)...", null, (s,e)=>{SaveImageWithDialog("png");}, Keys.Control | Keys.P) ); Controls.AddRange(new Control[]{ new Label(){ Location = new Point( 10, 35), Size = new Size( 80, 20), Text="アイコンサイズ"}, nudSizeOfIcon = new NumericUpDown(){ Location = new Point(100, 35), Size = new Size( 60, 20), Maximum = 256, Value = 64, Minimum = 16, Increment = 4} }); nudSizeOfIcon.ValueChanged += (s,e)=>{ int iconSize = (int)nudSizeOfIcon.Value; pct.Size = new Size(iconSize,iconSize); RedrawPreview(); }; GroupBox grpText = new GroupBox(){ Location = new Point( 10, 60), Size = new Size(280,95), Text = "テキスト" }; Controls.Add(grpText); grpText.Controls.AddRange(new Control[]{ new Label(){ Location = new Point(140, 20), Size = new Size(60, 20), Text = "サイズ"}, new Label(){ Location = new Point(210, 20), Size = new Size(65, 20), Text = "位置調整"}, new Label(){ Location = new Point( 10, 40), Size = new Size(50, 20), Text = "1行目"}, txtLine1 = new TextBox(){ Location = new Point( 60, 40), Size = new Size(70, 20), Text = "icon" }, nudFontSizeOfLine1 = new NumericUpDown(){ Location = new Point(140, 40), Size = new Size(60, 20), Maximum = 100, Value = 30, Minimum = 1, DecimalPlaces=1 }, nudOffsetYLine1 = new NumericUpDown(){ Location = new Point(210, 40), Size = new Size(60, 20), Maximum = 256, Value = 0, Minimum = -256 }, new Label(){ Location = new Point( 10, 65), Size = new Size(50, 20), Text = "2行目"}, txtLine2 = new TextBox(){ Location = new Point( 60, 65), Size = new Size(70, 20), Text = "maker" }, nudFontSizeOfLine2 = new NumericUpDown(){ Location = new Point(140, 65), Size = new Size(60, 20), Maximum = 100, Value = 15, Minimum = 1, DecimalPlaces=1 }, nudOffsetYLine2 = new NumericUpDown(){ Location = new Point(210, 65), Size = new Size(60, 20), Maximum = 256, Value = 18, Minimum = -256 } }); txtLine1.TextChanged += (s,e)=>{RedrawPreview();}; txtLine2.TextChanged += (s,e)=>{RedrawPreview();}; nudFontSizeOfLine1.ValueChanged += (s,e)=>{RedrawPreview();}; nudFontSizeOfLine2.ValueChanged += (s,e)=>{RedrawPreview();}; nudOffsetYLine1.ValueChanged += (s,e)=>{RedrawPreview();}; nudOffsetYLine2.ValueChanged += (s,e)=>{RedrawPreview();}; GroupBox grpFont = new GroupBox(){ Location = new Point( 10,165), Size = new Size(280,50), Text = "フォント" }; Controls.Add(grpFont); Button btnSelectFont; grpFont.Controls.AddRange(new Control[]{ txtFontName = new TextBox(){ Location = new Point( 10, 20), Size = new Size(170,25), Text = curFontSizeIndependent.Name, ReadOnly = true }, btnSelectFont = new Button(){ Location = new Point(185, 15), Size = new Size( 70,25), Text = "変更..." } }); btnSelectFont.Click += (s,e)=>{SelectAndUpdateFontWithDialog();}; GroupBox grpColor = new GroupBox(){ Location = new Point( 10,225), Size = new Size(280,100), Text = "色" }; Controls.Add(grpColor); grpColor.Controls.AddRange(new Control[]{ new Label(){ Location = new Point( 10, 30), Size = new Size(40,20), Text = "文字"}, btnColorForeground1 = new Button(){ Location = new Point( 50, 20), Size = new Size(30,20), FlatStyle = FlatStyle.Flat}, btnColorForeground2 = new Button(){ Location = new Point( 70, 30), Size = new Size(30,20), FlatStyle = FlatStyle.Flat}, new Label(){ Location = new Point( 10, 70), Size = new Size(40,20), Text = "背景"}, btnColorBackground1 = new Button(){ Location = new Point( 50, 60), Size = new Size(30,20), FlatStyle = FlatStyle.Flat}, btnColorBackground2 = new Button(){ Location = new Point( 70, 70), Size = new Size(30,20), FlatStyle = FlatStyle.Flat}, chkUseGradation = new CheckBox(){Location = new Point(140, 40), Size = new Size(100,25), Text = "グラデーション", Checked = true} }); SetFlatButtonColor(btnColorForeground1, colorForeground1); SetFlatButtonColor(btnColorForeground2, colorForeground2); SetFlatButtonColor(btnColorBackground1, colorBackground1); SetFlatButtonColor(btnColorBackground2, colorBackground2); btnColorForeground1.Click += (s,e)=>{SelectAndUpdateColorWithDialog((Button)s, ref colorForeground1);}; btnColorForeground2.Click += (s,e)=>{SelectAndUpdateColorWithDialog((Button)s, ref colorForeground2);}; btnColorBackground1.Click += (s,e)=>{SelectAndUpdateColorWithDialog((Button)s, ref colorBackground1);}; btnColorBackground2.Click += (s,e)=>{SelectAndUpdateColorWithDialog((Button)s, ref colorBackground2);}; chkUseGradation.Click += (s,e)=>{ ReflectGradationCheckToControls(); RedrawPreview(); }; ReflectGradationCheckToControls(); Controls.Add( pct = new PictureBox(){ Location = new Point(350, 20), Size = new Size(CANVAS_SIZE, CANVAS_SIZE), Image = new Bitmap(CANVAS_SIZE, CANVAS_SIZE), } ); Controls.Add( cmbBgFigureType = new ComboBox(){ Location = new Point( 20, 350), Size = new Size( 80, 20), DropDownStyle = ComboBoxStyle.DropDownList } ); cmbBgFigureType.Items.AddRange(new string[]{"なし", "六角形", "角丸", "丸", "四角"}); cmbBgFigureType.SelectedIndex = 1; cmbBgFigureType.SelectedIndexChanged += (s,e)=>{RedrawPreview();}; ClientSize = new Size(350+CANVAS_SIZE, 400); Load+=(sender,e)=>{RedrawPreview();}; curPen = Pens.Black; curBrush = Brushes.White; // とりあえず初期化しておく Controls.Add(menuStrip1); MainMenuStrip = menuStrip1; menuStrip1.ResumeLayout(false); menuStrip1.PerformLayout(); ResumeLayout(false); PerformLayout(); } void SetFlatButtonColor(Button btn, Color c) { btn.BackColor = c; btn.FlatAppearance.MouseOverBackColor = c; btn.FlatAppearance.MouseDownBackColor = c; } void SelectAndUpdateFontWithDialog() { FontDialog fd = new FontDialog(); fd.Font = curFontSizeIndependent; fd.MaxSize = DEFAULT_FONT_SIZE; fd.MinSize = DEFAULT_FONT_SIZE; fd.FontMustExist = true; fd.AllowVerticalFonts = true; //fd.ShowColor = true; fd.ShowEffects = true; fd.FixedPitchOnly = false; fd.AllowVectorFonts = true; //ダイアログを表示する if (fd.ShowDialog() != DialogResult.Cancel) { curFontSizeIndependent = fd.Font; txtFontName.Text = curFontSizeIndependent.Name; RedrawPreview(); } } void SelectAndUpdateColorWithDialog(Button sender, ref Color c) { ColorDialog cd = new ColorDialog(); cd.Color = c; cd.FullOpen = true; if (cd.ShowDialog() == DialogResult.OK) { c = cd.Color; SetFlatButtonColor(sender, c); RedrawPreview(); } } void ReflectGradationCheckToControls() { bool tmp = chkUseGradation.Checked; btnColorForeground2.Visible = tmp; btnColorBackground2.Visible = tmp; } void moveto(float x, float y) { curPoint.X = x; curPoint.Y = y; } void rmoveto(float rx, float ry) { curPoint.X += rx; curPoint.Y += ry; } void lineto(float x, float y) { curPath.AddLine(curPoint.X, curPoint.Y, x, y); curPoint.X = x; curPoint.Y = y; } void rlineto(float rx, float ry) { curPath.AddLine(curPoint.X, curPoint.Y, curPoint.X+rx, curPoint.Y+ry); curPoint.X += rx; curPoint.Y += ry; } void charpath(string s) { var sf = new StringFormat(); sf.Alignment = StringAlignment.Center; // 横方向の中央 sf.LineAlignment = StringAlignment.Center; // 縦方向の中央 FontFamily ff = curFont.FontFamily; curPath.AddString(s, ff, curFontStyle, curFont.Size, curPoint, sf); } void newpath() { curPath.Reset(); curPath.StartFigure(); } void closepath() { curPath.CloseFigure(); } void stroke() { if ( curG != null ) { curG.DrawPath(curPen, curPath); } } void fill() { if ( curG != null ) { curPath.FillMode = FillMode.Winding; curG.FillPath(curBrush, curPath); } } /* void eofill() { if ( curG != null ) { curPath.FillMode = FillMode.Alternate; curG.FillPath(curBrush, curPath); } } */ float cos(float degree) { return (float)Math.Cos((degree/180.0)*Math.PI); } float sin(float degree) { return (float)Math.Sin((degree/180.0)*Math.PI); } void RedrawPreview() { if(resumeRedraw){return;} int iconSize = (int)nudSizeOfIcon.Value; bmpToBeSaved = CreateTransparentBitmap(iconSize, iconSize); curPath = new GraphicsPath(); curG = Graphics.FromImage(bmpToBeSaved); curG.SmoothingMode = SmoothingMode.AntiAlias; try { if (chkUseGradation.Checked) { curBrush = new LinearGradientBrush(new Point(0, 0), new Point(iconSize, iconSize), colorBackground1, colorBackground2); } else { curBrush = new SolidBrush(colorBackground1); } DrawFigure(); if (chkUseGradation.Checked) { curBrush = new LinearGradientBrush(new Point(0, 0), new Point(iconSize, iconSize), colorForeground1, colorForeground2); } else { curBrush = new SolidBrush(colorForeground1); } curFont = new Font(curFontSizeIndependent.Name, (float)nudFontSizeOfLine1.Value); newpath(); moveto(iconSize/2, iconSize/2 + (int)nudOffsetYLine1.Value); charpath(txtLine1.Text); closepath(); fill(); if ( txtLine2.Text != "" ) { curFont = new Font(curFontSizeIndependent.Name, (float)nudFontSizeOfLine2.Value); newpath(); moveto(iconSize/2, iconSize/2 + (int)nudOffsetYLine2.Value); charpath(txtLine2.Text); closepath(); fill(); } } finally { curG.Dispose(); curG = null; } int zoom = 1;// 2; Graphics g = Graphics.FromImage(pct.Image); g.FillRectangle(Brushes.White, 0, 0, bmpToBeSaved.Width*zoom, bmpToBeSaved.Height*zoom); // pct.Image.Width, pct.Image.Height); g.DrawImage(bmpToBeSaved, 0, 0, bmpToBeSaved.Width*zoom, bmpToBeSaved.Height*zoom); g.Dispose(); pct.Refresh(); } void DrawFigure() { int iconSize = (int)nudSizeOfIcon.Value; string figureType = cmbBgFigureType.Text; if ( figureType == "六角形" ) { newpath(); for ( int deg=0 ; deg<360 ; deg+=60 ) { float x = iconSize/2 + (iconSize/2 - 1)*cos(deg); float y = iconSize/2 + (iconSize/2 - 1)*sin(deg); if ( deg == 0 ) { moveto(x,y); } else { lineto(x,y); } } closepath(); fill(); } else if ( figureType == "丸" ) { newpath(); curPath.AddArc(0, 0, iconSize, iconSize, 0, 360); closepath(); fill(); } else if ( figureType == "四角" ) { newpath(); curPath.AddRectangle(new Rectangle(0, 0, iconSize, iconSize)); closepath(); fill(); } else if ( figureType == "角丸" ) { int cornerSize = iconSize/2; newpath(); curPath.AddArc(0, 0, cornerSize, cornerSize, 180, 90); curPath.AddArc(iconSize-cornerSize, 0, cornerSize, cornerSize, 270, 90); curPath.AddArc(iconSize-cornerSize, iconSize-cornerSize, cornerSize, cornerSize, 0, 90); curPath.AddArc(0, iconSize-cornerSize, cornerSize, cornerSize, 90, 90); closepath(); fill(); } } bool OpenTemplateWithDialog() { OpenFileDialog ofd = new OpenFileDialog(); ofd.FileName = "テンプレート.xml"; ofd.Filter = "XMLファイル(*.xml)|*.xml"; // ofd.Filter = "XMLファイル(*.xml)|*.xml|すべてのファイル(*.*)|*.*"; // ofd.FilterIndex = 1; // ofd.Title = "開くファイルを選択してください"; ofd.RestoreDirectory = true; // ofd.CheckFileExists = true; // ofd.CheckPathExists = true; if (ofd.ShowDialog() == DialogResult.OK) { return LoadSettingsFromXml(ofd.FileName); } else { return false; } } bool SaveTemplateWithDialog() { SaveFileDialog sfd = new SaveFileDialog(); sfd.FileName = "新しいIconMakerテンプレート.xml"; // sfd.InitialDirectory = @"C:\"; sfd.Filter = "XMLファイル(*.xml)|*.xml"; sfd.Title = "保存先のファイルを選択してください"; sfd.RestoreDirectory = true; // sfd.OverwritePrompt = true; // sfd.CheckPathExists = true; //ダイアログを表示する if (sfd.ShowDialog() == DialogResult.OK) { return SaveSettingsToXml(sfd.FileName); } else { return false; } } bool SaveImageWithDialog(string fmt) { SaveFileDialog sfd = new SaveFileDialog(); // sfd.InitialDirectory = @"C:\"; if ( fmt == "ico" ) { sfd.FileName = "新しいアイコン.ico"; sfd.Filter = "アイコンファイル(*.ico)|*.ico"; } else if ( fmt == "png" ) { sfd.FileName = "新しい画像.png"; sfd.Filter = "画像ファイル(*.png)|*.png"; } else { return false; } sfd.Title = "保存先のファイルを選択してください"; sfd.RestoreDirectory = true; // sfd.OverwritePrompt = true; // sfd.CheckPathExists = true; //ダイアログを表示する if (sfd.ShowDialog() == DialogResult.OK) { bool saveResult = false; if ( fmt == "ico" ) { saveResult = SaveAsIcon(sfd.FileName); if ( saveResult ) { RunClearCache(); } } else if ( fmt == "png" ) { saveResult = SaveAsPng(sfd.FileName); } return saveResult; } else { return false; } } bool SaveAsIcon(string destPath) { Icons icons; icons = new Icons(); icons.AddIcon(bmpToBeSaved); try { icons.SaveToFile(destPath); return true; } catch (IOException e) { MessageBox.Show(e.ToString()); } return false; } bool SaveAsPng(string destPath) { try { bmpToBeSaved.Save(destPath, System.Drawing.Imaging.ImageFormat.Png); return true; } catch (IOException e) { MessageBox.Show(e.ToString()); } return false; } void RunClearCache() { try { System.Diagnostics.Process.Start(ICON_CACHE_CLEAR_EXE_NAME, ICON_CACHE_CLEAR_EXE_OPTION); } catch (System.ComponentModel.Win32Exception e) { Console.WriteLine(e); } } bool SaveSettingsToXml(string destPath) { var t = new SettingsClass() { fg1Red = colorForeground1.R, fg1Green = colorForeground1.G, fg1Blue = colorForeground1.B, fg2Red = colorForeground2.R, fg2Green = colorForeground2.G, fg2Blue = colorForeground2.B, bg1Red = colorBackground1.R, bg1Green = colorBackground1.G, bg1Blue = colorBackground1.B, bg2Red = colorBackground2.R, bg2Green = colorBackground2.G, bg2Blue = colorBackground2.B, gradiation = chkUseGradation.Checked, fontName = curFontSizeIndependent.Name, fontSizeLine1 = nudFontSizeOfLine1.Value, fontSizeLine2 = nudFontSizeOfLine2.Value, yOffsetLine1 = (int)nudOffsetYLine1.Value, yOffsetLine2 = (int)nudOffsetYLine2.Value, textLine1 = txtLine1.Text, textLine2 = txtLine2.Text, iconSize = (int)nudSizeOfIcon.Value, bgFigureTypeName = cmbBgFigureType.Text, }; var serializer = new System.Xml.Serialization.XmlSerializer(typeof(SettingsClass)); try { var sw = new System.IO.StreamWriter(destPath, false, new System.Text.UTF8Encoding(false)); try { serializer.Serialize(sw, t); } finally { sw.Close(); } return true; } catch( IOException e ) { MessageBox.Show(e.ToString()); return false; } } bool LoadSettingsFromXml(string srcPath) { // https://www.atmarkit.co.jp/ait/articles/1704/19/news021.html var xmlSerializer = new System.Xml.Serialization.XmlSerializer(typeof(SettingsClass)); SettingsClass t; //var xmlSettings = new System.Xml.XmlReaderSettings(); try { using (var streamReader = new StreamReader(srcPath, System.Text.Encoding.UTF8)) using (var xmlReader = System.Xml.XmlReader.Create(streamReader)) //, xmlSettings)) { t = (SettingsClass)xmlSerializer.Deserialize(xmlReader); } } catch (IOException e) { MessageBox.Show(e.ToString()); return false; } catch (InvalidOperationException e) { MessageBox.Show(e.ToString()); return false; } resumeRedraw = true; try { colorForeground1 = Color.FromArgb(t.fg1Red, t.fg1Green, t.fg1Blue); colorForeground2 = Color.FromArgb(t.fg2Red, t.fg2Green, t.fg2Blue); colorBackground1 = Color.FromArgb(t.bg1Red, t.bg1Green, t.bg1Blue); colorBackground2 = Color.FromArgb(t.bg2Red, t.bg2Green, t.bg2Blue); SetFlatButtonColor(btnColorForeground1, colorForeground1); SetFlatButtonColor(btnColorForeground2, colorForeground2); SetFlatButtonColor(btnColorBackground1, colorBackground1); SetFlatButtonColor(btnColorBackground2, colorBackground2); chkUseGradation.Checked = t.gradiation; ReflectGradationCheckToControls(); txtLine1.Text = t.textLine1; txtLine2.Text = t.textLine2; nudFontSizeOfLine1.Value = t.fontSizeLine1; nudFontSizeOfLine2.Value = t.fontSizeLine2; nudOffsetYLine1.Value = t.yOffsetLine1; nudOffsetYLine2.Value = t.yOffsetLine2; cmbBgFigureType.Text = t.bgFigureTypeName; nudSizeOfIcon.Value = t.iconSize; curFontSizeIndependent = new Font(t.fontName, (float)DEFAULT_FONT_SIZE); txtFontName.Text = curFontSizeIndependent.Name; } finally { resumeRedraw = false; } RedrawPreview(); return true; } // 透過色で初期化 Bitmap CreateTransparentBitmap(int width, int height) { Bitmap bmp = new Bitmap(width, height, PixelFormat.Format32bppArgb); BitmapData bd = bmp.LockBits(new Rectangle(0,0,width,height), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb); try { unsafe { // 書き込み byte* ptr = (byte*)bd.Scan0; for ( int y=0 ; y<height ; y++ ) { for ( int x=0 ; x<width ; x++ ) { ptr[y*bd.Stride + 4*x ] = 0;// B ptr[y*bd.Stride + 4*x + 1] = 0;// G ptr[y*bd.Stride + 4*x + 2] = 0;// R ptr[y*bd.Stride + 4*x + 3] = 0;// alpha = 0 (透過) } } } } finally { bmp.UnlockBits(bd); } return bmp; } [STAThread] static void Main() { Application.Run(new IconMaker()); } }ソースコード - IconUtility.cs
IconUtility.csusing System; using System.Collections; using System.Collections.Generic; using System.Data; using System.Drawing; using System.Drawing.Drawing2D; using System.Drawing.Imaging; using System.IO; using System.Reflection; using System.Runtime.InteropServices; namespace IconUtility { public class Icons { class IconEntry { [StructLayout(LayoutKind.Sequential)] public struct IconDir { public short icoReserved; // must be 0 public short icoResourceType; //must be 1 for icon public short icoResourceCount; public IconDir(int n) { icoReserved = 0; icoResourceType = 1; icoResourceCount = (short)n; } } [StructLayout(LayoutKind.Sequential)] public struct IconDirEntry { byte _Width; byte _Height; public byte ColorCount; public byte reserved1; public short reserved2; public short reserved3; public int icoDIBSize; public int icoDIBOffset; public int Width{get{return (_Width>0)?_Width:256;}} public int Height{get{return (_Height>0)?_Height:256;}} public IconDirEntry(int w, int h) { if ( w<0 || w>256 || h<0 || h>256 ) { throw new Exception("Size parameter error"); } _Width = (byte)w; _Height = (byte)h; ColorCount=0; reserved1=0; reserved2=0; reserved3=0; icoDIBSize=0; icoDIBOffset=0; } } [StructLayout(LayoutKind.Sequential)] struct BitmapInfoHeader { public int biSize; // must be 40 public int biWidth; public int biHeight; public short biPlanes; // must be 1 public short biBitCount; // color public int biCompression; // 0:not compress public int biSizeImage; public int biXPixPerMeter; public int biYPixPerMeter; public int biClrUsed; public int biClrImportant; public BitmapInfoHeader(int w, int h) { biSize = 40; biWidth = w; biHeight = h*2; // 本体とmaskを含むため2倍とする決まりらしい biPlanes = 1; biBitCount = 32; biCompression=0; biSizeImage=0; biXPixPerMeter=0; biYPixPerMeter=0; biClrUsed=0; biClrImportant=0; } } IconDirEntry iconDirEntry; BitmapInfoHeader bitmapInfoHeader; byte[] bitmapBody; byte[] bitmapMask; public System.Drawing.Size Size{get{return new System.Drawing.Size(iconDirEntry.Width, iconDirEntry.Height);}} public int Width{get{return iconDirEntry.Width;}} public int Height{get{return iconDirEntry.Height;}} int BitPerPixel{get{return bitmapInfoHeader.biBitCount;}} int CalcDIBSize() { return Marshal.SizeOf(typeof(BitmapInfoHeader)) + bitmapBody.Length + bitmapMask.Length; } public int UpdateIconDirEntry(int icoDIBOffset) { iconDirEntry.icoDIBOffset = icoDIBOffset; iconDirEntry.icoDIBSize = CalcDIBSize(); return iconDirEntry.icoDIBSize; } public void WriteIconDirEntryTo(BinaryWriter writer) { Icons.CopyDataToByteArray<IconDirEntry>(writer, iconDirEntry); } public void WriteDataTo(BinaryWriter writer) { Icons.CopyDataToByteArray<BitmapInfoHeader>(writer, bitmapInfoHeader); writer.Write(bitmapBody); writer.Write(bitmapMask); } IconEntry(IconDirEntry _iconDirEntry, BitmapInfoHeader _bitmapInfoHeader, byte[] _bitmapBody, byte[] _bitmapMask) { iconDirEntry = _iconDirEntry; bitmapInfoHeader = _bitmapInfoHeader; bitmapBody = _bitmapBody; bitmapMask = _bitmapMask; } // 本体 public IconEntry(Bitmap bmp, Color? alphaColor) { int w = bmp.Width; int h = bmp.Height; if ( w>256 || h>256 ) { throw new Exception("size parameter error"); } iconDirEntry = new IconDirEntry(w, h); bitmapInfoHeader = new BitmapInfoHeader(w, h); bitmapBody = new byte[Icons.GetBitmapBodySize(w, h, bitmapInfoHeader.biBitCount)]; bitmapMask = new byte[Icons.GetBitmapMaskSize(w, h)]; Draw32bppBitmapToData(bmp, alphaColor); } void Draw32bppBitmapToData(Bitmap bmp, Color? alphaColor) { Array.Clear(bitmapMask, 0, bitmapMask.Length); Array.Clear(bitmapBody, 0, bitmapBody.Length); BitmapData bd = bmp.LockBits(new Rectangle(0,0,bmp.Width,bmp.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb); try { IntPtr ptr = bd.Scan0; byte[] pixels = new byte[bd.Stride * bmp.Height]; Marshal.Copy(ptr, pixels, 0, pixels.Length); int maskStride = (((Width+7)/8+3)/4)*4; int icoStride = Width*4; for (int y = 0; y < bd.Height; y++) { for (int x = 0; x < bd.Width; x++) { int posIco = y * icoStride + 4*x; int bytePosMask = y * maskStride + x/8; int bitPosMask = 7-(x%8); int pos = (bd.Height-1-y) * bd.Stride + x * 4; bitmapBody[posIco ] = pixels[pos]; //blue; bitmapBody[posIco+1] = pixels[pos+1]; //green; bitmapBody[posIco+2] = pixels[pos+2]; //red; bitmapBody[posIco+3] = pixels[pos+3]; //alpha if ( pixels[pos+3] == 0 || (alphaColor != null && pixels[pos] ==alphaColor.Value.B && pixels[pos+1]==alphaColor.Value.G && pixels[pos+2]==alphaColor.Value.R )) { //bitmapMask[bytePosMask] |= (byte)(1<<bitPosMask); // 32bit色のiconだとmaskではなくalpha channelが使用されるっぽい bitmapBody[posIco+3] = 0x00; } } } } finally { bmp.UnlockBits(bd); } } } int UpdateIconDirEntries() { iconDir.icoResourceCount = (short)iconEntries.Count; int offset = Marshal.SizeOf(typeof(IconEntry.IconDir)) + iconEntries.Count * Marshal.SizeOf(typeof(IconEntry.IconDirEntry)); for (int i=0;i<iconEntries.Count;i++) { offset += iconEntries[i].UpdateIconDirEntry(offset); } return offset; } static int GetBitmapBodySize(int w, int h, int bitCount) { return ((((w*bitCount + 7)/8)+3)/4)*4 * h; } static int GetBitmapMaskSize(int w, int h) { return ((((w+7)/8)+3)/4)*4 * h; } static TStruct CopyDataToStruct<TStruct> (BinaryReader reader) where TStruct : struct { var size = Marshal.SizeOf(typeof(TStruct)); var ptr = IntPtr.Zero; try { ptr = Marshal.AllocHGlobal(size); Marshal.Copy(reader.ReadBytes(size), 0, ptr, size); return (TStruct)Marshal.PtrToStructure(ptr, typeof(TStruct)); } finally { if (ptr != IntPtr.Zero) { Marshal.FreeHGlobal(ptr); } } } static void CopyDataToByteArray<TStruct>(BinaryWriter writer, TStruct s) where TStruct : struct { var size = Marshal.SizeOf(typeof(TStruct)); var buffer = new byte[size]; var ptr = IntPtr.Zero; try { ptr = Marshal.AllocHGlobal(size); Marshal.StructureToPtr(s, ptr, false); Marshal.Copy(ptr, buffer, 0, size); } finally { if (ptr != IntPtr.Zero) { Marshal.FreeHGlobal(ptr); } } writer.Write(buffer); } IconEntry.IconDir iconDir; List<IconEntry> iconEntries; // ------------------------------------------------------------------------------ // public members public int Count {get{return iconEntries.Count;}} public Icons() { iconDir = new IconEntry.IconDir(0); iconEntries = new List<IconEntry>(); } public void AddIcon(Bitmap bmp, Color alphaColor) { iconEntries.Add(new IconEntry(bmp, alphaColor)); UpdateIconDirEntries(); } public void AddIcon(Bitmap bmp) { iconEntries.Add(new IconEntry(bmp, null)); UpdateIconDirEntries(); } public bool SaveToFile(string path) { //int size = UpdateIconDirEntries(); using ( var fs = new FileStream(path, FileMode.Create) ) { using ( var writer = new BinaryWriter(fs) ) { CopyDataToByteArray<IconEntry.IconDir>(writer, iconDir); foreach(var t in iconEntries) { t.WriteIconDirEntryTo(writer); } foreach(var t in iconEntries) { t.WriteDataTo(writer); } } } return true; } } }
- 投稿日:2020-06-27T02:54:02+09:00
C#触ってて便利と思った機能について
はじめに
最近になってUnityで開発することが増え、C#に触ることが多くなったので「使ってこれは便利だな」と感じたものをここに備忘録がてらまとめておきます。(主はC++からC#を触ってるのでC++と比較しての視点で書いてます。)
Unity2019.4.0f1時点でのバージョンはC#7.3が標準だそうです。プロパティ構文
C++ではことあるごとにGetter/Setterを作らないといけなかったがC#ならこのように記述できる。
test.cspublic class BaseCompornent : MonoBehaviour { //座標 public Vector3 Position { set { transform.position = value; } get { return transform.position; } } public float PosX { set { Vector3 pos = Position; pos.x = value; Position = pos; } get { return Position.x; } }参照するときはこのようにメソッドを呼び出すことなくメンバ変数のように扱える。
test.cspublic void Function() { PosX += 100; var posX = posX; PosX++; }Unityでゲーム作ってるとどうしてもクラス間の値の受け渡しが多くなるため、この機能はとても助かる。
ちなみにget/setのどちらかは省略することが可能である。
また自動実装プロパティといって単純に「get; set;」記述することもできる。その際はプロパティ自体がデータを保有することになる。名前付き引数
良く調べたらこの機能C++ではないそうです。(C#は4.0から実装されたみたい)
簡単に言えば指定した引数だけを渡せれるってやつです。
デフォルト引数と兼ねたコード例です。test.cspublic void Function() { AddPosition(); AddPosition(15.0f, 25.0f); AddPosition(1.0f,20.0f, 30.0f); AddPosition(vy: 30.0f); //指定することが可能 } public void AddPosition(float vx = 0.0f, float vy = 0.0f, float vz = 0.0f) { PosX += vx; PosY += vy; PosZ += vz; }何気にこの機能最近知ったので、上手いこと使いこなしていきたいですね。
VBには昔からある機能だそうです。LINQ
最初、LINQとはなんぞやとなりましたが要はC#独自のSQLみたいなやつです。(正式名称は統合言語クエリと呼ぶみたい)
データベース関係に疎いので上手い使い方は出来ていないかもしれませんが、現在開発中のコードではこんな感じで使っています。LINQ_Test.csprivate void RemovePort(BaseNode node, Port port) { var targetEdge = edges.ToList().Where(x => x.output.portName == port.portName && x.output.node == port.node); if (targetEdge.Any()) { var edge = targetEdge.First(); edge.input.Disconnect(edge); RemoveElement(targetEdge.First()); } //省略 }やってることとしては「where」の部分でエッジリストから「エッジの出力ポート名と対象のポートのポート名が同じ かつ 対象ノードと出力先のノードが同じ」という条件のエッジを抽出しています。
そして「Any()」の行で抽出したエッジがあれば if 文内の処理をするようにしています。
指定した条件の要素を抽出したいときに結構使う便利だなと感じます。リフレクション
この機能もC++には現状ありません。(これぞとばかりにC++に恨みがあるんかって感じですけど)
何が出来るかというと、クラスの情報をデータとして扱えます。(メタデータ)Typeクラスを使って実装します。
コード例も開発のものから引っ張てきました。BaseNode.cspublic class BaseNode : Node { public string Guid; public Type type; public bool EntryPoint; public BaseNode() { Guid = System.Guid.NewGuid().ToString(); type = this.GetType(); title = type.Name.Replace("Node", ""); //クラス名から "Node" を削除したもの EntryPoint = false; }まだこれは単純ですが、BaseNodeのメンバにTypeクラスの変数を宣言し、コンストラクタの「GetType()」でBaseNodeクラスをタイプオブジェクトとして格納しています。また文字列としても取得することが出来ます。「type.Name」でクラス名を取得し、"Node"部分を削除してそのままNodeのタイトルとして流用しています。
こうすることでこのBaseNodeクラスを継承したクラスでも機能が働き、子クラスのクラス名を勝手に取得して反映させてくれます。
Typeクラスからインスタンスを生成をすることもできます。詳しくは参考記事をご覧ください。おわりに
Unityを触ってて便利だなと感じたC#の機能はこんな感じです。(あとの機能はC++にもある機能だったりので省いてます。)
まだ他にも「式木」とかあるんですけど、使いどころがあまりないと感じたので割愛。
もっとこういう使い方あるよとか便利な機能があるよっていうアドバイスがありましたら気軽にコメントしてくれるととても助かります。参考記事
C#とC++の比較
Unityで使える!C#の便利な機能7選
オプション引数・名前付き引数
C#のプロパティについて調べてみた
はじめてのLINQ
C#リフレクションTIPS 55連発
リフレクションを利用して文字列からクラス操作