DOBON.NET DOBON.NETプログラミング掲示板過去ログ

pictureboxに描画した複数の線のうち1つだけ消したい

環境/言語:[visual basic2005]
分類:[.NET]

はじめまして。
PictureBox上にbmp画像を表示し、その画像上に任意の直線や矩形などを
描画し、後でその直線や矩形を移動したり消去したりしたいのですが、
その方法がわかりません。
backColorで塗ってしまうとbmp画像も一緒に消えてしまうので、他の方法を
考えているのですが。。。
どなたかお分かりになる方、よろしくお願いいたします。
> PictureBox上にbmp画像を表示し、その画像上に任意の直線や矩形などを
> 描画し、後でその直線や矩形を移動したり消去したりしたいのですが、
> その方法がわかりません。

できません。
再描画するほかないです。
2007/05/18(Fri) 23:32:05 編集(投稿者)
2007/05/18(Fri) 23:31:47 編集(投稿者)

以下、素朴な疑問です。
非難してるわけじゃないので気にしないでください>元登校者の人

たまに見かけるタイプの質問ですが、どうして「一度描いてしまったものを(無条件に?)元に戻せる」と思えるんでしょうね?

現実世界においても、そんな可逆的な操作のできるものなんて珍しいんじゃないかと思うんですが、どーしてプログラミングしている時だけそういう(自分にとって?)都合のいい発想が出来るんだろうと、いつも不思議に思います。
■No19707に返信(渋木宏明(ひどり)さんの記事)
> たまに見かけるタイプの質問ですが、どうして「一度描いて
> しまったものを(無条件に?)元に戻せる」と思えるんでしょうね?
実際の処理はブラックボックスで、結果だけ見てしまい、
しかもブラックボックスの中で行われているような基本的な
処理の知識がなく、実際の処理を想像できないからでは?

エンドユーザさんには多いですねw
開発者がそれでは困るのですが、質問掲示板で質問する側は
学生さんも多いでしょうし、そんなもんだと思ってます。
そーゆー突飛な発想から何かが生まれることもありますし、
 # ってゆーかビギナーズラック?
…そんなに嘆くことでもないかと。

OS の処理でも表面上だけ見ると、Window が重なって、重
なった Window を消して…って実際は再描画されまくって
るのに、そのことを知らない人は多いと思います。

> 再描画するほかないです。
ボクもそう思います。

どこまで再描画するか?再描画の範囲を最小限にする工夫は
いくつかあるでしょうが、ディスプレイは平面です。1つの
点には1つの色情報しか覚えられません。上から何かを書けば、
今までの情報は消えてしまいます。もとに戻したいなら、その
情報でもう一度上書きする必要があります。
2007/05/19(Sat) 11:33:00 編集(投稿者)

> 実際の処理はブラックボックスで、結果だけ見てしまい、
> しかもブラックボックスの中で行われているような基本的な
> 処理の知識がなく、実際の処理を想像できないからでは?

背景としてはそういうことだと思うんですが。。。

でも、紙にしたって黒板にしたって、「一度書いたものを特定部分だけ都合よく消す(あまつさえ、直前に描かれたものが自動的に復旧される)」なんてことはできないじゃないですか。

そういう現実世界においてさえ「できない」のがむしろ当たり前の操作が、どうして対象がピクチャボックスやらビットマップの時にだけ「できる」と思えるのかが不思議なんですよね。
私はむしろ 渋木宏明さんの発想の方が不可解に思いますけどね。

恐らく、古典的なビットマップスクリーンをイメージされた上での質問者の質問に対する
「疑問」なんでしょうが、GDI+のように物理的なハードウェアを隠蔽した仮想的な環境に対して
このような古典的な物理デバイスのイメージを素直に重ねることの方が余程不思議思考でしょう。

GDI+が描画系ソフトのレイヤーに類する機能を提供している可能性を想定するのは
むしろ自然なことに思いますがね。
質問者の意図はそのように解釈可能でしょう。

もちろん、実際にはGDI+にそのような機能は用意されていないわけですが。

しかし、この方の回答ってさわやかで好意が持てるものが多いですね。
もちろん反語的に読んでいただきたいのですが。


>アキラさん
上記のようにGDI+ではレイヤーや、昔のゲーム機のスプライトに相当するような
機能は用意されておらず、自前で実装する必要があります。

具体的には最終的な描画面と同じサイズのBitmap(裏画面)を作って、
この裏画面に対して「下方のレイヤー」の画像から順番に描画していき、
すべてのレイヤーを裏画面に対して描画し終わった後でこの裏画面の内容を
最終的な描画面(PictureBox)に描画します。

何れかのレイヤーの内容に変更があった場合はこの手順を最初からやり直します。
>質問者の意図はそのように解釈可能でしょう。

いや、だからどうして描画先がスクリーンやビットマップの時だけ一足飛びにそこまでいっちゃんでしょう?

質問者が Phtoshop なんかをいじっていて、既にレイヤーやそれに類似する実装を目にしていれば、それとの対比で「描画したもの選択的に削除する」ことを求めるのは分かります。

ですが、質問内容からそういった印象を持てない場合がほとんどです。てか、既に実例を目にしたことがあるなら、質問の内容自体もう少し異なったものになるんじゃないでしょうか。
話が別の方向へ進んでいるようですので、基の質問と関係が薄い議論は、お気楽掲示板のほうでお願いいたします。
■No19717に返信(管理人さんの記事)
> 話が別の方向へ進んでいるようですので、基の質問と関係が薄い議論は、お気楽掲示板のほうでお願いいたします。
こんにちは。基の質問と関係ありなら、続行してもいいかなと思い投稿しま〜す
自分でも、今回はじめて移動・消去・imageに固定、というデモを作ったので(何とか思い通りになりました)折角ですから、入門の方むけの参考になればと

VB2005で、Buttonを4個、300x300のPictureBox1、1個のListBox1をForm1に適当に配置してください。

Public Class Form1
Private exe_mode As Integer = 0
Private seled_idx As Integer
Private g_pic1 As Graphics
Private stP, edP As Point
Private befP As Point

Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
PictureBox1.Image = New Bitmap(PictureBox1.ClientRectangle.Width _
, PictureBox1.ClientRectangle.Height)
Dim g As Graphics = Graphics.FromImage(PictureBox1.Image)
g.Clear(Color.Black)
g.FillPie(Brushes.DarkGreen, -150, -150, 400, 400, 0, 90)

g_pic1 = PictureBox1.CreateGraphics()
Button1.Text = "直線追加" 'exe_mode = 1, 11
Button2.Text = "移動" 'exe_mode = 2, 21
Button3.Text = "削除"
Button4.Text = "永久固定"
ListBox1.Items.Add("選択解除")
End Sub

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
exe_mode = 1
End Sub
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
If (0 < seled_idx) Then exe_mode = 2
End Sub
Private Sub Button3_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button3.Click
If (0 < seled_idx) Then
ListBox1.Items.RemoveAt(seled_idx)
End If
End Sub
Private Sub Button4_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button4.Click
dr_all_lines(Graphics.FromImage(PictureBox1.Image), -1)
PictureBox1.Refresh()
ListBox1.Items.Clear()
ListBox1.Items.Add("選択解除")
End Sub
Private Sub PictureBox1_MouseDown(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles PictureBox1.MouseDown
Dim str1 As String
If (exe_mode = 1) Then exe_mode = 11 : stP = e.Location
If (exe_mode = 2) Then
exe_mode = 21 : befP = e.Location
str1 = ListBox1.Items.Item(seled_idx)
stP.X = Mid(str1, 1, 4) : stP.Y = Mid(str1, 5, 4)
edP.X = Mid(str1, 9, 4) : edP.Y = Mid(str1, 13, 4)
End If
End Sub
Private Sub PictureBox1_MouseMove(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles PictureBox1.MouseMove
Dim dx, dy As Integer
If (exe_mode = 11) Then
dr_all_lines(g_pic1, -1)
edP = e.Location
g_pic1.DrawLine(Pens.Wheat, stP, edP)
End If
If (exe_mode = 21) Then
dr_all_lines(g_pic1, seled_idx)
dx = e.X - befP.X : dy = e.Y - befP.Y
stP.X += dx : stP.Y += dy
edP.X += dx : edP.Y += dy
g_pic1.DrawLine(Pens.Red, stP, edP)
befP = e.Location
End If
End Sub
Private Sub PictureBox1_MouseUp(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles PictureBox1.MouseUp
Dim str1 As String
If (exe_mode = 11) Then
str1 = d4(stP) & d4(edP)
ListBox1.Items.Add(str1)
End If
If (exe_mode = 21) Then
str1 = d4(stP) & d4(edP)
ListBox1.Items.Item(seled_idx) = str1
End If
exe_mode = 0
End Sub

Private Sub dr_all_lines(ByVal g As Graphics, ByVal chg_col_idx As Integer)
PictureBox1.Refresh()
Dim str1 As String
Dim s1x, s1y, e1x, e1y As Integer
Dim pen1 As Pen
For ii As Integer = 1 To ListBox1.Items.Count - 1
If ii <> chg_col_idx Then pen1 = Pens.Wheat Else pen1 = Pens.Cyan

str1 = ListBox1.Items.Item(ii)
s1x = Mid(str1, 1, 4) : s1y = Mid(str1, 5, 4)
e1x = Mid(str1, 9, 4) : e1y = Mid(str1, 13, 4)
g.DrawLine(pen1, s1x, s1y, e1x, e1y)
Next
End Sub

Private Function d4(ByVal p As Point) As String
Return (Strings.Right(" " & p.X.ToString, 4) _
& Strings.Right(" " & p.Y.ToString, 4))
End Function

Private Sub ListBox1_SelectedIndexChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles ListBox1.SelectedIndexChanged
seled_idx = ListBox1.SelectedIndex
dr_all_lines(g_pic1, seled_idx)
End Sub
End Class

直線を描くときは、(始点で)左クリックしたまま移動する。クリックを離したら1つの直線が確定する、という仕様です。目で見えるように、ListBox1にその直線が追加されます。簡単なCADもどきです。違うところはimageに固定という可能性です、このあたりは実際にVB2005で動作を見てください。ソースも面白いことをしているところが多々あります
2007/05/30(Wed) 19:38:19 編集(投稿者)

■No19705に返信(アキラさんの記事)

しばらくこちらを見ていなかったので回答が遅くなりましたが、簡単にできます。
まず、PictureBoxのインスタンスを保持するクラスを作ります。

Public Class A
    Implements IDisposable
    Public ReadOnly Property Pict() As PictureBox
        Get
            Return m_pict
        End Get
    End Property
    Private bmp As Bitmap
    Private m_pict As New PictureBox
    Private backupBmp As Bitmap

    Public Sub BackupImage()
        If backupBmp IsNot Nothing Then backupBmp.Dispose()
        backupBmp = DirectCast(bmp.Clone, Bitmap)
    End Sub

    Public Sub RestoreImage()
        If backupBmp Is Nothing Then Return
        Dim bmpToDispose As Bitmap = bmp
        bmp = DirectCast(backupBmp.Clone, Bitmap)
        Pict.Image = bmp
        If bmpToDispose IsNot Nothing Then bmpToDispose.Dispose()
    End Sub

    Public Sub Dispose() Implements System.IDisposable.Dispose
        '必要な開放処理
    End Sub
End Class

bmpのインスタンスは適時生成して、これをPict.Imageに設定します。
「ここで画像をバックアップしておきたい」というときにBackupImageを呼び、
「さっきの画像に戻したい」というときにRestoreImageを呼びます。
あとは、描画のためのメソッド(例えば、bmpから生成したGraphicsに対して
DrawLineを呼ぶ、等)を使いやすい形で用意してやればよいと思います。
最後に描いた線を消すケースを無意識に想定していましたが、任意の線を消すとなると上の方法だけではできませんでした。レイヤー構造を自力で実装するという方法もありますが、再描画よりかえって煩雑になりそうです。線を描くのはそれほど重い処理ではありませんから、背景画像(あるいは、「この時点より前に戻ることはない」と確定してバックアップした画像)に戻って再描画するのが自然だろうと思います。
簡単かどうかは別にして、要素の選択と言う部分では、メタファイルを使って
実現させたことがあります。Windowsメタファイルってやつです。
最近、その手の情報は少ないですが、CAD的に扱えますし、描画されている
個々のオブジェクトを要素として選択も可能です。

後は、PictureBoxのデバイスコンテキストに、仮想空間としてのメモリデバイ
スコンテキストから描画要素を書くと言う動作でしょう。一般にはダブルバッ
ファリングとか言われていますが、メモリデバイスコンテキスト空間にメタフ
ァイルを描画し、それをPictureBoxのデバイスコンテキストに描画すれば、ち
らつきの少ない描画が可能です。

ダブルバッファを実現させる前に、メタファイルをPictureBoxに描画させる方
を実現できれば、後は、要素の選択をどのように行うかです。

技術的には当然可能な話ですので、頑張ってください。

http://msdn2.microsoft.com/ja-jp/library/zbk7dbtb(VS.80).aspx
一応、.NET Framework 2.0 でサポートされていますが、私が実現させた折に
は、.NETは無く、全てAPIで書きましたので、必要な全ての機能が.NETの
クラスにあるかどうかは未確認です。

以上。

DOBON.NET | プログラミング道 | プログラミング掲示板