I have created a paint program, which I use with a SmartBoard to draw on.
But when I draw, it reacts much slower than a usual Paint Program (MSPaint).
I have a PictureBox which I draw on,to capture the mouse path.
Every time the mouse is moved, the path is added to the "mousePath" variable.
Then I use PictureBox.Refresh() to re-paint the PictureBox, and draw the mousePath.
Is there any way I can get a better reaction, and draw the path faster, than I do now?
It's almost 2 times slower than usual Paint? Maybe use DirectX or XNA?
MouseMove is raised every tiny pixel of movement, so refreshing whole control from this will be very slow. Faster updates can be achieved by Invalidate only the parts that is necessary, and Update only ever so often.
When you call Refresh method, internally that just calls Invalidate method (with no arguments to invalidate the whole control) then Update method which repaints the invalidated areas (and raises Paint event).
My suggestion is two-fold; instead of Refresh you should pursue greater control of which parts that needs to Invalidate, and find a way to not Update the control so often (each MouseMove event is too often).
But is it bad, that I call use the Refresh method to raise the Paint event, and first THEN draw on the image?
What If a drew directly from the MouseMove event, instead of raising the Paint event?
Yes, a timer is a good idea. For example a 50ms frequency when drawing may look like real time to user, but could save you hundreds of graphics operations per second.
50ms is a bit too laggy, If I just use the imgBoard.Invalidate() method, it draws the area way too slow, but at 10ms it works quite okay.
Then I did some research on the internet, and found out that the Invalidate method can Invalidate regions - could this make my code more effecient?
VB.NET:
Private Sub tmrUpdate_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles tmrUpdate.Tick
imgBoard.Invalidate(New Rectangle(mouseLoc.X - 5, mouseLoc.Y - 5, 10, 10))
End Sub
The mouseLoc is a Point Variable which is set on the MouseMove event
This doesn't work to well, though. It draws a dashed line
A lag could possibly be explained by the missing call to Update method.
You will get a dashed/partial line if you don't invalidate everything that is drawn. So what is drawn? That you can only know from the points given by MouseMove event.
GraphicsPath class may give you the opportunity to manage the input points and drawing better. If not, remember you can call Invalidate many times for several areas before you call Update.
I decided to post a very basic freehand Paint sample, that uses the techniques previously mentioned to economize graphics operations (fewer drawing operations, less parts invalidated). This sample invalidates the bounding rectangle of the GraphicsPath, which is more than is strictly necessary, but it is also a great simplification and requires a lot less method calls all in all. Using a GraphicsPath allow for interesting possibilities for adding other graphics shapes later.
Each method has a comment that explains the overall operation, the rest should be self-explanatory.
Public Class PaintSampleForm
'form setup in designer:
' property DoubleBuffered set to True
' a PictureBox named DrawingPictureBox
' a Timer named UpdateTimer, not enabled, Interval 20-50ms
'creates a new bitmap to draw on
Private Sub Form2_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
Dim sz = Me.DrawingPictureBox.ClientSize
Dim bmp As New Bitmap(sz.Width, sz.Height)
Using g = Graphics.FromImage(bmp)
g.Clear(Color.White)
End Using
Me.DrawingPictureBox.Image = bmp
End Sub
'example cache that can be used for undo/redo
Private paths As New List(Of Drawing2D.GraphicsPath)
'holds current drawing path
Private current As Drawing2D.GraphicsPath
'starts a new drawing path, starts timer for paint updating
Private Sub DrawingPictureBox_MouseDown(sender As Object, e As System.Windows.Forms.MouseEventArgs) Handles DrawingPictureBox.MouseDown
If e.Button = Windows.Forms.MouseButtons.Left Then
Me.current = New Drawing2D.GraphicsPath
Me.current.AddLine(e.Location, e.Location)
Me.UpdateTimer.Start()
End If
End Sub
'adds line segment to drawing path
Private Sub DrawingPictureBox_MouseMove(sender As Object, e As System.Windows.Forms.MouseEventArgs) Handles DrawingPictureBox.MouseMove
If current Is Nothing Then Return
Me.current.AddLine(Me.current.GetLastPoint, e.Location)
End Sub
'stops updating, updates current path and adds it to bitmap.
Private Sub DrawingPictureBox_MouseUp(sender As Object, e As System.Windows.Forms.MouseEventArgs) Handles DrawingPictureBox.MouseUp
If current Is Nothing Then Return
Me.UpdateTimer.Stop()
Me.UpdateCurrent()
Me.CurrentToBitmap()
End Sub
'adds current drawing to bitmap and puts it in cache
Private Sub CurrentToBitmap()
Using g = Graphics.FromImage(Me.DrawingPictureBox.Image)
g.SmoothingMode = Drawing2D.SmoothingMode.HighQuality
g.DrawPath(Pens.Black, Me.current)
End Using
'done with current, move it to cache
Me.paths.Add(Me.current)
Me.current = Nothing
End Sub
'updates painting
Private Sub UpdateTimer_Tick(sender As System.Object, e As System.EventArgs) Handles UpdateTimer.Tick
Me.UpdateCurrent()
End Sub
'invalidates current drawing path (by bounding rectangle) and updates painting
Private Sub UpdateCurrent()
Dim rct = Rectangle.Round(Me.current.GetBounds)
rct.Inflate(1, 1)
Me.DrawingPictureBox.Invalidate(rct)
Me.DrawingPictureBox.Update()
End Sub
'dynamically paints current drawing path on control
Private Sub DrawingPictureBox_Paint(sender As Object, e As System.Windows.Forms.PaintEventArgs) Handles DrawingPictureBox.Paint
If Me.current Is Nothing Then Return
e.Graphics.SmoothingMode = Drawing2D.SmoothingMode.HighQuality
e.Graphics.DrawPath(Pens.Black, Me.current)
End Sub
End Class
In a more full-scale Paint app the cache would of course also store information about colors and line widths and other usefulness for each drawing operation.
The cache in that sample contains all drawing operations, so to undo you would create a new Bitmap and draw all except the last one to it, then set that as PictureBox.Image instead.
So, I implemented the new engine, and it seemed to work better than the old, but still not as good as for example Microsoft Paint.
I think the way MS Paint does it, is that it draws/invalidates with low quality graphics, but as soon the user stops drawing, it re-paints it with the HighQuality property.
I edited the "engine" to first draw in HighSpeed, and then to redraw in HighQuality, but it still doesn't update as fast as MS Paint.
The path can't keep up with the cursor, it's like a millisecond behind, when for example MS Paint draws instantly?!
This might not be too noticeable for a user with a mouse, but when you draw on a touch interface, it's really annoying!
Do you please have any other suggestions? I have tried to change the timer interval, and play a bit with the code, but so far I don't have any results
This site uses cookies to help personalise content, tailor your experience and to keep you logged in if you register.
By continuing to use this site, you are consenting to our use of cookies.