ゲームエフェクトデザイナーのブログ (新)

レポート記事とかUE4のマテリアルとか。C#とかも触ったり。

メインコンテンツ目次

●TOPIC

▼デスクトップキャプチャ『Tenpu Gazo Maker』をリリースしました!

▼シンプルなテキストエディタList Fusen 2』をリリースしました!

▼タスク管理アプリ『TaskQuest』をリリースしました!

▼作ったツールやスライドはこちらで公開しています
 moko-vfx (moko) · GitHub
 Moko, Video game fx artist | SlideShare

▼エフェクトの資料は Pinterest にまとめています。

▼ご支援いただけたらありがたいです。。


Unreal Engine 4(UE4

エフェクトツールを使ってできること
エフェクトのワークフロー

テクスチャのインポートについて
テクスチャのプロパティについて
テクスチャのフォーマットについて

マテリアル作成の基本的な知識
最終マテリアル入力の各ポートについて
パーティクル向けのマテリアルの設定について


●BISHAMON

BISHAMONの最初のステップ
BISHAMON Personal v1.8 の新機能について
BISHAMONへのカメラのインポートについて

Maya

MELの最初のステップ
MELのウインドウ作成について
MELでシェーダー作成 1
MELでシェーダー作成 2
MELスクリプトのある場所を参照する
MELを配布する時のプロシージャについて
UVの正規化
頂点カラーを別のモデルに転送
テクスチャの色を頂点カラーに転送

Photoshop

Photoshopのスクリプトを書いてみる

C#やプログラム全般

「C#」の記事一覧

●その他の記事

数学が苦手な人向けのCG数学シリーズ

イチオシゲームエフェクトアレコレ

処理速度と処理量のお話 ms(ミリセカンド)について
ドローコールについて

おおまかな開発コストの計算方法
おおまかなゲームソフトの売り上げの計算方法

勉強会開催のすすめ
大阪・京都の貸し会議室メモ

 

C# フォームで動画再生

Windows Media Playerのコントロールを利用する形で試してみました。
f:id:moko_03_25:20200329023827p:plain

利用すると言えどもやらないといけないことは色々あります。
・動画を開くボタンを追加、アイコンを作成して設定
・ショートカットキーによる再生/停止、コマ送り/戻し
・ショートカットキーによる動画サイズ変更
・フルスクリーン時にショートカットキーを受け付ける
・ボリューム設定の記憶と復元
・リピート再生(未実装)
・動画ファイルのドラッグ&ドロップ対応(こちらは継承しての拡張が必要なため未実装)
区間再生(UIが絡んで悩ましいため現状未実装)

以前調べたときのものはこちら。
effect.hatenablog.com


以下はお世話になった記事(感謝です!)。
AxWindowsMediaPlayer | C# プログラミング解説
c#.netでWindowsMediaPlayerコントロールを使って動画プレイヤーを作成する。 - 中堅プログラマーの備忘録
axWindowsMediaPlayerでコマ送りをするには?

試した際のソースコードはこちらのような感じ。

using System;
using System.IO;
using System.Windows.Forms;
using WMPLib;

namespace VideoPlayer
{
	public partial class Form1 : Form, IMessageFilter
	{
		string videoPath, videoTitle;
		WMPPlayState videoState;
		int srcWidth = 0;
		int srcHeight = 0;
		double loopSta = 0.0;
		double loopEnd = 0.0;
		bool ready = false;

		public Form1()
		{
			InitializeComponent();
			//wmpVideo.uiMode = "none";

			this.KeyPreview = true;
			wmpVideo.AllowDrop = true;
			wmpVideo.enableContextMenu = false;
		}

		// ロード時
		private void Form1_Load(object sender, EventArgs e)
		{
			// アプリケーションの設定を読み込む
			Properties.Settings.Default.Reload();

			try
			{
				wmpVideo.settings.mute = Properties.Settings.Default.isMute;
				wmpVideo.settings.volume = Properties.Settings.Default.volume;
			}
			catch (Exception)
			{

			}
		}

		// 終了時
		private void Form1_FormClosing(object sender, FormClosingEventArgs e)
		{
			try
			{
				Properties.Settings.Default.isMute = wmpVideo.settings.mute;
				Properties.Settings.Default.volume = wmpVideo.settings.volume;
			}
			catch (Exception)
			{

			}			

			// アプリケーションの設定を保存する
			Properties.Settings.Default.Save();
		}

		// フルスクリーンモード時にキーを受け付けるためのもの
		protected override void OnLoad(EventArgs e)
		{
			base.OnLoad(e);
			Application.AddMessageFilter(this);
		}
		protected override void OnFormClosing(FormClosingEventArgs e)
		{
			base.OnFormClosing(e);
			Application.RemoveMessageFilter(this);
		}
		public bool PreFilterMessage(ref Message m)
		{
			const int WM_KEYDOWN = 0x100;
			if (m.Msg == WM_KEYDOWN)
			{
				Keys keyCode = (Keys)m.WParam & Keys.KeyCode;
				if (keyCode == Keys.Escape)
				{
					this.wmpVideo.fullScreen = false;
					this.ActiveControl = this.wmpVideo;
					return true;
				}
				else if (keyCode == Keys.D1)
				{
					this.wmpVideo.fullScreen = false;
					this.ActiveControl = this.wmpVideo;
					changeScreen(1);
					return true;
				}
				else if (keyCode == Keys.D2)
				{
					this.wmpVideo.fullScreen = false;
					this.ActiveControl = this.wmpVideo;
					changeScreen(2);
					return true;
				}
				else if (keyCode == Keys.D3)
				{
					this.wmpVideo.fullScreen = false;
					this.ActiveControl = this.wmpVideo;
					changeScreen(3);
					return true;
				}
			}
			return false;
		}

		// ショートカットキー
		private void WmpVideo_PreviewKeyDown(object sender, PreviewKeyDownEventArgs e)
		{
			if (ready)
			{
				// 動画の状態を更新
				videoState = wmpVideo.playState;

				// スペースキー
				if (e.KeyCode == Keys.Space)
				{
					if ("wmppsPlaying" == videoState.ToString())
					{
						wmpVideo.Ctlcontrols.pause();
					}
					else if ("wmppsPaused" == videoState.ToString())
					{
						wmpVideo.Ctlcontrols.play();
					}
				}
				else if (e.KeyCode == Keys.E)
				{
					// 一時停止
					wmpVideo.Ctlcontrols.pause();

					// コマ送り
					((IWMPControls2)wmpVideo.Ctlcontrols).step(1);
				}
				else if (e.KeyCode == Keys.W)
				{
					// 一時停止
					wmpVideo.Ctlcontrols.pause();

					// コマ戻し
					((IWMPControls2)wmpVideo.Ctlcontrols).step(-1);
				}
				else if (e.KeyCode == Keys.OemOpenBrackets)
				{
					// 現在の再生位置を取得
					loopSta = wmpVideo.Ctlcontrols.currentPosition;
				}
				else if (e.KeyCode == Keys.OemCloseBrackets)
				{
					// 現在の再生位置を取得
					loopEnd = wmpVideo.Ctlcontrols.currentPosition;
				}
				else if (e.KeyCode == Keys.D1)
				{
					changeScreen(1);
				}
				else if (e.KeyCode == Keys.D2)
				{
					changeScreen(2);
				}
				else if (e.KeyCode == Keys.D3)
				{
					changeScreen(3);
				}
				else if (e.KeyCode == Keys.D4)
				{
					// フルスクリーン
					wmpVideo.fullScreen = true;
				}
			}
		}

		// ドラッグ&ドロップ
		private void Form1_DragEnter(object sender, DragEventArgs e)
		{
			// コントロール内にドラッグされたとき実行される
			if (e.Data.GetDataPresent(DataFormats.FileDrop))
				// ドラッグされたデータ形式を調べ、ファイルのときはコピーとする
				e.Effect = DragDropEffects.Copy;
			else
				// ファイル以外は受け付けない
				e.Effect = DragDropEffects.None;
		}
		private void Form1_DragDrop(object sender, DragEventArgs e)
		{
			// コントロール内にドロップされたとき実行される
			// ドロップされたすべてのファイル名を取得する
			string[] fileName =
				(string[])e.Data.GetData(DataFormats.FileDrop, false);
			
			// 動画のファイルパスを取得
			videoPath = fileName[0];
			videoTitle = Path.GetFileName(videoPath);

			setUpValue();
		}

		// マウスクリックで再生/一時停止をトグル
		private void WmpVideo_ClickEvent(object sender, AxWMPLib._WMPOCXEvents_ClickEvent e)
		{
			if (ready)
			{
				// 動画の状態を更新
				videoState = wmpVideo.playState;

				if ("wmppsPlaying" == videoState.ToString())
				{
					//wmpVideo.Ctlcontrols.pause();
				}
				else if ("wmppsPaused" == videoState.ToString())
				{
					//wmpVideo.Ctlcontrols.play();
				}

				//srcWidth = wmpVideo.currentMedia.imageSourceWidth;
				//srcHeight = wmpVideo.currentMedia.imageSourceHeight;
				//MessageBox.Show(srcWidth.ToString() + " : " + srcHeight.ToString() + "\r\n" +
					//"Form " + this.Width.ToString() + " : " + this.Height.ToString() + "\r\n" +
					//"Screen" + wmpVideo.Width.ToString() + " : " + wmpVideo.Height.ToString());
			}
		}

		// ボタンのアイコン差し替え
		private void BtnOpenVideo_MouseEnter(object sender, EventArgs e)
		{
			btnOpenVideo.BackgroundImage = Properties.Resources.Icon_Open_on;
		}
		private void BtnOpenVideo_MouseLeave(object sender, EventArgs e)
		{
			btnOpenVideo.BackgroundImage = Properties.Resources.Icon_Open;
		}

		// 開くボタン
		private void BtnOpenVideo_Click(object sender, EventArgs e)
		{
			OpenFileDialog openFileDialog = new OpenFileDialog() { Multiselect = false, Filter = "MP4 File|*.mp4|All File|*.*" };
			if (openFileDialog.ShowDialog() == DialogResult.OK)
			{
				videoPath = openFileDialog.FileName;
				videoTitle = openFileDialog.SafeFileName;

				setUpValue();
			}
		}

		// 動画を開いた際の準備
		public void setUpValue()
		{
			wmpVideo.URL = videoPath;
			this.Text = videoTitle;
			ready = true;
		}

		// 動画サイズを変える
		public void changeScreen(int sizeID)
		{
			wmpVideo.fullScreen = false;
			wmpVideo.stretchToFit = true;

			srcWidth = wmpVideo.currentMedia.imageSourceWidth;
			srcHeight = wmpVideo.currentMedia.imageSourceHeight;

			switch (sizeID)
			{
				case 1:
					this.Width = (srcWidth / 2) + 16;
					this.Height = (srcHeight / 2) + 39;
					break;

				case 2:
					this.Width = (srcWidth) + 16;
					this.Height = (srcHeight) + 39;
					break;

				case 3:
					this.Width = (srcWidth * 2) + 16;
					this.Height = (srcHeight * 2) + 39;
					break;

				default:
					break;
			}
		}
	}
}

「C#」の記事一覧

C#.NET frameworkを使ったWindows Forms Aplicationと、制作時のちょっとしたTIPSメモのメニューになります。

作ったツール


C# リスト管理できるテキストエディタ「List Fusen」を制作&リリース
C# UE4のアセットの命名規則チェックツールを作ってみた
C# 線グラデーション画像を作成するツール
BMP画像に保存された色の値を調べる
C# よく使う文字列をコピペするための補助ツール
C# ファイルパス・フォルダパスをリストアップするツール
C# タスクスケジューラへタスクを登録するツール
C# UE4のフォルダカラーを設定するリストを作成するツール
C# 英単語を覚えるためのツール「English Words Check Tool」を公開しました!
リスト管理できるテキストエディタ「List Fusen」がバージョン2になりました!
Windows用タスク管理アプリ『TaskQuest』について
Tenpu Gazo Maker をリリースしました!


入門


C#のはじめ方
C# ソリューションの基本的なファイル構成について
C# WindowsFormのコントロールの概要

C# Formの主なプロパティ
C# Labelの主なプロパティ
C# ComboBoxの主なプロパティ
C# DataGridViewの主なプロパティと関数
C# TreeViewの主なプロパティ
C# PictureBoxの主なプロパティ


入門の次のステップ


C# 初心者の次のステップ
GitHubでのツールやソース公開について(CGアーティスト向け)
ライセンスについてのメモ
C#でWPFを始める
C#を覚えてからC++を始めたときのメモ


テキスト


C#で正規表現による置換を行う
C# TreeViewのノードに対応した文字列をTextBoxに表示する
C# XMLファイルを読み込んでTreeViewにノードを追加する
C# XMLへの階層構造の保存メモ1(基本編)
C# XMLへの階層構造の保存メモ2(TreeView編)
C# XMLへの階層構造の保存メモ3(TreeView編)
C# TreeViewの親子階層内で指定のノードを選択する
C# テキストの暗号化と複合の方法について
C# RPG風にテキストメッセージを1文字ずつ表示させる
C# WordドキュメントをMarkdownに変換したい


ファイル


C#でサブフォルダをリストアップ
C#でサブフォルダの総サイズのリストアップ(ListBox編)
C#でサブフォルダの総サイズのリストアップ(DataGridView編)


データベース


C# Listの中身をDataGridViewに追加する方法
C# DataGridViewでの値のコピー&ペーストの実装
C# DataGridViewの表示がおかしくなった時の対処法
C# DataGridViewのソートとフィルタのサンプル
C# DataGridViewにDataTableをデザイナでバインドしている際のフィルタのサンプル
C# DataGridViewにDataTableをデザイナでバインドしている際に列を名前で指定する
C# DataGridViewの列の値に0から始まる番号を入力する
C# タスクをリスト管理するツールの基礎となるサンプルデータを公開


画像 / 動画


C# PictureBoxを2つ重ねるサンプル
C# 画像にラインを引く
C# 画像にラインを引く際にShiftキーで垂直/水平に補正する
C# 画像にラインを引く際にShiftキーで45度に補正する
C# 画像に選択範囲のような枠線を引く
C# ハイトマップからノーマルマップを生成してみた
C# フォームで動画再生
動画再生プレイヤーを作りたいメモ


3D


C#で3Dモデルを表示したい場合の方法メモ
C# 多角形の3Dモデルの描画
C# 穴の空いた多角形の3Dモデルの描画
C# 穴の空いた多角形の3Dモデルにテクスチャを貼ってみた


フォーム / コントロール


C# コントロールの表示をボタンで切り替える
C# Buttonコントロールの枠線を消す方法
C# スペースやEnterキーでボタンを反応させない
C# Formから別のFormを作成する
C# フォーム作成時の最小サイズは136*39
C# スペースキー+マウスドラッグでスクロールを制御する1
C# スペースキー+マウスドラッグでスクロールを制御する2


Twitter


C# Twitterアプリを作ってみる1
C# Twitterアプリを作ってみる2

その他


C# カラー管理クラスを考える
C# Visual Studioで設定したマルチアイコンでサイズ別に古い画像が残る現象
WPF導入に良さそうな情報まとめ 
Making of TaskQuest
Making of TaskQuest<実装編>

C# スペースキー+マウスドラッグでスクロールを制御する2

前回の記事の続きです。
effect.hatenablog.com

すでに事前準備ができているので、あとはスクロールバーの制御の部分を実装するだけです。
考え方としては、PictureBox の「MouseDown」「MouseMove」「MouseUp」のイベントでマウスドラッグを判定して、ドラッグ中にスペースキーを押している状態であればPanelのスクロールバーを動かすという感じです。
「MouseMove」はただマウスを動かしているだけの時も反応してしまうので「MouseDown」と「MouseUp」を組み合わせる必要が出ます。ここは少々不便な点ではあります。

具体的な記述方法についてはこちらの記事を参考にさせていただきました。
codepanic.itigo.jp

という訳で無事に動作しました!

f:id:moko_03_25:20200314212815g:plain

ソ-スコードは以下のような感じです。

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

namespace Sample_NoFocusedButton
{
	public partial class Form1 : Form
	{
		bool isDragging = false;
		bool downSpaceKey = false;
		Point posStart;


		public Form1()
		{
			InitializeComponent();
		}

		// フォームロード時
		private void Form1_Load(object sender, EventArgs e)
		{
			// サンプル画像を PictureBox に読み込む
			Bitmap b = new Bitmap(@"sample.png");
			pictureBox1.Image = b;
		}

		// ボタンクリック時
		private void Button1_Click(object sender, EventArgs e)
		{
			// ボタンを押した後にスペースキーでボタンをクリックさせない対処
			ActiveControl = null;
		}

		// スペースキーを押した時
		private void Form1_KeyDown(object sender, KeyEventArgs e)
		{
			if (e.KeyCode == Keys.Space)
			{
				pictureBox1.Cursor = Cursors.Hand;
				downSpaceKey = true;
			}
		}

		// スペースキーを離した時
		private void Form1_KeyUp(object sender, KeyEventArgs e)
		{
			if (e.KeyCode == Keys.Space)
			{
				pictureBox1.Cursor = Cursors.Default;
				downSpaceKey = false;
			}
		}

		// マウスクリック時
		private void PictureBox1_MouseDown(object sender, MouseEventArgs e)
		{
			isDragging = true;
			posStart = e.Location;
		}

		// マウスドラッグ時
		private void PictureBox1_MouseMove(object sender, MouseEventArgs e)
		{
			if (isDragging && downSpaceKey)
			{
				Point pos = new Point(
					e.Location.X - posStart.X,
					e.Location.Y - posStart.Y);

				panel1.AutoScrollPosition = new Point(
					-panel1.AutoScrollPosition.X - pos.X,
					-panel1.AutoScrollPosition.Y - pos.Y);
			}
		}

		// マウスドラッグ終了時
		private void PictureBox1_MouseUp(object sender, MouseEventArgs e)
		{
			isDragging = false;
		}
	}
}

C# スペースキー+マウスドラッグでスクロールを制御する1

フォーム上に配置したPictureBoxに大きな画像を読み込んだ際に、PictureBox内でスペースキーを押しっぱなしにしている間、マウスドラッグでスクロールバーを制御できるようにするための準備についてメモしておきたいと思います。

まずはPctureBoxで画像を表示した際に、画像が大きい場合にスクロールバーが表示されるようにします。
PictureBoxにスクロールバーが存在しないため、Panelを利用する必要があります。
具体的には以下のように設定。

・フォームにPictureBoxの親となるPanelを配置
 PictureBoxを表示したい位置/サイズにして AutoScroll を True にする
・Panel内にPictureBoxを配置
 SizeMode を AutoSize にする

次に、ボタンを押すとフォーカスがボタンに移り、スペースキーを押すとボタンが反応してしまう挙動を解消する必要があります。
この方法については前回の記事をご覧ください。

ではスペースキーを押した際にPictureBox内でのみマウスカーソルが手のひらアイコンになるようにします。
丁度システムカーソルに手のひら状態が存在するので、そちらを利用します。
具体的な方法はdobon.netさんの記事を参考にさせていただきました。
システムカーソルを取得する - .NET Tips (VB.NET,C#...)

ちなみに最初、「PictureBox1_Mouse_Enter」と「PictureBox1_Mouse_Leave」のイベントでカーソルのアイコンを変えようとしていましたがなぜかうまくいきませんでした。
そこで Form1 の KeyPreview を True にしてキーイベントをフォームが受け取るようにし、「Form1_KeyDown」と「Form1_KeyUp」で pictureBox1 のカーソルが変化するように指定することでうまくいきました。
KeyPreviewについてはこちらの記事で解説されています。
KeyPressなどのキーイベントをすべてフォームが受け取るようにする - .NET Tips (VB.NET,C#...)


実行してみると、うまく動作しています!
f:id:moko_03_25:20200314204844g:plain

ソースコードは下記のような感じです。

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

namespace Sample_NoFocusedButton
{
	public partial class Form1 : Form
	{
		public Form1()
		{
			InitializeComponent();
		}

		// フォームロード時
		private void Form1_Load(object sender, EventArgs e)
		{
			// サンプル画像を PictureBox に読み込む
			Bitmap b = new Bitmap(@"sample.png");
			pictureBox1.Image = b;
		}

		// ボタンクリック時
		private void Button1_Click(object sender, EventArgs e)
		{
			// ボタンを押した後にスペースキーでボタンをクリックさせない対処
			ActiveControl = null;
		}

		// スペースキーを押した時
		private void Form1_KeyDown(object sender, KeyEventArgs e)
		{
			if (e.KeyCode == Keys.Space)
			{
				pictureBox1.Cursor = Cursors.Hand;
			}
		}

		// スペースキーを離した時
		private void Form1_KeyUp(object sender, KeyEventArgs e)
		{
			if (e.KeyCode == Keys.Space)
			{
				pictureBox1.Cursor = Cursors.Default;
			}
		}
	}
}


続きはこちら!
effect.hatenablog.com

C# スペースやEnterキーでボタンを反応させない

ボタンを押すと、TabStopを無効にしていたとしてもそのボタンにフォーカスが移りますが、するとスペースキーやEnterキーを押すとそのボタンをクリックするという困った仕様があります。
これは大体の場合に問題ですし、特にスペースキーを何かの機能のショートカットキーとして使いたい場合に厄介です。

例えば、スペースキーを押しっぱなしするとカーソルガ手のひらのアイコンになって、スクロールバーが出ているコントロールのスクロールを制御できる機能(Photoshopの手のひらツールみたいな)を実装したいと思った時に困りました。

標準のボタンではフォーカスを無効にするプロパティは表に出ておらず、ボタンコントロールを継承して無効に設定したボタンを作ってやる必要があるようです。
そこでこちらの記事と質問サイトを参考に試してみました。
フォーカスを受け取らないボタンを作成する - C#プログラミング
フォームが来ないボタン

結果から言うとダメだったのですが、一応試した際のコードを載せておきます。

試してみた時のフォームの外観はこちらのような感じ。
「button1」と「picturebox1」を配置した状態です。
f:id:moko_03_25:20200314193557p:plain

▼Form1.cs

using System;
using System.Windows.Forms;

namespace Sample_NoFocusedButton
{
	public partial class Form1 : Form
	{
		public Form1()
		{
			InitializeComponent();
		}
	}

	public partial class NoFocusedButton : Button
	{
		public NoFocusedButton() : base()
		{
			SetStyle(ControlStyles.Selectable, false);
		}
	}
}


▼Form1.Designer.cs のButton1の生成部分をこちらのように書き換えてみたもの。

/// <summary>
/// デザイナー サポートに必要なメソッドです。このメソッドの内容を
/// コード エディターで変更しないでください。
/// </summary>
private void InitializeComponent()
{
	this.button1 = new Sample_Sample_NoFocusedButton.NoFocusedButton();
	this.panel1 = new System.Windows.Forms.Panel();
	this.pictureBox1 = new System.Windows.Forms.PictureBox();
	this.panel1.SuspendLayout();
	((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).BeginInit();
	this.SuspendLayout();


コメントで書かれているようにForm1.Designer.cs の内容をコードエディタで変更するのは良くないようなので、ユーザコントロールとして新規追加してビルドし、フォームデザイナ上に配置してあげる方が良いと思います。


さて、こちらは「試してダメだった」のですが、じゃあどうやって解決したかというと、下記のコードのようにアクティブコントロールNull を設定してやることでフォーカスを解除しました。
ちょっと強引な方法ですが、フォーカスが解除されて問題ない状況なら有効ではと思います。そもそもTabキーでフォーカスを変えられない別のコントロール(この事例だとpicturebox1)にフォーカスを移してやるのでもうまくいきました。

▼Form1.cs

using System;
using System.Windows.Forms;

namespace Sample_NoFocusedButton
{
	public partial class Form1 : Form
	{
		public Form1()
		{
			InitializeComponent();
		}

		private void Button1_Click(object sender, EventArgs e)
		{
			ActiveControl = null;
		}
	}
}


スマートではないですが、とりあえずそんな方法もあるということで。。
何か良い解決法をご存じの方はコメント欄で教えていただけると助かります!

C# PictureBoxを2つ重ねるサンプル

フォーム上でPictureBoxを2つ重ねて、下のレイヤーは背景用・上のレイヤーは透明にして一部だけ描画したい場合があると思います。
(画像を読み込んでトリミングするために選択範囲を描画したい場合など)

こちらが実際にスクリーンショット画像を取り込んでPictureBox1に表示し、PictureBox2で赤いラインを引くのを試してみてうまくいった状態になります。
f:id:moko_03_25:20200311232257j:plain

しかし落とし穴がいくつかあるのでメモしておきたいと思います。

まずForm1上にPictureBox1を置いて想定する位置に配置し、PictureBox2は小さくても良いのでLocationを 0, 0 にしておきます。
f:id:moko_03_25:20200311232250j:plain

またPictureBox2のBackColorは「Color.Transparent」にして背景を透明にしますが、透明にならない場合はコントロールの親子の設定が関係してくるようです。なのでフォームのロードイベントで「pictureBox2.Parent = pictureBox1;」のように設定しています。
この点に関してこちらの記事を参考にさせていただきました。
ossannt.hatenablog.com

この時、PictureBox2はPictureBox1の 0, 0 を原点とした位置に移動します。
そこで2つピッタリ重ねるために先ほど書いたようにあらかじめPictureBox2のLocationを 0, 0 に設定しています。
最初はフォームのロードイベントでLocationが 0, 0 にするよう試しましたがダメでした(もしかしたら親子付けする前に移動すれば良かっただけかも‥)。

実際のソースコードはこちらのような感じです。
PictureBox1のサイズが固定である前提の書き方になっていますが、変動する場合はPictureBox1のSizeChangedイベントでPictureBox2のサイズが連動するように書くと良いです。

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

namespace SampleDoubleDraw
{
	public partial class Form1 : Form
	{
		public Form1()
		{
			InitializeComponent();
		}

		// 変数
		Point startPoint;
		Point endPoint;
		Bitmap canvas;
		Graphics g;
		Pen linePen;

		// Formロード時
		private void Form1_Load(object sender, EventArgs e)
		{
			LoadSampleImage();

			// PictureBox2の設定
			pictureBox2.Parent = pictureBox1;
			pictureBox2.Width = pictureBox1.Width;
			pictureBox2.Height = pictureBox1.Height;

			// サイズだけ指定して透明のキャンバスにする
			// pictureBox1のサイズで生成する
			canvas = new Bitmap(pictureBox1.Width, pictureBox1.Height);

			// ImageオブジェクトのGraphicsオブジェクトを作成
			g = Graphics.FromImage(canvas);

			// Penオブジェクトの作成
			linePen = new Pen(Color.Red, 4);

			// スタイルを指定
			linePen.DashStyle = DashStyle.Solid;
		}

		// マウスクリック時
		private void PictureBox2_MouseDown(object sender, MouseEventArgs e)
		{
			// カーソルの開始座標を取得
			startPoint = CursorPos();
		}

		// マウスドラッグ時
		private void PictureBox2_MouseMove(object sender, MouseEventArgs e)
		{
			// マウスの左ボタンが押されている場合のみ処理
			if ((Control.MouseButtons & MouseButtons.Left) == MouseButtons.Left)
			{
				// カーソルの終了座標を取得
				endPoint = CursorPos();

				// 描画
				DrawLine();
			}
		}

		// マウスドラッグ終了時
		private void PictureBox2_MouseUp(object sender, MouseEventArgs e)
		{
			// 何か処理が必要であれば書く
		}

		// 関数:ラインを描画
		private void DrawLine()
		{
			// 前回のライン描画を一旦クリア
			g.Clear(Color.Transparent);

			// ラインを描画
			g.DrawLine(linePen, startPoint, endPoint);

			// PictureBox2に表示
			pictureBox2.Image = canvas;
		}

		// 関数:カーソル位置を取得
		private Point CursorPos()
		{
			// 画面座標でカーソルの位置を取得
			Point p = Cursor.Position;
			// 画面座標からコントロール上の座標に変換
			Point cp = pictureBox2.PointToClient(p);

			return cp;
		}

		// 関数:サンプル画像を読み込んで表示
		private void LoadSampleImage()
		{
			Bitmap b = new Bitmap(@"sample.png");
			pictureBox1.Image = b;
		}
	}
}


マウスドラッグで選択範囲(ラバーバンド)を描画する場合に、前のフレーム結果にそのまま合成すると選択範囲の描画が残って重なっていってしまいます。そこで選択範囲を描画する前のImageを取得しておいて毎フレームPictureBoxを上書きしてから選択範囲を描画することでカバーすると、非常に重くなります。Winfows Formsは古いライブラリでありGPUを利用しないからと思われます。

この場合、OpenCLの利用やWPFに乗り換えるといった手があるようですが、今回のPictureBoxを2枚重ねる方法でかなり快適になりました。
f:id:moko_03_25:20200312021211g:plain

しかし限界を感じてきています。
そろそろWPFに手を出す時期か。。

Tenpu Gazo Maker をリリースしました!

矩形範囲でスクリーンショットを撮れて、ちょっとした画像編集が可能なツールです。 f:id:moko_03_25:20200311032315g:plain

GitHubにアップしています。
binaryフォルダ内にある「TenpuGazoMaker_v1xx.zip」にツール本体の実行ファイルが入っていますので、こちらをクリックして飛んだ先の「Download」ボタンからDLしてお使いください。

ライセンスは MIT License です。

使い方は各ボタンにカーソルを置くと表示されるツールチップをご覧ください。
不具合があったらtwitter(@moko_03_25)等でお知らせいただけると助かります!

Tenpu Gazo Maker の特徴


●画像取り込み

・デスクトップの矩形キャプチャ
 その際にキャプチャ枠の移動やサイズ変更が可能
クリップボードから画像のペースト
・画像をドラッグ&ドロップで放り込み

●画像のちょっとした編集

・枠線をボタン1発で付ける(白い背景の時などに)
・選択範囲をトリミング
・選択範囲の枠を塗る
・ライン描画モードで直線を引ける
 矢印付きのラインも可能

●そのほか

・画像をクリップボードへコピー
・画像をPNGで出力
・Undo/Redo
・各アイコンはショートカットキーで実行可能

既知の不具合


・右クリックで表示するミニ設定ウインドウが消えない時がある
 ※何度かマウスカーソルを行き来すると消えますが原因不明‥

更新履歴


・2020.03.11 v.1.00 リリース!

・2020.03.12 v.1.01
 選択範囲やラインの描画の処理落ちを大幅に改善

・2020.03.13 v.1.02
 横スクロールバーが表示されない不具合を修正
 ライン描画時のみアンチエイリアスを有効化
 Ctrl+Vでクリップボードから画像を取り込むよう変更
 Ctrl+Dでキャプチャ枠から背景画像を取り込むよう変更
 Escで設定画面を表示
 設定画面の表示位置とデザインを少し修正
 ツール下部に枠線カラーと直線カラーを表示
 ツールチップの説明を拡充

・2020.03.13 v.103
 色設定を変えた際にツール下部のカラー表示が更新されない不具合を修正

・2020.03.14 v.104
 スペースキー+マウスドラッグによる表示画像のスクロールを追加

・2020.03.15 v.105
 選択範囲の枠の描画時に角に隙間ができる不具合を修正
 画像上で右クリックすることでミニ設定ウインドウを表示
 マウスオーバーでアイコン画像を変更

・2020.03.17 v.106
 ラインモード時に一度ラインを引くとカーソルが戻る不具合を修正
 英語/日本語の切り替え表示に対応(ツールチップとエラーメッセージ)

・2020.03.28 v.107
 画像を閉じた時にサイズ表示が残る不具合を修正
 ライン描画モードをOFFにしてもカーソルアイコンが戻らない不具合を修正

 

作者 / 著作権


moko

動作確認済みのOS


Windows10(64bit)

使用条件


本ツールはOSSオープンソース・ソフトウェア・ライセンス)です。
商用・非商用に関わらず(プライベートでもお仕事でも)ご自由にお使いください。
複製、改変、再配布も自由です。

免責事項


本ソフトウェアは予告なく機能を変更することがあります。
本ソフトウェアは動作環境を満たす全ての環境で正常に動作することを期待していますが、保証はできません。
本ソフトウェアの使用により生じたいかなる損害に関して、作者は一切の責任を負いません。

C# フォーム作成時の最小サイズは136*39

フォームオブジェクトを作成する際には、どうもサイズ制限があるようです。

こちらの図ではボタンを押したらメインフォームの下に子フォームを生成していますが、その際に大きさを30*30に指定してから生成しています。

form.Width = 30;
form.Height = 30;
form.show();

しかし136*39以下にはできません。

f:id:moko_03_25:20200310100030p:plain

なぜそんな小さいサイズが必要かというと、デスクトップキャプチャを指定した矩形範囲で実行したいからです。方法はこちらの記事を参考にさせていただきました。

どうすれば良いか悩みましたが自己解決しました。
このように、生成してからサイズを変えたら良いだけの話でした。。

form.show();
form.Width = 30;
form.Height = 30;

f:id:moko_03_25:20200310101445g:plain


どなたかのお役に立てば幸いです。

C# Formから別のFormを作成する

簡単なサンプルのメモです。

f:id:moko_03_25:20200310011202g:plain

using System;
using System.Windows.Forms;

namespace Sample_CreateForm
{
	public partial class Form1 : Form
	{
		public Form1()
		{
			InitializeComponent();
		}

		Form2 form;

		private void Button1_Click(object sender, EventArgs e)
		{
			form = new Form2();

			form.ShowInTaskbar = false; // タスクバーに表示させない
			form.TopMost = true; // 最前面に表示
			form.Left = this.Left + 30;
			form.Top = this.Top + 30;
			form.Width = 300;
			form.Height = 200;
			form.StartPosition = FormStartPosition.Manual;

			form.Show();
		}
	}

	public partial class Form2 : Form
	{
		// コンストラクタ
		public Form2()
		{
			// 必要な処理があれば書く
		}
	}
}