質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.32%
C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

Windows Forms

Windows Forms(WinForms)はMicrosoft .NET フレームワークに含まれる視覚的なアプリケーションのプログラミングインターフェイス(API)です。WinFormsは管理されているコードの既存のWindowsのAPIをラップすることで元のMicrosoft Windowsのインターフェイスのエレメントにアクセスすることができます。

Q&A

1回答

376閲覧

選択範囲の形状を描画する方法

fana

総合スコア12136

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

Windows Forms

Windows Forms(WinForms)はMicrosoft .NET フレームワークに含まれる視覚的なアプリケーションのプログラミングインターフェイス(API)です。WinFormsは管理されているコードの既存のWindowsのAPIをラップすることで元のMicrosoft Windowsのインターフェイスのエレメントにアクセスすることができます。

0グッド

0クリップ

投稿2025/03/10 04:28

編集2025/03/10 04:29

Winform(.NET Framework 4.8) でペイントソフトを作っているのですが,自由形状の選択(ユーザがマウスドラッグで囲んだ範囲を選択)が成された際に,その範囲を示す表示を行う方法で困っています.

ペイントソフトなので表示倍率を変えることができるわけですが,その場合に下図の点線のような形で範囲を示す描画を行いたいのですが,これを簡単に実現する方法があるものでしょうか?

イメージ説明
赤い領域が選択されている範囲で,大きく拡大して表示しているイメージです.

矩形形状での選択であれば,拡大率Nのときには座標をN倍して矩形を描画すれば済むのですが……


とりあえず原始的(?)に,画素単位で「この画素は範囲の内側か外側か」を調べていけば,境界を見つけること自体はできるでしょうから,そういう方法で全て自前で頑張って「どの画素のどちら側に点線を引くべきか」みたいなのをやるしかないのでしょうか?

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

矩形形状での選択であれば,拡大率Nのときには座標をN倍して矩形を描画すれば済むのですが……

矩形でなくてもN倍でいいんじゃないですかね??
Graphics.DrawPolygon メソッド (System.Drawing) | Microsoft Learn

とりあえず原始的(?)に,画素単位で「この画素は範囲の内側か外側か」を調べていけば,境界を見つけること自体はできるでしょうから,そういう方法で全て自前で頑張って「どの画素のどちら側に点線を引くべきか」みたいなのをやるしかないのでしょうか?

標準のペイントでは中か外かは特にないというか、マウス軌跡そのままに引いているように見えます(ドット単位に境界はなく拡大すると線からはみ出るドットがあったりする)

いやゆっくり引くとガタガタになるので、ポイントごとのスナップ処理はあるんかな?(少なくともドット境界に合わせる気はなさそう^^;
ペイント

実際のところ選択範囲の移動等を考えると、GraphicsPathでやることになるかと思います。
GraphicsPath クラス (System.Drawing.Drawing2D) | Microsoft Learn
GraphicsPath.AddPolygon メソッド (System.Drawing.Drawing2D) | Microsoft Learn
方法: 領域でクリッピングを使用する - Windows Forms .NET Framework | Microsoft Learn

外側が重要な場合はWidenが使えそうです。
GraphicsPath.Widen メソッド (System.Drawing.Drawing2D) | Microsoft Learn


アイコンエディタのような小さい画像向けならばドット境界は重要でしょうが、大きい画像向けなら(わたしは)気にしていないかも(自由選択を使ってないから?^^;


FrameRgn

cs

1using System; 2using System.Drawing; 3using System.Drawing.Drawing2D; 4using System.Runtime.InteropServices; 5using System.Windows.Forms; 6 7namespace Q8nihxba9mjinwo 8{ 9 public partial class Form1 : Form 10 { 11 private readonly PictureBox pictureBox1; 12 private readonly PictureBox pictureBox2; 13 14 private GraphicsPath scaledPath; 15 private GraphicsPath realPath; 16 private Region region; 17 18 private bool isDragging; 19 private int startX, startY; 20 21 public Form1() 22 { 23 InitializeComponent(); 24 25 pictureBox1 = new MyPictureBox 26 { 27 BackColor = Color.White, 28 Image = new Bitmap(64, 64), 29 SizeMode = PictureBoxSizeMode.Zoom, 30 Width = 64 * 8, 31 Height = 64 * 8, 32 Parent = this, 33 }; 34 pictureBox1.MouseDown += PictureBox1_MouseDown; 35 pictureBox1.MouseMove += PictureBox1_MouseMove; 36 pictureBox1.MouseUp += PictureBox1_MouseUp; 37 pictureBox1.Paint += PictureBox1_Paint; 38 39 using (var g = Graphics.FromImage(pictureBox1.Image)) 40 { 41 g.FillEllipse(Brushes.Orange, new Rectangle(0, 0, 40, 40)); 42 } 43 44 pictureBox2 = new MyPictureBox 45 { 46 BackColor = Color.White, 47 Image = new Bitmap(64, 64), 48 Location = new Point(520, 0), 49 SizeMode = PictureBoxSizeMode.Zoom, 50 Width = 64 * 8, 51 Height = 64 * 8, 52 Parent = this, 53 }; 54 pictureBox2.Paint += PictureBox2_Paint; 55 } 56 57 private void PictureBox1_MouseDown(object sender, MouseEventArgs e) 58 { 59 scaledPath?.Dispose(); 60 scaledPath = new GraphicsPath(); 61 var matrix = new Matrix(); 62 matrix.Scale(8, 8); 63 scaledPath.Transform(matrix); 64 65 realPath?.Dispose(); 66 realPath = new GraphicsPath(); 67 68 isDragging = true; 69 startX = e.X; 70 startY = e.Y; 71 } 72 73 private void PictureBox1_MouseMove(object sender, MouseEventArgs e) 74 { 75 if (!isDragging) return; 76 77 scaledPath.AddLine(startX / 8 * 8, startY / 8 * 8, e.X / 8 * 8, e.Y / 8 * 8); 78 realPath.AddLine(startX / 8, startY / 8, e.X / 8, e.Y / 8); 79 80 pictureBox1.Refresh(); 81 startX = e.X; 82 startY = e.Y; 83 } 84 85 private void PictureBox1_MouseUp(object sender, MouseEventArgs e) 86 { 87 isDragging = false; 88 scaledPath.CloseFigure(); 89 pictureBox1.Refresh(); 90 91 var clippedImage = new Bitmap(64, 64); 92 using (var g = Graphics.FromImage(clippedImage)) 93 { 94 g.Clip = new Region(realPath); 95 g.DrawImage(pictureBox1.Image, 0, 0); 96 } 97 pictureBox2.Image?.Dispose(); 98 pictureBox2.Image = clippedImage; 99 100 using (var selectionMask = new Bitmap(64, 64)) 101 { 102 using (var g = Graphics.FromImage(selectionMask)) 103 { 104 g.Clip = new Region(realPath); 105 g.FillRectangle(Brushes.Aqua, 0, 0, 64, 64); 106 } 107 108 region?.Dispose(); 109 region = null; 110 for (var x = 0; x < selectionMask.Width; x++) 111 { 112 for (var y = 0; y < selectionMask.Height; y++) 113 { 114 if (0 == selectionMask.GetPixel(x, y).B) continue; 115 116 var rect = new Rectangle(x * 8, y * 8, 8, 8); 117 if (region == null) region = new Region(rect); 118 else region.Union(rect); 119 } 120 } 121 } 122 } 123 124 private void PictureBox1_Paint(object sender, PaintEventArgs e) 125 { 126 if (scaledPath == null) return; 127 e.Graphics.DrawPath(Pens.Red, scaledPath); 128 } 129 130 private void PictureBox2_Paint(object sender, PaintEventArgs e) 131 { 132 if (region == null) return; 133 134 var colorref = new COLORREF(Color.Red); 135 var hdc = IntPtr.Zero; 136 var hbrush = IntPtr.Zero; 137 var hrgn = IntPtr.Zero; 138 139 try 140 { 141 hrgn = region.GetHrgn(e.Graphics); 142 hdc = e.Graphics.GetHdc(); 143 hbrush = CreateSolidBrush(colorref.colorref); 144 145 FrameRgn(hdc, hrgn, hbrush, 1, 1); 146 } 147 finally 148 { 149 if (hrgn != IntPtr.Zero) region.ReleaseHrgn(hrgn); 150 if (hbrush != IntPtr.Zero) DeleteObject(hbrush); 151 if (hdc != IntPtr.Zero) e.Graphics.ReleaseHdc(hdc); 152 } 153 } 154 155 [DllImport("gdi32")] private static extern IntPtr CreateSolidBrush(uint colorref); 156 [DllImport("gdi32")] private static extern bool FrameRgn(IntPtr hDC, IntPtr hRgn, IntPtr hBrush, int nWidth, int nHeight); 157 [DllImport("gdi32")] private static extern bool DeleteObject([In] IntPtr hObject); 158 159 [StructLayout(LayoutKind.Explicit)] 160 private struct COLORREF 161 { 162 [FieldOffset(0)] public uint colorref; 163 [FieldOffset(0)] public byte red; 164 [FieldOffset(1)] public byte green; 165 [FieldOffset(2)] public byte blue; 166 public COLORREF(Color color) : this() => (red, green, blue) = (color.R, color.G, color.B); 167 } 168 169 private class MyPictureBox : PictureBox 170 { 171 protected override void OnPaint(PaintEventArgs e) 172 { 173 e.Graphics.InterpolationMode = InterpolationMode.NearestNeighbor; 174 e.Graphics.PixelOffsetMode = PixelOffsetMode.Half; 175 base.OnPaint(e); 176 } 177 } 178 } 179}

アプリ動画

投稿2025/03/11 09:51

編集2025/03/13 11:31
TN8001

総合スコア9957

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

fana

2025/03/11 14:24

> アイコンエディタのような小さい画像向けならばドット境界は重要 個人的な用途としては,そういう場面がほとんどなのです…… 頂点の座標をN倍してのざっくりとした表示でも(表示が何も無いよりは全然マシなので)画素単位でのきっちりとしたやつの難易度が厳しい場合には選択肢としては有りかな,とは思います. Widen でいい感じのものが得られるのかどうか,ちょっと検討してみます.
TN8001

2025/03/11 15:55

>> アイコンエディタのような小さい画像向けならばドット境界は重要 > 個人的な用途としては,そういう場面がほとんどなのです…… なるほど。 ならきっちり出したいところですねぇ。 ドラッグ中はどっちが外か中かわからないから(のでいいんかな?)パスができた後に、点をつないだセル正方形をRegionにUnionしいていくとか? [正方形と直線の当たり判定について](https://teratail.com/questions/0eejhldh6q6ehu) 小さいサイズなら処理速度は気にならないとは思うが、Xorができなくなる(変になる)のが難点か? それとも常に線の右側に外形を出す(時計回りで円を描くと小さく反対だと大きくなる)ように決めうちでギザギザさせる??(これが「自前で頑張って」ですよね? あるいは確定したパスで実際に(画像サイズと同サイズの1色の画像を)クリップしてしまって、その画像から表示する選択範囲を再作成するとか?(バカバカしいけど楽そうw
fana

2025/03/12 01:53

> RegionにUnion Regionを使うことはちょっと試したのですが, Regionのoutlineを描画する手段ってのが何故か無さそうなんですよね.Graphics に FillRegion() しかない.(ググると Win32 の世界まで潜っていけ,みたいな…) GraphicsPath の方ならば描画は Graphics.DrawPath() があるけど,こっちはこっちで union 的な操作が無い. Region → GraphicsPath 方向の変換があれば良いのだけど,これも無いっていう…
fana

2025/03/12 01:59

> ドラッグ中 でも,例えばそこまでに得られた座標群だけを GraphicsPath.AddPolygon() に食わせてやれば,まぁ「なんとなく」な領域情報は得られるかな?とか思っていますが,要はそれを描画する手段に困っているわけです. 現在の検討状況で言えば, 【 GraphicsPath.AddPolygon() の結果を描画したいけど,拡大表示している際に質問文内の図のようにちゃんと画素を囲む形に描画する方法がわからん!】 という話ですね.
TN8001

2025/03/12 11:16

なるほどぉ。。。一筋縄ではいかんのですね^^; > (ググると Win32 の世界まで潜っていけ,みたいな…) FrameRgnですかね? [c# - How can I draw the outline of a region with a dashed line - Stack Overflow](https://stackoverflow.com/questions/34017908) 雑に試してみました。 実線でよければ十分使えそうな感じはします。実線でよければw GdipWindingModeOutlineも試しましたがこっちは全然ダメでした。 WPFならもうちょっとなんかありそうなんだがなぁ...
fana

2025/03/14 01:36

うーん,ただマウスでえいやっと囲んだ際に「おまえさんが選択した範囲はココですぜ」っていう表示がしたいだけなのに,何故ここまで厄介なのか…… 「これもうドット境界にそろってなくても,ある程度わかればいいかなぁ」という妥協方向に心が揺らぎ中です^^ (あるいは「点線で囲む」という素敵な表示形態は諦めて,実現が容易な別の提示方法でやるとか)
TN8001

2025/03/14 03:07

> 表示がしたいだけなのに,何故ここまで厄介なのか…… ありがちですよねーw 昔のMSペイントとかってどんなんでしたっけ? なんか不定形の選択範囲がうにょうにょ動いていたような記憶があるんですが、何かのフリーソフトと混同してるかなぁ? GDI/GDI+にないなら、昔からBounding Boxだけだったんかなぁ。 > 妥協方向に心が揺らぎ中です^^ FrameRgnはお気に召さなかったですか^^; バカバカしいですが今のマシンパワーなら、256・512サイズくらいなら一瞬かなって気はします(未確認ですが) 「実線がなぁ」ってことなら市松模様で抜くくらいですかねぇ... > (あるいは「点線で囲む」という素敵な表示形態は諦めて,実現が容易な別の提示方法でやるとか) 「半透明の背景色を重ねる」みたいな手は大いにありですよね^^(どんな絵にも対応できる色やハッチパターンを試すのが大変そうですがw
fana

2025/03/14 03:39 編集

> 昔のMSペイント Win10までのやつは BoundingBox のみなんですよ.で,ドット絵レベルの作業してると「囲えたのか囲えてないのかわかんねぇよ」っていうのが不便だなぁ,とずっと思ってました. > FrameRgnは 「Win32の世界にGO!」っていうのがなんとなく嫌なのですが,いい感じの代替手段が見つからないようであれば,候補のひとつではあるかな,とは思います. (多分,「線をいい感じにするためのブラシ」とかの探索が必要なのかなーとか) 代替というのは,例えば「画素の外周」という形態じゃなくて「外周の画素」みたいな形態ならば「範囲の表示としては見られるものになって 且つ 実装が簡単」とかであれば,そっちでもいいかなー,みたいな. 「外周の画素」なら,Graphics.Scaletransform() と DrawPath() あたりでいけんかなーとか思ったのだけど,これ,「描画結果をスケール変換」ではなくて,「座標をスケール変換してから描画」なんですよね… そうじゃねぇよ,っていう.
TN8001

2025/03/14 04:19

> Win10までのやつは BoundingBox のみなんですよ そうでしたか。じゃあなんかほかのソフトだったようですね(アイコンですらもう何年も描いてないです^^; > 「Win32の世界にGO!」っていうのがなんとなく嫌なのですが そういう意味でしたか。 まあC#デスクトップは「最終的にはWin32」を避けれませんねw > 代替というのは,例えば「画素の外周」という形態じゃなくて「外周の画素」みたいな形態ならば「範囲の表示としては見られるものになって 且つ 実装が簡単」 ふむぅ。 何にしろ嘘をつかれるのが一番イヤですから、Clip結果が正義というか「それを元にどうこうする」方針が安心安全な気がします^^
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだベストアンサーが選ばれていません

会員登録して回答してみよう

アカウントをお持ちの方は

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.32%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問