ブログ用の簡単な画像処理ができるアプリを作りました。
C# で画像処理アプリを作りました。
— プログラミング素人 (@s51517765) 2020年2月24日
・クリップボードから画像を取り込む
・画像ファイルから画像を読み込む
・適切な大きさに縮小する
・周囲を囲む
・四角く塗りつぶす
・トリミングする
・保存するときに自動的に名前を付ける
・ひとつの工程分だけ戻す
といったことが出来ます。 pic.twitter.com/V8X4goCV5A
欲しい機能としては以下でした。
・クリップボードから画像を取り込む
・画像ファイルから画像を読み込む(File open dialog or DragDrop)
・適切な大きさに縮小する
・周囲を囲む
・四角く塗りつぶす
・トリミングする
・保存するときに自動的に名前を付ける
・ひとつの工程分だけ戻すことが出来る(Undo)
Windows標準のペイントや、ペイント3D、フリーソフトのGIMPなどはありますが、そこまで高度でなくていいし、もっと限定的な用途に特化して作業効率アップが目的です。
ここで使ったC#のコードの一部を紹介します。
Dobon.netのサンプルを大量に参考にしています。
dobon.net
ファイルオープン
フォルダアイコンをクリックしたとき。private void buttonFileOpen_Click(object sender, EventArgs e) { //OpenFileDialogクラスのインスタンスを作成 OpenFileDialog ofd = new OpenFileDialog(); //ダイアログを表示する if (ofd.ShowDialog() == DialogResult.OK) { //OKボタンがクリックされたとき、選択されたファイル名を表示する textBoxFileName.Text = ofd.FileName; PreviewDrowPicture(); } }
ファイルをドラッグドロップしたとき。
private void Form1_DragDrop(object sender, DragEventArgs e) { //コントロール内にドロップされたとき実行される //ドロップされたすべてのファイル名を取得する string[] fileName = (string[])e.Data.GetData(DataFormats.FileDrop, false); //textBoxに追加する textBoxFileName.Text = fileName[0]; PreviewDrowPicture(); } private void Form1_DragEnter(object sender, DragEventArgs e) { //コントロール内にドラッグされたとき実行される if (e.Data.GetDataPresent(DataFormats.FileDrop)) //ドラッグされたデータ形式を調べ、ファイルのときはコピーとする e.Effect = DragDropEffects.Copy; else //ファイル以外は受け付けない e.Effect = DragDropEffects.None; }
どちらも、TextBoxにファイル名(フルパス)をいったん取得し、 PreviewDrowPicture();
を呼び出し画像描画します。
プレビュー
画像の出力先となる、Bitmap
、Graphics
オブジェクトを作成し、imageを読み込みます。出力サイズ
FinalPictureHeight
、FinalPictureWidth
以下になるように、reSizeRate
を算出して縮小します。 labelStartMsg(xx)
は最初に表示しているガイドです。処理を開始したら不要ですので見えなくします。
クリップボードから画像を取得したときは、引数にその画像が入ります。
それ以外のときは、
textBoxFileName
のファイルを取得します。
//描画先とするImageオブジェクトを作成する Bitmap canvas = new Bitmap(pictureBoxWidth, pictureBoxHeight); private void PreviewDrowPicture(Image defImg = null) { //ImageオブジェクトのGraphicsオブジェクトを作成する Graphics g = Graphics.FromImage(canvas); //画像ファイルを読み込んで、Imageオブジェクトとして取得する Image img; if (defImg == null) { img = Image.FromFile(textBoxFileName.Text); } else { img = defImg; } //リサイズ率が小さいほう reSizeRate = ((float)pictureBox1.Width / (float)img.Width) < ((float)pictureBox1.Height / (float)img.Height) ? (float)pictureBox1.Width / (float)img.Width : (float)pictureBox1.Height / (float)img.Height; if (reSizeRate > 1) reSizeRate = 1; //小さい画像は拡大しない //画像をcanvasの座標(0, 0)の位置に描画する float w = (float)img.Width; float h = (float)img.Height; FinalPictureHeight = (int)(h * reSizeRate); FinalPictureWidth = (int)(w * reSizeRate); g.DrawImage(img, 0, 0, (int)(w * reSizeRate), (int)(h * reSizeRate)); //Imageオブジェクトのリソースを解放する img.Dispose(); //Graphicsオブジェクトのリソースを解放する g.Dispose(); labelStartMsg1.Visible = false; labelStartMsg2.Visible = false; labelStartMsg3.Visible = false; labelStartMsg4.Visible = false; } private void buttonGetClipbord_Click(object sender, EventArgs e) { if (Clipboard.ContainsImage()) { Image img = Clipboard.GetImage(); PreviewDrowPicture(img); } }
Undoできるようにする
意外とUndoは使います。そのため、現在の画像を保存しておくことで、一つだけ工程を戻すことが出来るようにします。
何か処理を行うときには、先にこの関数を呼び出すようにしておくことで、状態を保存しておくことが出来ます。
using System.Drawing; const int pictureBoxWidth = 1000; const int pictureBoxHeight = 750; Color[,] pic = new Color[pictureBoxWidth, pictureBoxHeight]; private void storeCurrentImage() { Image img = pictureBox1.Image; Bitmap bitmap = new Bitmap(img); for (int x = 0; x < FinalPictureWidth; x++) { for (int y = 0; y < FinalPictureHeight; y++) { pic[x, y] = bitmap.GetPixel(x, y); } } }
周囲を線で囲む
private void buttonDrowEdgeLine_Click(object sender, EventArgs e) { storeCurrentImage(); Color color; if (radioButtonDrowEdge_Red.Checked) color = Color.FromArgb(255, 0, 0); else if (radioButtonDrowEdge_Yellow.Checked) color = Color.FromArgb(255, 255, 0); else if (radioButtonDrowEdge_Green.Checked) color = Color.FromArgb(0, 255, 0); else if (radioButtonDrowEdge_Blue.Checked) color = Color.FromArgb(0, 0, 255); else color = Color.FromArgb(0, 0, 0); for (int x = 0; x < FinalPictureWidth; x++) { canvas.SetPixel(x, 0, color); } for (int y = 0; y < FinalPictureHeight; y++) { canvas.SetPixel(0, y, color); } for (int x = 0; x < FinalPictureWidth; x++) { canvas.SetPixel(x, FinalPictureHeight - 1, color); } for (int y = 0; y < FinalPictureHeight; y++) { canvas.SetPixel(FinalPictureWidth - 1, y, color); } pictureBox1.Image = canvas; }
四角く囲む
マウスクリックイベントで2回クリックされたら、始点と終点として四角形の対角線として、四角を作成します。1回目であるか2回目であるかはFlagを立てて区別します。
塗りつぶす場合も同様です。
private void pictureBox1_MouseClick(object sender, MouseEventArgs e) { if (FlagMask == 1) { Mask_address[0] = e.Location.X;//配列の0番にクリックした座標を入れる Mask_address[1] = e.Location.Y;//配列の1番にクリックした座標を入れる FlagMask = 2; //ImageオブジェクトのGraphicsオブジェクトを作成する Graphics g = Graphics.FromImage(canvas); //Penオブジェクトの作成(幅1の黒色) Pen p = new Pen(brush, 1); g.DrawRectangle(p, Mask_address[0], Mask_address[1], 1, 1); g.Dispose(); } else if (FlagMask == 2) { Mask_address[2] = e.Location.X;//配列の2番に現在の座標を入れる Mask_address[3] = e.Location.Y;//配列の3番に現在の座標を入れる if (Mask_address[0] > Mask_address[2]) swap_address(ref Mask_address[0], ref Mask_address[2]); if (Mask_address[1] > Mask_address[3]) swap_address(ref Mask_address[1], ref Mask_address[3]); //ImageオブジェクトのGraphicsオブジェクトを作成する Graphics g = Graphics.FromImage(canvas); //Penオブジェクトの作成(幅1の黒色) Pen p = new Pen(Color.Black, 1); //長方形を描く int size_x = Mask_address[2] - Mask_address[0]; int size_y = Mask_address[3] - Mask_address[1]; if (size_x > 0 && size_y > 0) { g.FillRectangle(brush, Mask_address[0], Mask_address[1], size_x, size_y); } else if (size_x < 0 && size_y < 0) { g.FillRectangle(brush, Mask_address[2], Mask_address[3], -size_x, -size_y); } //リソースを解放する p.Dispose(); g.Dispose(); buttonDrowMask.Enabled = true; FlagMask = 0; } } <h3>トリミングする</h3> トリミングはnumericUpDownでpixel数を指定し、切り取り後の範囲を指定して再描画します。 >|cs| private void buttonTrimming_Click(object sender, EventArgs e) { storeCurrentImage(); Image img = pictureBox1.Image; //ImageオブジェクトのGraphicsオブジェクトを作成する Graphics g = Graphics.FromImage(canvas); //切り取る部分の範囲を決定する。 int up = (int)numericUpDownTrimUp.Value; int down = (int)numericUpDownTrimDown.Value; int right = (int)numericUpDownTrimRight.Value; int left = (int)numericUpDownTrimLeft.Value; FinalPictureWidth -= (right + left); FinalPictureHeight -= (up + down); Rectangle srcRect = new Rectangle(left, up, FinalPictureWidth, FinalPictureHeight); //描画する部分の範囲。位置(0,0)、大きさX*Yで描画する Rectangle desRect = new Rectangle(0, 0, FinalPictureWidth, FinalPictureHeight); //画像の一部を描画する g.DrawImage(img, desRect, srcRect, GraphicsUnit.Pixel); //Graphicsオブジェクトのリソースを解放する g.Dispose(); }
名前を付けて保存
ここでの「トリミング」はCanvasより画像が小さい場合もあるため、Canvasの範囲を指定しています。private void buttonSave_Click(object sender, EventArgs e) { if (pictureBox1.Image != null) { int kakuchoshi = textBoxFileName.Text.IndexOf("."); string FileName; if (kakuchoshi == -1) { // https://dobon.net/vb/dotnet/file/getfolderpath.html FileName = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory) + "\\EasyRetouch.jpg"; textBoxFileName.Text = FileName; } else { FileName = textBoxFileName.Text.Substring(0, kakuchoshi); } //描画先とするImageオブジェクトを作成する Bitmap canvas = new Bitmap(FinalPictureWidth, FinalPictureHeight); //ImageオブジェクトのGraphicsオブジェクトを作成する Graphics g = Graphics.FromImage(canvas); //トリミング Rectangle srcRect = new Rectangle(0, 0, FinalPictureWidth, FinalPictureHeight); //描画する部分の範囲。位置(0,0)、大きさX * Yで描画する Rectangle desRect = new Rectangle(0, 0, FinalPictureWidth, FinalPictureHeight); //画像の一部を描画する g.DrawImage(pictureBox1.Image, desRect, srcRect, GraphicsUnit.Pixel); string suffix = textBoxSuffix.Text; int suffix_no = 0; while (System.IO.File.Exists(FileName + suffix + (suffix_no == 0 ? "" : suffix_no.ToString()) + ".jpg")) { suffix_no += 1; } canvas.Save(FileName + suffix + (suffix_no == 0 ? "" : suffix_no.ToString()) + ".jpg"); } }