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!
 
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?
 
Would that make any difference? No, not in any way.
Besides, that would directly conflict with how control painting works.
 
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?
 
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.
 
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!
 
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.
 
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:
 
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 :(
 
Back
Top