C#のWinFormsで画像加工アプリ1「ドラックアンドドロップで画像を表示しCtrl+Cでクリップボードへコピー」

コンピュータ

画像加工アプリケーションを作成します。
今回はメインとなるフォームにドラックアンドドロップで画像表示、Ctrl+Cでクリップボードへコピー、メニューで画像の拡大機能を作成してみました。

プロジェクトの作成

mkdir GazouKakou02
cd GazouKakou02
dotnet new winforms

ソースコード

ファイル名:Form1.cs


using System.Text.RegularExpressions;

namespace GazouKakou02;

public partial class Form1 : Form
{
    Bitmap? _buffBmp;
    Bitmap? _viewBmp;

    public Bitmap? Bmp
    {
        get
        {
            return _viewBmp;
        }
        set
        {
            _buffBmp?.Dispose();
            _buffBmp = value;
            if (_buffBmp is null) return;
            _viewBmp?.Dispose();
            _viewBmp = Scaleup(this._buffBmp, this._scale);
            mainPicbox.Image = _viewBmp;
        }
    }
    // メインメニュー
    readonly MenuStrip menubar = new();
    readonly ToolStripMenuItem viewMenuItem = new()
    {
        Text = "表示",
    };
    readonly ToolStripMenuItem filterMenuItem = new()
    {
        Text = "フィルター",
    };
    // ステータスバー
    readonly StatusStrip statusbar = new();
    readonly ToolStripStatusLabel statusLabel1 = new();
    // パネル
    readonly Panel mainPanel = new()
    {
        Dock = DockStyle.Fill,
        AllowDrop = true,
        AutoScroll = true,
    };
    // ピクチャボックス
    readonly PictureBox mainPicbox = new()
    {
        SizeMode = PictureBoxSizeMode.AutoSize,
    };
    public Form1()
    {
        InitializeComponent();
        
        // キーボードイベントをFormで受け取る
        this.KeyPreview = true;
        // コントロールの登録
        this.Controls.AddRange([
            mainPanel,
            menubar,
            statusbar,]);
        // メインメニューの登録
        menubar.Items.AddRange(new ToolStripItem[]{
            viewMenuItem,
            filterMenuItem,});
        // ステータスバーを登録
        statusbar.Items.Add(statusLabel1);        
        // ピクチャボックスを登録
        mainPanel.Controls.Add(mainPicbox);
        // ドラックアンドドロップ
        mainPanel.DragEnter += (s, e) =>
        {
            e.Effect = DragDropEffects.Copy;
        };
        mainPanel.DragDrop += async (s, e) =>
        {
            if (e.Data is null) return;
            var data = e.Data.GetData(DataFormats.FileDrop, false);
            if (data is not string[] files) return;

            this.Bmp = await Task.Run(()=>{
                using var fs = new FileStream(files[0], FileMode.Open, FileAccess.Read);
                var bmp = new Bitmap(fs);
                return bmp;
            });
        };
        // キーボードイベント
        this.KeyDown += async (s, e) =>
        {
            if (_buffBmp is null) return;

            // Ctrl+C
            if ((e.Modifiers & Keys.Control) == Keys.Control && e.KeyCode == Keys.C)
            {
                // クリップボードへ画像をコピー
                var ms = new MemoryStream();
                _buffBmp.Save(ms, System.Drawing.Imaging.ImageFormat.Png);
                Clipboard.SetData("PNG", ms);
                statusLabel1.Text = "クリップボードへコピーしました。";
                await Task.Delay(1000);
                statusLabel1.Text = "";
            }
        };

        // 各種機能の初期化処理
        Type type = this.GetType(); // 自分自身のクラスの情報を取得
        // メソッドの一覧を取得
        foreach(var mi in type.GetMethods())
        {
            // Initで始まるメソッド以外を排除
            if (Regex.IsMatch(mi.Name, "^Init_.+$") == false) continue;

            // メソッドの呼び出し
            mi.Invoke(this, null);
        }

        // クローズ
        this.FormClosed += (s, e) =>
        {
            _viewBmp?.Dispose();
            _buffBmp?.Dispose();    
        };
    }
}

ファイル名:FilterDialog.cs

namespace GazouKakou02;

public class FilterDialog : Form
{
    readonly public PictureBox Picbox = new()
    {
        Location = new (10, 10),
        Size = new (600, 240),
    };
    readonly public Button OkBtn = new()
    {
        Location = new (10, 260),
        Size = new (80, 40),
        Text = "OK",
        DialogResult = DialogResult.OK,
    };
    readonly public Button CancelBtn = new()
    {
        Location = new (100, 260),
        Size = new (80,40),
        Text = "Cancel",
        DialogResult = DialogResult.Cancel,
    };        
    readonly public Label Track1Label = new()
    {
        Location = new(10, 310),
        Size = new(90, 40),
    };
    readonly public TrackBar Track1 = new()
    {
        Location = new Point(110, 310),
        Size = new Size(200, 40),
    };
    // コンストラクタ
    public FilterDialog(string filterName="")
    {
        this.ClientSize = new Size(640, 480);
        this.Text = filterName;

        this.AcceptButton = OkBtn;
        this.CancelButton = CancelBtn;

        this.Controls.AddRange(
            [
                Picbox,
                OkBtn,
                CancelBtn,
                Track1Label,
                Track1,
            ]);
    }
}

ファイル名:Form1.Scaleup.cs

/// <summary>
/// プレビューの拡大表示
/// </summary>
using System.Drawing.Drawing2D;

namespace GazouKakou02;
public partial class Form1 : Form
{
    // メニュー項目
    readonly ToolStripMenuItem expandX1MenuItem = new()
    {
        Text = "等倍",
        Checked = true, // チェック
        Tag = 1,
    };
    readonly ToolStripMenuItem expandX2MenuItem = new()
    {
        Text = "拡大2倍",
        Checked = false, // 未チェック
        Tag = 2,
    };
    readonly ToolStripMenuItem expandX4MenuItem = new()
    {
        Text = "拡大x4",
        Checked = false, // 未チェック
        Tag = 4,
    };
    readonly ToolStripMenuItem expandX8MenuItem = new()
    {
        Text = "拡大x8",
        Checked = false, // 未チェック
        Tag = 8,
    };

    int _scale = 1; // 拡大倍率
    /// <summary>
    /// 拡大処理
    /// </summary>
    static public Bitmap Scaleup(Bitmap src, int scale)
    {
        int w = src.Width * scale;
        int h = src.Height * scale;
        Bitmap dst = new (w, h);

        using var g = Graphics.FromImage(dst);
        g.InterpolationMode = InterpolationMode.NearestNeighbor;
        g.DrawImage(src, 0, 0, w, h);

        return dst;
    }
    /// <summary>
    /// 拡大処理メニュー項目を選択
    /// </summary>
    public void ScaleupMenuItemSelect(Object? obj)
    {
        if (obj is null) return;
        if (obj is not ToolStripMenuItem menu) return;

        foreach(ToolStripMenuItem m in viewMenuItem.DropDownItems)
        {
            if (m.Tag == menu.Tag)
            {
                m.Checked = true;
                this._scale = (menu.Tag is null) ? 1 : (int)menu.Tag;
            } else {
                m.Checked = false;
            }
        }
        if (_buffBmp is null) return;
        this._viewBmp?.Dispose();
        this._viewBmp = Scaleup(this._buffBmp, this._scale);
        mainPicbox.Image = _viewBmp;
    }
    /// <summary>
    /// 拡大処理の初期化
    /// </summary>
    public void Init_Scaleup()
    {
        // メニューの登録
        viewMenuItem.DropDownItems.AddRange(
        new ToolStripItem[] {
            expandX1MenuItem,
            expandX2MenuItem,
            expandX4MenuItem,
            expandX8MenuItem,
        });
        // メニューアイテムのクリックイベント
        expandX1MenuItem.Click += (s, e) => ScaleupMenuItemSelect(s);
        expandX2MenuItem.Click += (s, e) => ScaleupMenuItemSelect(s);
        expandX4MenuItem.Click += (s, e) => ScaleupMenuItemSelect(s);
        expandX8MenuItem.Click += (s, e) => ScaleupMenuItemSelect(s);
    }

}

実行


フォームに画像ファイルをドラックアンドドロップ

画像が表示されます。

メニューで拡大率を選択できます。
このほかにキーボードでCtrl+Cでクリップボードへ画像がコピーされます。

今後

画像の編集は基本的にGIMPで行っているのですが、GIMPに無いOpenCVのフィルターなどをアプリケーションに実装し、フィルターの結果をコピーしGIMPで貼り付けて連携するような使い方を想定しています。
過去の記事で試したOpenCVのフィルター類を一個のアプリケーションで呼び出せるような形になりますが、ソースコードが膨大になりそうですので、フィルターごとに一つのソースファイル(=記事)としてプロジェクトに追加していく予定です。

コメント