none
VB2008 用 Graphics 畫圖, 游標速度快時, 讓圓變多邊形? RRS feed

  • 問題

  • 我使用 Visual Basic 2008, 使用 Bitmap 及 Graphics 等指令寫一個用滑鼠或繪圖板的塗鴨程式, 但發現有個問題, 只要滑鼠的動作快一點, 本來手畫一個圓的形狀, 會變成一個多邊形而不是我畫的圓.
    不知大大們有什麼方法可以解決?
    這是我主要的程式說明.

    Dim bmp1 As New Bitmap(800, 600)
    Dim G1 As Graphics = Graphics.FromImage(bmp1)

    1. 以 Mouse_Down 做為開始畫圖, 並紀錄滑鼠的起始座標.
    old.X=e.X
    old.Y=e.Y

    2. 以 Mouse_Move 繪製上個座標到目前座標的 DrawLine
    Current.X=e.X
    Current.Y=e.Y
    G1.DrawLine(Pen, old.X, old.Y, current.X, current.Y)
    Me.PictureBox1.Image=bmp1
    Old.X=Current.X
    Old.Y=Current.Y

    3. 並以 PictureBox1_Paint 做為重繪原圖之事件
    Me.PictureBox1.Image=bmp1

    可能因 事件 Mouse_Move 程式跑的不夠快, 導致快速手畫一個大圓後, 繪圖出來的圖, 卻是一個 不規則多邊形.
    使用小畫家或繪圖板的手寫程式測試時, 並不會因為速度快而把畫圓變成畫多邊形, 它們都可以把游標經過的 Point 紀錄並畫出.

    用滑鼠塗鴨時, 於線的轉角處 (G1.DrawLine(Pen, old.X, old.Y, current.X, current.Y)), 都會有缺角或呈現像踞齒狀, 使用 G1.SmoothingMode = Drawing2D.SmoothingMode.HighQuality 這個功能有改善但沒有完全解決問題.

    請問:
    1. 如何能像小畫家那樣游標再快, 都不會把圓畫成多邊形?
    2. PictureBox1.Paint 會讓 CPU 跑 50% (2 Core), 如果是單顆 CPU 則跑滿百 (小畫家 CPU 不會跑滿百), 這更讓 Mouse_Move 的處理速度下降, 不知如何解決?
    我用 System.Threading.Thread.Sleep(30) 可稍為改善, 但用到單顆 CPU 的電腦時, 除 Sleep 的時間必需變長外, 晝線會更差.
    3. 滑鼠塗鴨時, 如何讓線的轉角處 (G1.DrawLine(Pen, old.X, old.Y, current.X, current.Y)), 不會有缺角或呈現像踞齒狀的情形 (線越粗越明顯)?
    4. 繪圖板的筆壓效果, 如何用程式取得? (我用的是 XP-Pen_XP-3300A)

    有點困擾我, 請知道的大大不吝賜教. 感激不盡!!!
    2009年8月27日 下午 02:16

解答

  • 樓主你好:
    在此對繪圖的效果,提醒一點你在畫線的 Pen 。

    設定             Dim p As New Pen(Color.Black)

                       p.StartCap = Drawing2D.LineCap.Round  ' 設定圓形線條的端點

                      G1.DrawLine(P, old.X, old.Y, current.X, current.Y)

    如上做法可以試試,這樣線條看起來比較不會有一格一格,框框角角的感覺。

    再來樓主是否有想過,當需要簽名時再原簽名位置上,動態新增一個空白 PictureBox ,而之後再把動態的簽名圖繪製到原圖上,而不是直接對原圖做繪製


    • 已標示為解答 Lolota Lee 2009年9月3日 上午 06:09
    2009年8月31日 上午 06:46
  • To Kevin 大大,

    用了 P.StartCap = Drawing2D.LineCap.Round 後, 確實讓線條更像 Pen 了.

    至於 CPU 飆高的原因, 皆因 PictureBox1_Paint 的因素, 因為我發現 Me.PictureBox1.Image = bmp1 即可以有 AutoRedraw 的效果, 所以我已經將 Paint 的事件棄置, 直接在 Mouse_Move 的事件中, 加上 Me.PictureBox1.Image = bmp1 即可.
    在沒有畫線時, 可說是完全沒有佔用 CPU 資源.
    但是畫線時, CPU 還是飆高, 也造成 Mouse_Move 的處理速度下降, 畫圓又會變成多邊形, 速度愈快愈明顯.

    其實我想到了一個退而求其次的方法, 因為想想自己, 任拿一支樹枝 / 筷子 / 沒水的筆等, 只要眼睛看著筆頭, 還是可以漂亮的簽出自己的名字,
    所以我在 Mouse_Down 及 Mouse_Move 時, 只用 G1.DrawLine(Pen, old.X, old.Y, current.X, current.Y) 畫線, 因為 Graphics 本就可以記憶畫過的圖, 所以在 Mouse_Up 時, 再用 Me.PictureBox1.Image = bmp1 於畫面顯示上一筆劃的圖, 這樣在 CPU 等級較差的 P4_2G, 還是可以畫出不錯的圓了, 只不過最大的缺點就是, 我們在簽名時, 通常都是一個筆劃就是一個字, 一般人可能會不習慣.

    程式中, 我把 OldX, CurrentX 等 Point 改成 Location, 也許可以減少一個指令, 加快處理速度.

    以下是我只要修改的程式, 供大大參考之:
      Private Sub PictureBox1_MouseDown(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles PictureBox1.MouseDown

            myMouseKeyed = True
            G1.SmoothingMode = Drawing2D.SmoothingMode.HighQuality
            G1.CompositingQuality = Drawing2D.CompositingQuality.HighQuality
            P.StartCap = Drawing2D.LineCap.Round
            myOldLocation = e.Location
            '利用座標的上下左右來畫一個點, 避免只有 Down 沒有 Move 時, 螢幕看不出有一個點
            Dim myPoint As Point() = {New Point(e.X - 1, e.Y - 1), New Point(e.X + 1, e.Y + 1), New Point(e.X + 1, e.Y - 1), _
                                    New Point(e.X - 1, e.Y - 1), New Point(e.X - 1, e.Y + 1), New Point(e.X, e.Y), New Point(e.X - 1, e.Y - 1)}
            G1.DrawCurve(P, myPoint)
        End Sub

        Private Sub PictureBox1_MouseMove(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles PictureBox1.MouseMove

            If myMouseKeyed Then
                Me.G1.DrawLine(P, myOldLocation.X, myOldLocation.Y, e.X, e.Y)
                myOldLocation = e.Location
            End If

        End Sub

        Private Sub PictureBox1_MouseUp(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles PictureBox1.MouseUp

            myMouseKey = False
            Me.PictureBox1.Image = bmp1

        End Sub

    謝謝大大們的指導.

    • 已標示為解答 Lolota Lee 2009年9月3日 上午 06:09
    2009年9月2日 下午 01:44

所有回覆

  • ...
    你這種程式在小畫家算是用鉛筆畫圓...
    請用:Graphics.DrawEllipse 畫圓。
    論壇是網友平等互助 保證解答請至 微軟技術支援服務
    2009年8月27日 下午 04:15
  • hi
    你可以參考open source Paint.NET(C#)
    http://www.getpaint.net/ 
    http://www.dotblogs.com.tw/ricochen/Default.aspx
    2009年8月27日 下午 04:25
  • To 心冷熱情熄大大,
    我不是要畫圓或橢圓, 而是要配合手寫板 (或繪圖板) 來做電子簽名用, 因為簽的字不夠 Smoothing, 所以持續再找原因.
    且上列的程式在 P4_2G 的電腦上跑時, Smoothing 的效果無法接受, 2Core CPU_2G 的簽字效果尚可接受.

    其實用小畫家畫任意線時, 也看得出是由較短的 直線 所連接出來的任意線, 但還在可接受範圍, 我的 VB2008 程式卻讓我無法接受.
    謝謝您的回覆.
    2009年8月28日 下午 01:50
  • To Ricoisme 大大,

    您提供的網站上, 好像找不到 Source Code for Paint.Net, 在 Google 上好像也找不到 VS2008 能跑的 Source Code.
    不知您是否知道 VS2008 能跑的 Source Code?

    謝謝您提供這個程式.
    2009年8月28日 下午 02:09
  • hi
    剛去user blog觀看    發現已不提供source code了
    但還有之前的版本

    http://www.afterdawn.com/software/source_codes/paint.net.cfm

    但須確認系統的最低需求 

    System Requirements

    Minimum System

    • Windows XP (SP2 or newer),
         or Windows Vista,
         or Windows 7,
         or Windows Server (2003 SP1 or newer)
    • .NET Framework 3.5 SP1 (free download from Microsoft)
    • 256 MB of RAM (Recommended: 512 MB or more)
    • 1024 x 768 screen resolution
    • 200+ MB hard drive space
    • 64-bit mode requires a 64-bit CPU and a 64-bit edition of Windows

    http://www.dotblogs.com.tw/ricochen/Default.aspx
    2009年8月28日 下午 04:53
  • To Rico 大大,

    Paint.Net V3.05 好像不能跑 VS2008, 謝謝您費心的解答.

    為了讓 Mouse_Move 的速度更快, 我將 G1.DrawLine(Pen, old.X, old.Y, current.X, current.Y) 移至 PictureBox1_Paint 去畫線, Mouse_Move 只單純的紀錄游標的座標, 並先把不必要的底圖去掉, 待簽完名後, 再將簽名貼上底圖, 減少 Re-Paint 的數量, 這樣感覺速度有變快一點點點.

    不過這樣的寫法如果加上 System.Threading.Thread.Sleep(x) 來減低 CPU 的負擔時, 畫線的效果又會更差, 所以必須讓 CPU 跑滿百才能行.

    有一好沒兩好???

    持續找答案中, 謝謝.
    2009年8月29日 上午 03:33
  • hi
    要正常開啟專案是否有注意到這點呢??
  • 64-bit mode requires a 64-bit CPU and a 64-bit edition of Windows

  • http://www.dotblogs.com.tw/ricochen/Default.aspx
2009年8月29日 上午 04:30
  • 樓主你好:
    在此對繪圖的效果,提醒一點你在畫線的 Pen 。

    設定             Dim p As New Pen(Color.Black)

                       p.StartCap = Drawing2D.LineCap.Round  ' 設定圓形線條的端點

                      G1.DrawLine(P, old.X, old.Y, current.X, current.Y)

    如上做法可以試試,這樣線條看起來比較不會有一格一格,框框角角的感覺。

    再來樓主是否有想過,當需要簽名時再原簽名位置上,動態新增一個空白 PictureBox ,而之後再把動態的簽名圖繪製到原圖上,而不是直接對原圖做繪製


    • 已標示為解答 Lolota Lee 2009年9月3日 上午 06:09
    2009年8月31日 上午 06:46
  • To Kevin 大大,

    用了 P.StartCap = Drawing2D.LineCap.Round 後, 確實讓線條更像 Pen 了.

    至於 CPU 飆高的原因, 皆因 PictureBox1_Paint 的因素, 因為我發現 Me.PictureBox1.Image = bmp1 即可以有 AutoRedraw 的效果, 所以我已經將 Paint 的事件棄置, 直接在 Mouse_Move 的事件中, 加上 Me.PictureBox1.Image = bmp1 即可.
    在沒有畫線時, 可說是完全沒有佔用 CPU 資源.
    但是畫線時, CPU 還是飆高, 也造成 Mouse_Move 的處理速度下降, 畫圓又會變成多邊形, 速度愈快愈明顯.

    其實我想到了一個退而求其次的方法, 因為想想自己, 任拿一支樹枝 / 筷子 / 沒水的筆等, 只要眼睛看著筆頭, 還是可以漂亮的簽出自己的名字,
    所以我在 Mouse_Down 及 Mouse_Move 時, 只用 G1.DrawLine(Pen, old.X, old.Y, current.X, current.Y) 畫線, 因為 Graphics 本就可以記憶畫過的圖, 所以在 Mouse_Up 時, 再用 Me.PictureBox1.Image = bmp1 於畫面顯示上一筆劃的圖, 這樣在 CPU 等級較差的 P4_2G, 還是可以畫出不錯的圓了, 只不過最大的缺點就是, 我們在簽名時, 通常都是一個筆劃就是一個字, 一般人可能會不習慣.

    程式中, 我把 OldX, CurrentX 等 Point 改成 Location, 也許可以減少一個指令, 加快處理速度.

    以下是我只要修改的程式, 供大大參考之:
      Private Sub PictureBox1_MouseDown(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles PictureBox1.MouseDown

            myMouseKeyed = True
            G1.SmoothingMode = Drawing2D.SmoothingMode.HighQuality
            G1.CompositingQuality = Drawing2D.CompositingQuality.HighQuality
            P.StartCap = Drawing2D.LineCap.Round
            myOldLocation = e.Location
            '利用座標的上下左右來畫一個點, 避免只有 Down 沒有 Move 時, 螢幕看不出有一個點
            Dim myPoint As Point() = {New Point(e.X - 1, e.Y - 1), New Point(e.X + 1, e.Y + 1), New Point(e.X + 1, e.Y - 1), _
                                    New Point(e.X - 1, e.Y - 1), New Point(e.X - 1, e.Y + 1), New Point(e.X, e.Y), New Point(e.X - 1, e.Y - 1)}
            G1.DrawCurve(P, myPoint)
        End Sub

        Private Sub PictureBox1_MouseMove(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles PictureBox1.MouseMove

            If myMouseKeyed Then
                Me.G1.DrawLine(P, myOldLocation.X, myOldLocation.Y, e.X, e.Y)
                myOldLocation = e.Location
            End If

        End Sub

        Private Sub PictureBox1_MouseUp(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles PictureBox1.MouseUp

            myMouseKey = False
            Me.PictureBox1.Image = bmp1

        End Sub

    謝謝大大們的指導.

    • 已標示為解答 Lolota Lee 2009年9月3日 上午 06:09
    2009年9月2日 下午 01:44
  • To Kevin 大大,

    少回覆一個大大的疑問,
    我已經用空白的 Bitmap & Graphics 來簽名後, 再貼上底圖, 即使用空白的圖來簽名, 效果還是不滿意.

    再次謝謝各位大大的解答, 謝謝.
    2009年9月2日 下午 01:52
  •  現在將我這幾週來所獲得的成果提供給大家, 也將此主題做個結局.

    修改我原先的程式:
    Dim bmp1 As New Bitmap(800, 600)     '原有的
    Dim G1 As Graphics = Graphics.FromImage(bmp1)    '原有的
    Dim G2 As Graphics    '新增的

    Form_Load 事件中加入.
    G2 = Me.PictureBox1.Creategraphics    '新增的

    1. 以 Mouse_Down 做為開始畫圖, 並紀錄滑鼠的起始座標.
    old.X=e.X    '原有的
    old.Y=e.Y    '原有的

    2. 以 Mouse_Move 繪製上個座標到目前座標的 DrawLine
    Current.X=e.X    '原有的
    Current.Y=e.Y    '原有的
    G1.DrawLine(Pen, old.X, old.Y, current.X, current.Y)    '原有的, 畫在 bitmap 上
    G2.DrawLine(Pen, old.X, old.Y, current.X, current.Y)    '新增的, 畫在螢幕上
    Old.X=Current.X    '原有的
    Old.Y=Current.Y    '原有的

    Me.PictureBox1.Image=bmp1    '原有的, 在 Mouse_Move 事件中刪除, 這是 CPU 跑滿百的原兇.

    3. 並以 PictureBox1_Paint 做為重繪原圖之事件    '不在 Paint 的事件中重畫圖 (刪除此事件)
    Me.PictureBox1.Image=bmp1     '原有的, 在 Paint 事件中刪除


    4. 在 Form_DeActive 的事件中, 加入 bitmap 的重畫指令
    Me.PictureBox1.Image=bmp1    '新增的, DeActive 時, 不會有畫圖的需求, 所以對畫圖完全沒有影響

    做了上述的修改後:
    1. 畫的圖幾乎與小畫家相同了.
    2. CPU 不會跑滿百, 都只有個位數而已, 而且連 P4_2G 的 CPU 一樣可以畫的很漂亮.
    3. 線的轉角處會有缺角的問題, 使用 "Kevin 維大大" 的提醒, 加入 P.StartCap = Drawing2D.LineCap.Round, 可以獲得明顯的改善.
    4. 繪圖板的筆壓, 目前無解.

    我這個問題, 在此應該算是圓滿的解決了.

    特別感謝 "心冷熱情熄" 大大, "Kevin 維" 大大, "Ricoisme" 大大 熱情的指導, 謝謝.

    2009年9月16日 下午 01:20