- 投稿日:2022-04-02T23:13:05+09:00
BouncyCastle(.NET)を使ってみた
はじめに C#にて証明書を発行するプログラムを作ったのですが、 .NETに慣れているとBouncyCastleの設計は少々特殊に感じる部分もあるので備忘録として残します。 OpenSSLを利用する場合と、BouncyCastleを利用する場合の2パターンを紹介します。 今回はオレオレ証明書を作ってみます。 環境 .NET 6 BouncyCastle.NetCore (1.8.10) オレオレ証明書の作成手順 鍵を作成⇒証明書発行要求を作成⇒証明書作成 の流れでオレオレ証明書(自己著名証明書)を作成していきます。 秘密鍵の作成 OpenSSL openssl genrsa 2048 > ca.key BouncyCastle var keyGen = new RsaKeyPairGenerator(); var keyGenParam = new KeyGenerationParameters(new SecureRandom(), 2048); keyGen.Init(keyGenParam); var keyPair = keyGen.GenerateKeyPair(); using (var sw = new StreamWriter("ca.key")) { var writer = new PemWriter(sw); writer.WriteObject(keyPair.Private); } 備考 上記OpenSSLで作成した鍵はPKCS#1フォーマットとなりますが、 そのままではBouncyCastleはPKCS#1未対応のため読み込めません。 BouncyCastleで利用するには以下のコマンドによりPKCS#8に変換する必要があります。 openssl pkcs8 -in rs256.key -out rs256.key.pkcs8 -topk8 -nocrypt 証明書著名要求の作成 OpenSSL openssl req -new -key ca.key -subj "/CN=oreoreca" > ca.csr BouncyCastle // Subject var attributes = new Dictionary<DerObjectIdentifier, string>() { { X509Name.CN, "oreoreca" }, }; var attributeOrder = new [] { X509Name.CN, }; var subject = new X509Name(attributeOrder, attributes); // 拡張情報 var extGen = new X509ExtensionsGenerator(); extGen.AddExtension(X509Extensions.SubjectKeyIdentifier, false, new SubjectKeyIdentifierStructure(keyPair.Public)); extGen.AddExtension(X509Extensions.KeyUsage, false, new KeyUsage(KeyUsage.DigitalSignature)); var extensions = extGen.Generate(); var extAttr = new AttributeX509( PkcsObjectIdentifiers.Pkcs9AtExtensionRequest, new DerSet(extensions)); // CSR作成 var csr = new Pkcs10CertificationRequest( "SHA256withRSA", subject, keyPair.Public, new DerSet(extAttr), keyPair.Private); using (var sw = new StreamWriter("ca.csr")) { var writer = new PemWriter(sw); writer.WriteObject(csr); } オレオレ証明書の発行 OpenSSL openssl x509 -req -in ca.csr -signkey ca.key -days 365 -out ca.crt BouncyCastle var random = new SecureRandom(); // CSR読み込み using var csrStreamReader = new StreamReader("ca.csr"); var csrReader = new PemReader(csrStreamReader); var csrPemObj = csrReader.ReadPemObject(); var csr = new Pkcs10CertificationRequest(csrPemObj.Content); // CSR検証 if (!csr.Verify()) throw new Exception(); // 証明書著名用秘密鍵読み込み using var keyStreamReader = new StreamReader("ca.key"); var keyReader = new PemReader(keyStreamReader); var keyObj = keyReader.ReadObject() as AsymmetricCipherKeyPair; var privateKey = keyObj?.Private; if (privateKey == null) throw new Exception("Failed to read private key."); // Issure var attributes = new Dictionary<DerObjectIdentifier, string>() { { X509Name.CN, "oreoreca" }, }; var attributeOrder = new [] { X509Name.CN, }; var issure = new X509Name(attributeOrder, attributes); // 証明書作成 var certGen = new X509V3CertificateGenerator(); certGen.SetSerialNumber(new Org.BouncyCastle.Math.BigInteger(256, random)); certGen.SetIssuerDN(issure); certGen.SetSubjectDN(csr.GetCertificationRequestInfo().Subject); var today = DateTime.UtcNow.Date; certGen.SetNotBefore(today); certGen.SetNotAfter(today.AddYears(1)); certGen.SetPublicKey(csr.GetPublicKey()); certGen.AddExtension(X509Extensions.BasicConstraints, false, new BasicConstraints(true)); certGen.AddExtension(X509Extensions.SubjectKeyIdentifier, false, new SubjectKeyIdentifierStructure(csr.GetPublicKey())); var factory = new Asn1SignatureFactory( "SHA256withRSA", privateKey, random); var cert = certGen.Generate(factory); using (var sw = new StreamWriter("ca.crt")) { var writer = new PemWriter(sw); writer.WriteObject(cert); } さいごに このサンプルソースで作成した証明書は非常にミニマムな構成となっております。 実際に公開するような証明書を作る場合は、キー使用法の制限等しっかりと設定してやる必要があるかと思います。 次回、別のユースケースでのサンプルソースを載せていく予定です。
- 投稿日:2022-04-02T22:41:55+09:00
Unity 回転とスケールだけを用いたShear(せん断)の実装
Shearとは Shear(せん断)とはアフィン変換の一種で、下の様にある軸にそって引き伸ばす変換です。 しかし残念ながら、UnityにはShearを行う機能はありません。 ただ実は回転とスケールの合成だけでShear変換を作ることができるので、代わりにこれで実装します。 Shear変換行列の分解 実装の前に行列ではどのように表現されるのかみてみましょう。 Shearは次の変換行列で表される変換です。 \begin{pmatrix} 1 & x \\ 0 & 1 \\ \end{pmatrix} 唐突ですが、$x$を次のように表します。 x = \frac{1}{\tan \theta} すると、Shearは次のように回転行列と対角行列の積に分解できることが知られています。 (この導出は調べた限り分かりませんでした…ご存知の方は教えてください) \begin{pmatrix} 1 & \frac{1}{\tan\theta} \\ 0 & 1 \\ \end{pmatrix} = \begin{pmatrix} \frac{\sqrt2}{\sin\theta} & 0 \\ 0 & \sqrt2 \end{pmatrix} \begin{pmatrix} \cos -45 ^\circ & -\sin -45 ^\circ \\ \sin -45 ^\circ & \cos -45 ^\circ \end{pmatrix} \begin{pmatrix} \sin\frac{\theta}{2} & 0 \\ 0 & \cos\frac{\theta}{2} \end{pmatrix} \begin{pmatrix} \cos\frac{\theta}{2} & -\sin\frac{\theta}{2} \\ \sin\frac{\theta}{2} & \cos\frac{\theta}{2} \end{pmatrix} これはつまり、単純な回転とスケールだけでShearを行えることを意味しています。 また、 \phi = 90 ^\circ - \theta とおくことで \begin{pmatrix} 1 & \tan\phi \\ 0 & 1 \\ \end{pmatrix} = \begin{pmatrix} \frac{\sqrt2}{\sin90^\circ - \phi} & 0 \\ 0 & \sqrt2 \end{pmatrix} \begin{pmatrix} \cos -45 ^\circ & -\sin -45 ^\circ \\ \sin -45 ^\circ & \cos -45 ^\circ \end{pmatrix} \begin{pmatrix} \sin\frac{90^\circ - \phi}{2} & 0 \\ 0 & \cos\frac{90^\circ - \phi}{2} \end{pmatrix} \begin{pmatrix} \cos\frac{90^\circ - \phi}{2} & -\sin\frac{90^\circ - \phi}{2} \\ \sin\frac{90^\circ - \phi}{2} & \cos\frac{90^\circ - \phi}{2} \end{pmatrix} \tag{1} と表すこともできます。 Unityでの実装 (1)の変換をコードに落とし込んでいきましょう。 次のようなコンポーネントを作ります。 using UnityEngine; [ExecuteAlways] public class Shear : MonoBehaviour { [Range(-90, 90)] public float phi = 30f; private void OnValidate() { // z軸回りにShearさせる float angle = 90 - phi; // 一つのTransformでは、Scale→Rotationの順に適用されることに注意 transform.localRotation = Quaternion.Euler(0, 0, angle / 2); transform.parent.localScale = new Vector3(Mathf.Sin(angle * Mathf.Deg2Rad / 2), Mathf.Cos(angle * Mathf.Deg2Rad / 2), 1); transform.parent.localRotation = Quaternion.Euler(0, 0, -45); transform.parent.parent.localScale = new Vector3(Mathf.Sqrt(2) / Mathf.Sin(angle * Mathf.Deg2Rad), Mathf.Sqrt(2), 1); } } これをShear対象のオブジェクトにアタッチし、親として2つEmptyオブジェクトを設定します。 その状態でスライダーを動かすとShearすることがわかります。 参考 https://www.cs.cmu.edu/~fp/courses/02-graphics/asst2/solution/asst2-sol.pdf https://answers.unity.com/questions/961330/shear-transformation-using-gameobject-transformati.html https://ja.wikipedia.org/wiki/%E4%B8%89%E8%A7%92%E9%96%A2%E6%95%B0
- 投稿日:2022-04-02T22:32:58+09:00
キーボーddddddddddッドを壊してみた
ddddddddddっ導入 「キーボーddddddddddッドが壊れた」とは、2016年07月15日に、「2ちゃんねる」という日本再ddddddddddっ大級のddddddddddっ電子掲示板サイトに投稿されたスレddddddddddddddddddddddッドの一つddddddddddっです。 そのスレddddddddddddddddddddddッドについては下のddddddddddっ動画ddddddddddっで詳しく説明されていますのddddddddddっで、ご存じない方はぜひ一ddddddddddっ度ご覧くddddddddddっださい。 完成品 (流石に飽きてきたと思うので元に戻します。) Readme.txtの書き方についてはこちらのサイトを参考にさせていただきました。 ダウンロード こちらからダウンロード ※注意: クリックするとすぐにダウンロードが始まります 使い方 「キーボーddddddddddッドが壊れた.exe」をダブルクリックして実行するだけです。 Windows Defenderに止められるかもしれませんが、悪いことはしていないので大丈夫です。 タスクトレイに常駐するので、不要になったときは右クリックから終了できます。 使用中の様子 なかなかに酷いです。スレ主さんの状況を再現できたのではないでしょうか。 ソースコード このプログラムは実行中、キー入力を監視します。Dキーのキーダウンイベントが発生したらそのイベントをキャンセルして、代わりにDキーを12回キーダウンさせます。12回という定数はスレタイから特定しました。 こちら記事を参考に(ほぼコピペ)させていただきました。 // ---------- キー入力をするところのみ抜粋 ---------- // bool preve = false; // 無限ループ防止用変数 int HookCallback(int nCode, IntPtr wParam, IntPtr lParam) { switch ((int)wParam) { case 256: // キーダウンイベントの場合 short keyCode = (short)Marshal.ReadInt32(lParam); // キーコードを取得 if (keyCode == 68 & !preve) // Dキー(キーコード68)なら { preve = true; // 無限ループ防止 for (int i = 0; i < 12; i++) // 12回繰り返す { Thread.Sleep(1); // 1ms待つ(リアル感を出すため) keyInput.Input(keyCode); // Dキーをダウン } preve = false; return 1; // キーイベントをキャンセル } else { break; } } return 0; // その他のキーなら何もせずスルー } GitHubに公開しました。不要なコードなどがありましたら、どなたかアドバイスいただけると幸いです。 あとがき スレ主さんは、悪意(笑)のある誰かが作ったこのようなクソアプリを、知らず知らずのうちにダウンロードしてしまっていたのかもしれませんね。 参考にさせていただいたサイト様 ・タスクトレイに常駐について ・キーフックについて
- 投稿日:2022-04-02T21:20:06+09:00
C# AngleSharp ログイン後の画面遷移を行う
発生した問題 AngleSharpでスクレイピングをしてみたのだが、どうもログイン後の画面遷移がうまくいっていないっぽい。 ログインしてトップページまで表示させることはできたのだが、別ページに遷移しようとすると以下のエラーメッセージが表示されてしまう。 ※ちなみにこれは某証券サイトのエラーメッセージ ログイン画面から再度入力して下さい。(エラーコード) 何らかの原因によりXXXXXから自動的にログアウトいたしました。 ご利用を継続される場合は、再度ログインをお願いいたします。 ・最後にXXXXXを操作してから、30分以上経過した ・ブックマークからログイン後のページにアクセスした ・インターネットから一旦切断された後、再度アクセスされた等 問題のコード using System.Diagnostics; using AngleSharp.Dom; using AngleSharp; using AngleSharp.Html.Dom; namespace SampleFormApp { public class ScrapingMain { const string HOST_URL = "https://xxxxx.co.jp"; public async Task<System.Collections.Generic.IEnumerable<string>> Exec() { var config = Configuration.Default.WithDefaultLoader().WithJs(); var context = BrowsingContext.New(config); await context.OpenAsync($"{HOST_URL}/xxxxx/"); var topPage = await context.Active.QuerySelector<IHtmlFormElement>("form[name='form_TopPage']").SubmitAsync(new { koza1 = "[支店コード]", koza2 = "[口座番号]", passwd = "[パスワード]", }); var topPageHeaderMenu = context.Active.QuerySelectorAll("table"); var transactionPageUrl = topPageHeaderMenu[3].Children[0].Children[0].Children[2].Children[0].Attributes["href"].Value; // この遷移がうまくいっていないっぽい await context.OpenAsync($"{HOST_URL}{transactionPageUrl}"); // ここはとりあえず適当・・・。 return (IEnumerable<string>)topPageHeaderMenu; } } } 解決方法 色々調べたところ、デフォルトではCookieを保持するような設定になっていないみたい。 そりゃセッション維持できないよね・・・。 ということで WithDefaultCookies() を追加したら、問題なく画面遷移できた。 using System.Diagnostics; using AngleSharp.Dom; using AngleSharp; using AngleSharp.Html.Dom; namespace SampleFormApp { public class ScrapingMain { const string HOST_URL = "https://xxxxx.co.jp"; public async Task<System.Collections.Generic.IEnumerable<string>> Exec() { var config = Configuration.Default.WithDefaultLoader().WithJs().WithDefaultCookies(); // WithDefaultCookies()を追加 var context = BrowsingContext.New(config); await context.OpenAsync($"{HOST_URL}/xxxxx/"); var topPage = await context.Active.QuerySelector<IHtmlFormElement>("form[name='form_TopPage']").SubmitAsync(new { koza1 = "[支店コード]", koza2 = "[口座番号]", passwd = "[パスワード]", }); var topPageHeaderMenu = context.Active.QuerySelectorAll("table"); var transactionPageUrl = topPageHeaderMenu[3].Children[0].Children[0].Children[2].Children[0].Attributes["href"].Value; await context.OpenAsync($"{HOST_URL}{transactionPageUrl}"); return (IEnumerable<string>)topPageHeaderMenu; } } } バージョン情報 Visual Studio 2022 Version 17.1.3 .NET 6.0 AngleSharp 0.16.1 AngleSharp.Js 0.15.0
- 投稿日:2022-04-02T14:37:27+09:00
C#のフォームアプリでライフゲーム作成
Youtubeで解説したC#で作成したライフゲームのソースコード。 下記URLで長々と解説していますが、結局はこのコードを最終的に書きました。 遊んでくれると嬉しいかも。 1回目:ライフゲーム作成するための考え方とか試行錯誤のお話だったはず。 2回目:クラス設計~完成までと、最後は小話。 LifeGame.cs using System; using System.Text; using System.Drawing; using System.Windows.Forms; //クラスはFormを継承して作成 class LifeGame : Form { private Button startBtn, resetBtn, randomBtn; private static Timer timer = new Timer(); //ここで作成したMapクラスのオブジェクトを作成(タイマー操作の記述中でも使用したいため) private Map mp = new Map(40, 40); private Label lb; private int dotSize=20; public static void Main() { Application.Run(new LifeGame()); } public LifeGame() { this.Text = "LifeGame"; //ウィンドウサイズの指定(px) this.Width = 1200; this.Height = 1000; //タイマー発動間隔の指定(下記では100ms毎にタイマー発動) timer.Interval = 100; timer.Tick += new EventHandler(timer_Tick); lb = new Label(); //テキストでグラフィック表現するため、フォントやフォントサイズをあらかじめ定めておく //Font(フォント名, フォントサイズ, フォントサイズをピクセル単位で指定) lb.Font = new Font("MS UI Gothic", dotSize, GraphicsUnit.Pixel); lb.Location = new Point(0,0); lb.Size = new Size(this.Width, this.Height - 100); lb.Text = mp.textMap(); lb.Parent = this; lb.MouseClick += Form_MouseClick; //Startボタン startBtn = new Button(); startBtn.Text = "▶"; startBtn.Location = new Point(0,this.Height - 100); this.Controls.Add(startBtn); startBtn.Click += new EventHandler(startBtn_Click); //Resetボタン resetBtn = new Button(); resetBtn.Text = "Reset"; resetBtn.Location = new Point(startBtn.Location.X+startBtn.Width+10,this.Height - 100); this.Controls.Add(resetBtn); resetBtn.Click += new EventHandler(resetBtn_Click); //ランダム入力ボタン randomBtn = new Button(); randomBtn.Text = "Random"; randomBtn.Location = new Point(resetBtn.Location.X+resetBtn.Width+10,this.Height - 100); this.Controls.Add(randomBtn); randomBtn.Click += new EventHandler(randomBtn_Click); } private void Form_MouseClick(Object sender, MouseEventArgs e) { int pointX, pointY; if(e.Button==MouseButtons.Left) { pointX = (int)(e.X - 0.2*dotSize) / dotSize; pointY = (int)(e.Y - 0.025*dotSize) / dotSize; mp.dotReverse(pointX, pointY); lb.Text = mp.textMap(); lb.Parent = this; } } private void startBtn_Click(Object sender, EventArgs e) { if(startBtn.Text == "▶") { startBtn.Text = "■"; timer.Enabled = true; } else { startBtn.Text = "▶"; timer.Enabled = false; } } private void resetBtn_Click(Object sender, EventArgs e) { mp.Reset(); lb.Text = mp.textMap(); lb.Parent = this; } private void randomBtn_Click(Object sender, EventArgs e) { mp.randomInput(); lb.Text = mp.textMap(); lb.Parent = this; } private void timer_Tick(Object sender, EventArgs e) { nextStep(); lb.Text = mp.textMap(); lb.Parent = this; } //ライフゲームのルールから1世代後のマップ情報計算 private void nextStep() { int[,] nowStatus = new int[mp.Width,mp.Height]; int[,] nextStatus = new int[mp.Width,mp.Height]; int cellSum; //現在情報取得 for(int j=0; j<mp.Height; j++) { for(int i=0; i<mp.Width; i++) { nowStatus[i,j] = mp.dotStatus(i, j); } } //ライフゲームのルールから次世代生死ドットの計算 for(int j=0; j<mp.Height; j++) { for(int i=0; i<mp.Width; i++) { cellSum = 0; //マップの外周部は無視 if(i>0 && j>0 && i <mp.Width-1 && j <mp.Height-1) { for(int aj=j-1; aj<j+2; aj++) for(int ai=i-1; ai<i+2; ai++) cellSum += nowStatus[ai, aj]; cellSum -= nowStatus[i, j]; } if(nowStatus[i,j]==0 && cellSum==3) nextStatus[i, j] = 1; else if(nowStatus[i,j]==1) { if(cellSum==2 || cellSum==3) nextStatus[i, j] = 1; if(cellSum <2 || 3< cellSum) nextStatus[i, j] = 0; } } } //現在マップ情報と次世代マップ情報で差分あればドット反転 for(int j=0; j<mp.Height; j++) { for(int i=0; i<mp.Width; i++) { if((nowStatus[i,j]-nextStatus[i,j])!=0) mp.dotReverse(i, j); } } } } class Map { private string strT="■", strF="□", strN="\n"; public string text; public string mapString; public StringBuilder sbmap; public int Width, Height; public int maxLength; //オブジェクト生成時に縦横のドット数からテキストマップ情報をstringbuilderに格納する public Map(int m, int n) { Width = m; Height = n; maxLength = m * n - 1; text = ""; for(int i=0; i<n; i++) { for(int j=0; j<m; j++) { text += strF; } text += strN; } mapString = text; sbmap = new StringBuilder(mapString); } //指定したドット位置(x方向, y方向)を反転させるメソッド public void dotReverse(int m, int n) { //エラー処理 if(m>Width-1 || n>Height-1) return; if(0>m || 0>n) return; int changePoint = (Width+1) * n + m; if(sbmap.ToString()[changePoint] == strF[0]) { sbmap.Insert(changePoint, strT, 1); } else { sbmap.Insert(changePoint, strF, 1); } sbmap.Remove(changePoint+1, 1); } //指定されたドット情報(■か□)を判定し1or0で返す public int dotStatus(int m, int n) { //エラー処理 if(m>Width-1 || n>Height-1) return 0; if(0>m || 0>n) return 0; int getPoint = (Width+1) * n + m; if(sbmap.ToString()[getPoint] == strT[0]) return 1; else return 0; } //テキストのマップ情報をリセット public void Reset() { text = ""; for(int i=0; i<Height; i++) { for(int j=0; j<Width; j++) { text += strF; } text += strN; } mapString = text; sbmap = new StringBuilder(mapString); } //マップの情報をランダムに入力するメソッド public void randomInput() { Random r = new Random(); int randomN; text = ""; for(int i=0; i<Height; i++) { for(int j=0; j<Width; j++) { randomN = (int)r.Next(0,2); if(randomN == 0) text += strF; else text += strT; } text += strN; } mapString = text; sbmap = new StringBuilder(mapString); } //操作は全てstringbuilderで行ったため、string型を返すメソッドも用意しておく //他のメソッドで全て返り値stringを返すようにしてしまってもいいかもしれない。。。 public string textMap() { return sbmap.ToString(); } }
- 投稿日:2022-04-02T11:56:52+09:00
cscの作法 その90
概要 cscの作法、調べてみた。 ContextMenu使ってみた。 参考にしたページ 写真 以上。
- 投稿日:2022-04-02T11:24:44+09:00
cscの作法 その89
概要 cscの作法、調べてみた。 Dictionary使ってみた。 参考にしたページ 写真 サンプルコード using System; using System.Drawing; using System.Windows.Forms; using System.Collections.Generic; public class Form1 : Form { Panel panel1; Panel panel2; Panel panel3; Button button1, button2, button3; Dictionary<Button, Panel> _dic = new Dictionary<Button, Panel>(); public Form1() { this.ClientSize = new Size(500, 500); this.Text = "panel"; panel1 = new Panel(); panel1.Location = new Point(10, 50); panel1.Width = 400; panel1.Height = 400; panel1.BackColor = Color.Gray; panel2 = new Panel(); panel2.Location = new Point(10, 50); panel2.Width = 400; panel2.Height = 400; panel2.BackColor = Color.Red; panel3 = new Panel(); panel3.Location = new Point(10, 50); panel3.Width = 400; panel3.Height = 400; panel3.BackColor = Color.Blue; button1 = new Button(); button1.Location = new Point(10, 10); button1.Size = new Size(100, 30); button1.Text = "1"; button2 = new Button(); button2.Location = new Point(160, 10); button2.Size = new Size(100, 30); button2.Text = "2"; button3 = new Button(); button3.Location = new Point(310, 10); button3.Size = new Size(100, 30); button3.Text = "3"; this.Controls.Add(panel1); this.Controls.Add(panel2); this.Controls.Add(panel3); this.Controls.Add(button1); this.Controls.Add(button2); this.Controls.Add(button3); _dic.Add(this.button1, this.panel1); _dic.Add(this.button2, this.panel2); _dic.Add(this.button3, this.panel3); AddEvent(); SelectPanel(this.button3); } private void AddEvent() { foreach (KeyValuePair<Button, Panel> pair in _dic) { Button button = pair.Key; button.Click += new System.EventHandler(this.Button_Click); } } private void Button_Click(object sender, EventArgs e) { Button button = (Button) sender; SelectPanel(button); } private void SelectPanel(Button selectedButton) { foreach (KeyValuePair<Button, Panel> pair in _dic) { Button button = pair.Key; Panel panel = pair.Value; if (button.Equals(selectedButton)) { button.BackColor = Color.DarkBlue; button.ForeColor = Color.White; panel.Visible = true; } else { button.BackColor = Color.Gray; button.ForeColor = Color.Black; panel.Visible = false; } } } [STAThread] public static void Main() { Application.Run(new Form1()); } } 以上。
- 投稿日:2022-04-02T11:10:26+09:00
C#を使用してExcelに透かしを追加する方法
Microsoft Excelには、Excelテーブルに透かしを直接追加する組み込み関数がないことはわかっていますが、実際には、ヘッダー画像やワードアートを追加して透かしの外観を模倣するなど、他の回避策を使用してこの問題を解決できます。 したがって、この投稿では、Excelにヘッダー画像を作成して挿入することにより、Excelに透かしを追加する方法を紹介します。 ここでは、Excel用の無料コンポーネントであるFree Spire.XLSをダウンロードする必要があります。これにより、時間を節約し、コードを簡素化できます。コンポーネントがインストールされたら、プロジェクトを作成し、インストールディレクトリにプロジェクトへの参照としてdllファイルを追加し、次の名前空間を追加します。 using System; using System.Drawing; using System.Windows.Forms; using Spire.Xls; 元のExcelシートのスクリーンショットは次のとおりです。 詳細な手順とサンプルコード ステップ1:最初にDrawText()メソッドを定義し、文字列の内容に基づいて画像を作成します。文字列は、「機密」、「ドラフト」、「サンプル」、または透かしとして表示する任意のテキストにすることができます。 private static System.Drawing.Image DrawText(String text, System.Drawing.Font font, Color textColor, Color backColor, double height, double width) { //指定された幅と高さのビットマップ画像を作成する Image img = new Bitmap((int)width, (int)height); Graphics drawing = Graphics.FromImage(img); //テキストサイズを取得する SizeF textSize = drawing.MeasureString(text, font); //画像を回転する drawing.TranslateTransform(((int)width - textSize.Width) / 2, ((int)height - textSize.Height) / 2); drawing.RotateTransform(-45); drawing.TranslateTransform(-((int)width - textSize.Width) / 2, -((int)height - textSize.Height) / 2); //背景を描く drawing.Clear(backColor); //テキストブラシを作成する Brush textBrush = new SolidBrush(textColor); drawing.DrawString(text, font, textBrush, ((int)width - textSize.Width) / 2, ((int)height - textSize.Height) / 2); drawing.Save(); return img; } ステップ2:新しいブックを初期化し、透かしを入れたファイルをロードします。 Workbook workbook = new Workbook(); workbook.LoadFromFile(@"C:\Users\Administrator\Desktop\sample.xlsx"); ステップ3:DrawText()メソッドを呼び出して新しい画像を作成し、ヘッダー画像を左揃えに設定します。次に、ヘッダー画像は表示モードがレイアウトの場合にのみ表示されるため、表示モードをレイアウトに変更することを忘れないでください。 Font font = new System.Drawing.Font("arial", 40); String watermark = "内部用資料"; foreach (Worksheet sheet in workbook.Worksheets) { //DrawText()メソッドを呼び出して、新しい画像を作成する System.Drawing.Image imgWtrmrk = DrawText(watermark, font, System.Drawing.Color.LightCoral, System.Drawing.Color.White, sheet.PageSetup.PageHeight, sheet.PageSetup.PageWidth); //ヘッダー画像を左揃えにする sheet.PageSetup.LeftHeaderImage = imgWtrmrk; sheet.PageSetup.LeftHeader = "&G"; //透かしはこのモードでのみ表示されます sheet.ViewMode = ViewMode.Layout; } ステップ4:ファイルを保存して開きます。 workbook.SaveToFile("透かし.xlsx", ExcelVersion.Version2010); System.Diagnostics.Process.Start("透かし.xlsx"); 追加した効果は以下のように: 完全なるコード using System; using System.Drawing; using System.Windows.Forms; using Spire.Xls; namespace Add_Watermark_To_Excel { public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void button1_Click(object sender, EventArgs e) { //新しいブックを初期化し、透かしを入れるファイルをロードする Workbook workbook = new Workbook(); workbook.LoadFromFile(@"C:\Users\Administrator\Desktop\sample.xlsx"); //ヘッダーに画像を挿入する Font font = new System.Drawing.Font("arial", 40); String watermark = "内部用資料"; foreach (Worksheet sheet in workbook.Worksheets) { //DrawText()メソッドを呼び出して、新しい画像を作成する System.Drawing.Image imgWtrmrk = DrawText(watermark, font, System.Drawing.Color.LightCoral, System.Drawing.Color.White, sheet.PageSetup.PageHeight, sheet.PageSetup.PageWidth); //ヘッダー画像を左揃えにする sheet.PageSetup.LeftHeaderImage = imgWtrmrk; sheet.PageSetup.LeftHeader = "&G"; //透かしはこのモードでのみ表示される sheet.ViewMode = ViewMode.Layout; } workbook.SaveToFile("透かし.xlsx", ExcelVersion.Version2010); System.Diagnostics.Process.Start("透かし.xlsx"); } private static System.Drawing.Image DrawText(String text, System.Drawing.Font font, Color textColor, Color backColor, double height, double width) { //指定された幅と高さのビットマップ画像を作成する Image img = new Bitmap((int)width, (int)height); Graphics drawing = Graphics.FromImage(img); //テキストサイズを取得する SizeF textSize = drawing.MeasureString(text, font); //画像を回転する drawing.TranslateTransform(((int)width - textSize.Width) / 2, ((int)height - textSize.Height) / 2); drawing.RotateTransform(-45); drawing.TranslateTransform(-((int)width - textSize.Width) / 2, -((int)height - textSize.Height) / 2); //背景を描く drawing.Clear(backColor); //テキストブラシを作成する Brush textBrush = new SolidBrush(textColor); drawing.DrawString(text, font, textBrush, ((int)width - textSize.Width) / 2, ((int)height - textSize.Height) / 2); drawing.Save(); return img; } } } 今回のExcelに透かしを追加する方法は以上でした、最後まで読んでいただきありがとうございました。
- 投稿日:2022-04-02T09:03:33+09:00
cscの作法 その88
概要 cscの作法、調べてみた。 xml使ってみた。 参考にしたページ test.xml <MyDataSet> <PersonalInfoTable ID="1"> <Name>taro yamada</Name> <Height>180</Height> </PersonalInfoTable> </MyDataSet> 写真 以上。
- 投稿日:2022-04-02T08:57:21+09:00
cscの作法 その87
概要 cscの作法、調べてみた。 menu使ってみた。 参考にしたページ 写真 以上。
- 投稿日:2022-04-02T08:53:08+09:00
cscの作法 その86
概要 cscの作法、調べてみた。 tabcontrol使ってみた。 参考にしたページ 写真 以上。
- 投稿日:2022-04-02T02:40:40+09:00
RLlibで強化学習したモデルをC#で使う
目的 Ray RLlibで強化学習したモデルをC#で動かせるようにします。 背景 AI・機械学習は python でやるのがライブラリ等、環境が整備されていて一番楽だと思います。 一方でゲーム開発はUnityをよく使うと思いますが、こちらはC#でスクリプトを書きます。 自作ゲームにつよつよAIを導入したくないですか?したいですよね。 僕がまさにその状況にあるので、pythonで学習したモデルをC#に移植する方法を調べてみました。 注意 ここでは僕の用途の関係上、RLlibという強化学習ライブラリで学習させたAIを、C#で使う方法について書きます。 学習済みモデルをC#で使うだけならMicrosoftのこの記事を読んだ方が早いです。 本記事もこのMicrosoftの記事をめちゃくちゃ参考にして書いています。 具体的な目標 PPOエージェントを用いてSimpleCorriderという環境に対して強化学習を実施して、出来上がった学習済みモデルをC#のML.Netライブラリを使って動かします。 作業の概要 pythonでやること: RLlibで強化学習 → onnx形式でエクスポート C#でやること: onnx形式のモデルをインポート → 学習した通りの結果をPredictできることの確認 【Python】RLlibで強化学習 RLlibのGetting Started Guideにある例で強化学習モデルを作成します。 必要なパッケージ 以下のパッケージを使います。 Ray: 強化学習 Tensorflow: ニューラルネットワーク作成・学習 tf2onnx: onnx形式への変換 pip install "ray[rllib]" tensorflow tf2onnx SimpleCorrider環境 これから強化学習するSimpleCorrider環境を説明します。 SimpleCorriderは動作確認用の環境で、面白いものではないです。 直線状にスタート地点とゴール地点があり、スタート地点から開始してプラスかマイナスのどちらかに進むという2つのアクションをとります。 行動のたびにペナルティとしてマイナスの報酬が課され、ゴール地点までたどり着ければプラスの報酬がもらえます。 オブザベーション[int]: 現在の位置 アクション[int]: 0 or 1 (0のとき-1, 1のとき+1進む) 報酬[float]: ゴールしたら+1.0, それ以外は -0.1 うまく学習できれば、ポリシーは1を連打するわけです。 学習したモデルのエクスポート Ray Getting Started GuideのSimpleCorriderを強化学習している使用例のコードをコピペしてください。Ray ML Quick StartのRLlibのドロップダウンリストになっている箇所です。 このコードはPPOTrainerで学習し、最後に学習済みモデルを使って1エピソードを走らせて累積報酬を求めるという内容になっています。この学習の後(trainer.train()のあるfor文を抜けた後)に以下のように追記します。 追記する outdir = "exported_onnx" # 適当なディレクトリ trainer.export_policy_model(outdir, onnx=11) 追記したスクリプトを実行すれば、 outdirディレクトリにsaved_model.onnxというファイル名で学習済みモデルがonnx形式でエクスポートされます。 ただし次のセクションの内容もスクリプトに追加してからの実行をおすすめします。 対応するグラフの変数名を調べる この後C#にモデルをインポートするわけですが、ここでいうモデルというのは、ニューラルネットワークのグラフのことです。 グラフにC#から入出力するには、グラフ上の変数名を指定してやる必要があります。 なので変数名を調べておきましょう。 policy = trainer.get_policy() print(policy._obs_input) # >> Tensor("default_policy/obs:0", shape=(?, 1), dtype=float32) print(policy._sampled_action) # >> Tensor("default_policy/cond_1/Merge:0", shape=(?,), dtype=int64) PPOエージェントの場合はpolicy._obs_inputにあるdefault_policy/obs:0がオブザベーションに対応するグラフの入力名、policy._sampled_actionにあるdefault_policy/cond_1/Merge:0がアクションに対応するグラフの出力名になります。 注意 今回はアクションが離散値なので、default_policy/cond_1/Merge:0が直接アクションに対応しますが、例えば境界のある連続値(gym envでいうBox)であればスケーリング処理はグラフの外側で行われる仕様のため、C#上でグラフの出力を期待したアクションの形式になるように変換する必要があります。 【C#】学習済みonnxモデルの実行 先ほどエクスポートしたsaved_model.onnxをC#で動かします。 必要なパッケージ 以下のパッケージを使います。 Microsoft.ML Microsoft.ML.OnnxRuntime Microsoft.ML.OnnxTransformer 学習モデルのインポート ML.Netでモデルを予測させるのは結構面倒です。 Load() → Predict()くらいシンプルにならんもんかね。。。 入出力の定義 まずグラフの入力と出力を定義します Program.cs public class OnnxInput { [ColumnName("default_policy/obs:0")] public float CurPos { get; set; } } public class OnnxOutput { [ColumnName("default_policy/cond_1/Merge:0"), OnnxMapType(typeof(Int64), typeof(Single))] public Int64[] Action { get; set; } } Attributeにグラフとの対応を指定します。変数名や型については「対応するグラフの変数名を調べる」のセクションを参照ください。 float型は型変換の必要はないですが、それ以外の場合はOnnxMapTypeで変換しないとエラーになります。 インポート Predict()メソッドを持っているのはPredictEngineというオブジェクトです。 このオブジェクトを作るまでが長いです。おまじないだと思って我慢しましょう。 以下のようにします。 Program.csのProgramクラス static PredictionEngine<OnnxInput, OnnxOutput> CreatePredictEngine() { string modelPath = "..../saved_model.onnx"; // Python編でエクスポートしたonnxファイルを指定 var mlContext = new MLContext(); var tensorFlowModel = mlContext.Transforms.ApplyOnnxModel(modelPath); var emptyDv = mlContext.Data.LoadFromEnumerable(new OnnxInput[] { }); var trnsf = tensorFlowModel.Fit(emptyDv); var engine = mlContext.Model.CreatePredictionEngine<OnnxInput, OnnxOutput>(trnsf); return engine; } 愚痴 var trnsf = tensorFlowModel.Fit(emptyDv); この行やばすぎる。 trnsfはOnnxTranformerという名前のクラスのインスタンスなんだけど、NNでTransformerといったらAll you needしか思いつかん。それにFit()はいまにも学習しそうな名前。ややこしいのやめて>< Predict ここまで来れば予測は簡単です。 さっき作成した入出力クラスで型付けされるのでpythonよりも整然でエレガント。 var predictEngine = CreatePredictEngine() var onnxInput = new OnnxInput { CurPos = obs }; var onnxOutput = predictEngine.Predict(onnxInput); 動作確認 ちゃんとonnxモデルを読み込めていて、動作するかを確認します。 せっかくなのでSimpleCorrider EnvのC#版を作ってみます。 SimpleCorriderクラス(C#版) Program.cs public class SimpleCorridor { public int EndPos { get; set; } public int CurPos { get; set; } public SimpleCorridor(int corridorLength) { this.EndPos = corridorLength; this.CurPos = 0; } public int Reset() { this.CurPos = 0; return this.CurPos; } public (int, double, bool) Step(int action) { this.CurPos += ((action == 0) && (this.CurPos > 0)) ? -1 : 1; var done = (this.CurPos >= this.EndPos); var reward = done ? 1.0 : -0.1; return (this.CurPos, reward, done); } } 動作確認 インポートしたモデルで1エピソード実行してみます。 Program.cs static void Main(string[] args) { var env = new SimpleCorridor(10); var predictEngine = CreatePredictEngine(); var obs = env.Reset(); var done = false; double reward; var totalReward = 0.0; while (!done) { var onnxInput = new OnnxInput { CurPos = obs }; var onnxOutput = predictEngine.Predict(onnxInput); // 予測 var action = (int)onnxOutput.Action[0]; // intに変換 (obs, reward, done) = env.Step(action); totalReward += reward; } Console.WriteLine("Total Reward = {0}", totalReward); } これで Total Reward がpythonのときと同じくらいになれば成功です。 おつかれさまでした。 参考文献 Ray documentation RLlibのエクスポートの例 Make predictions with an AutoML ONNX model in .NET