Question Faster Paint Software

Joined
Sep 15, 2011
Messages
17
Programming Experience
3-5
Hello forum,

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?

Please help!
 

JohnH

VB.NET Forum Moderator
Staff member
Joined
Dec 17, 2005
Messages
15,334
Location
Norway
Programming Experience
10+
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.
 
Joined
Sep 15, 2011
Messages
17
Programming Experience
3-5
So should I use the "Invalidate" method instead of?
And where should it be called from?
 

JohnH

VB.NET Forum Moderator
Staff member
Joined
Dec 17, 2005
Messages
15,334
Location
Norway
Programming Experience
10+
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).
 
Joined
Sep 15, 2011
Messages
17
Programming Experience
3-5
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?
 

JohnH

VB.NET Forum Moderator
Staff member
Joined
Dec 17, 2005
Messages
15,334
Location
Norway
Programming Experience
10+
Would that make any difference? No, not in any way.
Besides, that would directly conflict with how control painting works.
 
Joined
Sep 15, 2011
Messages
17
Programming Experience
3-5
But if I don't update the control so often, wouldn't that make the program slower to draw my path onto the image?


... instead of Refresh you should pursue greater control of which parts that needs to Invalidate ...
How can I invalidate the parts that I actually need to invalidate? Would a timer make it better or worse?
 

JohnH

VB.NET Forum Moderator
Staff member
Joined
Dec 17, 2005
Messages
15,334
Location
Norway
Programming Experience
10+
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.
 
Joined
Sep 15, 2011
Messages
17
Programming Experience
3-5
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?


Code:
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 :(
 

JohnH

VB.NET Forum Moderator
Staff member
Joined
Dec 17, 2005
Messages
15,334
Location
Norway
Programming Experience
10+
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.
 

JohnH

VB.NET Forum Moderator
Staff member
Joined
Dec 17, 2005
Messages
15,334
Location
Norway
Programming Experience
10+
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.
 
Joined
Sep 15, 2011
Messages
17
Programming Experience
3-5
Wow, thanks a lot mate!
I will try this software using our SmartBoard tomorrow, and analyze it's performance!

How can I actually include an "Undo" function?
I have set the KeyPreview of the form to true, but what code do I insert, to actually undo? :)

Thanks alot!
 

JohnH

VB.NET Forum Moderator
Staff member
Joined
Dec 17, 2005
Messages
15,334
Location
Norway
Programming Experience
10+
How can I actually include an "Undo" function?
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.
 
Joined
Sep 15, 2011
Messages
17
Programming Experience
3-5
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.

Have you any ideas? :crushed:
 
Joined
Sep 15, 2011
Messages
17
Programming Experience
3-5
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 :(
 

JohnH

VB.NET Forum Moderator
Staff member
Joined
Dec 17, 2005
Messages
15,334
Location
Norway
Programming Experience
10+
Now that you have a minimum amount of graphics operations, which is also limited to only the current drawing shapes bounds, try to remove the timer and call UpdateCurrent from MouseMove.
For the record I measured at most 16ms average updates with the timer with that code, that translates to 60 fps, and 8ms average (125 fps) with direct updating each move event.

Also bear in mind, a release build not hosted in debugger will generally execute faster.

By the way, example was updated with a bug-fix, MouseMove and MouseUp now checks for the 'current' object reference instead of left mouse button.
 
Joined
Sep 15, 2011
Messages
17
Programming Experience
3-5
So, I have been working a bit on some code, and I came up with the idea to "predict" the users movement direction.
So I would first have to determine the direction of the mouse, and then add some pixels to the direction.

My code is kinda working, but not exactly:

Code:
Sub AddPath(ByVal LastPoint As PointF, ByVal CurrentLocation As Point)        'Get direction
        Dim xloc As Integer = 0
        Dim yloc As Integer = 0
        Dim intMultiplier = 20


        If System.Math.Abs(LastPoint.X - CurrentLocation.X) > System.Math.Abs(LastPoint.Y - CurrentLocation.Y) Then
            'Change in X is greater, now find Left or Right
            If (LastPoint.X - CurrentLocation.X) < 0 Then
                lblAbout.Text = "Right"
                xloc = -(intMultiplier)
            Else
                lblAbout.Text = "Left"
                xloc = intMultiplier
            End If
        Else
            'Change in Y is greater, now find Up or Down
            If (LastPoint.Y - CurrentLocation.Y) < 0 Then
                lblAbout.Text = "Down"
                yloc = -(intMultiplier)
            Else
                lblAbout.Text = "Up"
                yloc = intMultiplier
            End If
        End If


        mousePath.AddLine(mousePath.GetLastPoint.X, mousePath.GetLastPoint.Y, CurrentLocation.X - xloc, CurrentLocation.Y - yloc)
    End Sub
Am I doing something wrong, or is there maybe a better way of doing this?
(I'll keep trying!)

PS. See the attachment! :)
screen222.png
 
Top Bottom