Results 1 to 34 of 34

Thread: [RESOLVED] Slow graphics

  1. #1

    Thread Starter
    Hyperactive Member
    Join Date
    Apr 2010
    Posts
    350

    Resolved [RESOLVED] Slow graphics

    I'm drawing a lot of lines on a panel. As I move the mouse, I move my own cursor over the lines. When there is a lot of data my cursor is slow to respond - I assume it's because I'm drawing everything and my cursor.
    I am using double buffering.
    Can you suggest how I can make my cursor more responsive?

    Below are code snippets to show what I'm doing.

    Thanks

    Code:
    Private Sub MyPanel_MouseMove(sender As Object, e As System.Windows.Forms.MouseEventArgs) Handles Me.MouseMove
            Dim x As Single = e.X
            Dim y As Single = e.Y
    
            MyCursor.X = x
            MyCursor.Y = y
    
            Me.Refresh()
    
        End Sub
    Code:
    Private Sub MyPanel_Paint(sender As Object, e As System.Windows.Forms.PaintEventArgs) Handles Me.Paint
    
            eGraphics = e.Graphics
            ' draw lots of lines 
            Call DrawCursor()
    End Sub
    Code:
    Public Sub DrawCursor()
            eGraphics.DrawLine(Pens.Black, MyCursor.X, 0, MyCursor.X, frmMain.MyPanel1.Height)
            eGraphics.DrawLine(Pens.Black, 0, MyCursor.Y, frmMain.MyPanel1.Width, MyCursor.Y)
    End Sub

  2. #2
    PowerPoster boops boops's Avatar
    Join Date
    Nov 2008
    Location
    Holland/France
    Posts
    3,201

    Re: Slow graphics

    The first (and simplest) thing to try is to replace Refresh by Invalidate.

    Explanation: Refresh forces an immediate repaint. Since the Mouse events may be trying to fire at 64 hz. or so, there will be far more repainting taking place than necessary, and this will slow everything else down. Invalidate, on the other hand, places repainting on a queue so repainting may take place less often; it's a bit like using a separate thread to do the repainting.

    What is more, you are Refreshing the whole form instead of just the panel, and that will mean repainting all the other controls on the form too. So use Panel1.Invalidate instead. In some situations you can further improve repainting performance by specifying only the "dirty" rectangle of the control to be repainted.
    Code:
    control.Invalidate(rectangle)
    Occasionally I have found Refresh working better than Invalidate without understanding why. So if you are getting slow response to mouse moves, I suggest you try one then the other to see if it makes any difference.

    BB

  3. #3

    Thread Starter
    Hyperactive Member
    Join Date
    Apr 2010
    Posts
    350

    Re: Slow graphics

    Thanks, I tried replacing Refresh with Invalidate but it has made no apparent difference.

  4. #4
    Fanatic Member Spooman's Avatar
    Join Date
    Mar 2017
    Posts
    868

    Re: Slow graphics

    Dave

    It may help if you "trigger" the DrawCursor sub less frequently.

    Instead to triggering on every pixel (ie, X and Y), could you live with doing so
    every, say, 10 pixels? This could be accomplished by the following:

    1. save the X and Y coords each time you do DrawCursor
    2. compare "actual" cursor new X and Y coords with the "saved" ones
    3. if the "actual" coords are too close to saved ones, don't redraw.
    4. If the "actual" coords are greater that your "minimum" move, the do redraw, and resave the "used" coords.

    Spoo

  5. #5
    PowerPoster boops boops's Avatar
    Join Date
    Nov 2008
    Location
    Holland/France
    Posts
    3,201

    Re: Slow graphics

    Quote Originally Posted by DavidGraham167 View Post
    Thanks, I tried replacing Refresh with Invalidate but it has made no apparent difference.
    That's possible. You are drawing dynamically, so your drawing surface needs to be Double Buffered. If it's not, there will be a lot of flashing and jerking as you draw. I wonder if that's what you identify as slowness?

    Not that setting the form's DoubleBuffered property to True (or doing the same thing with SetStyle) doesn't help because it only applies to the form background, not to the controls. But there are several ways to get a double buffered control.

    The simplest way is to use a PictureBox instead of a Panel, bacause that is double buffered by default.

    Alternative ways to get a DoubleBuffered control are:
    - draw directly on the Form and set its DoubleBuffered property to True.
    - create a UserControl (VS menu Project/Add User Control), set its DoubleBuffered property to True, build the project, and use the UC instead of a Panel.
    - add a double buffered panel Class (VS menu Project/Add Class) to the project, code it like this, and build the project:
    Code:
    Public Class DBPanel
       Inherits Panel
       Public Sub New()
          Me.DoubleBuffered = True
       End Sub
    End Class
    Once you build the project, will find the UC or custom Panel in the Forms Toolbox, and you can add it in the designer and/or in code just like a normal panel.

    If this doesn't help, we'll need to see more of your code.

    BB

  6. #6

    Thread Starter
    Hyperactive Member
    Join Date
    Apr 2010
    Posts
    350

    Re: Slow graphics

    I have attached a screen shot to show what is happening.

    I was using double buffering by inheriting a panel but I have changed to just use a PictureBox.
    I have tried to redraw only if the cursor moves by a few pixels but it has made no significant improvement.

    I am moving the cursor and the arrow cursor moves. On the paint event I'm drawing the graphics and my own cursor. If there is a lot of graphics to be drawn them my cursor is slow to follow the arrow cursor. If there is not of graphics to be drawn then it works fine.
    Attached Images Attached Images  

  7. #7
    PowerPoster boops boops's Avatar
    Join Date
    Nov 2008
    Location
    Holland/France
    Posts
    3,201

    Re: Slow graphics

    Thanks for the screen shot, it makes the situation clearer. I suspect that the "lots of graphics" may be the problem. If you were drawing that as a static image, there should be no problem in dragging your cursor around smoothly. So does "lots of graphics" have to change in some way, or is there anything else that must happen while you are moving the cursor?

    It's clear from your code that the Graphics object eGraphics is declared at a higher level than the Paint sub. It would be safer to pass the PaintEventArgs or e.Graphics as a parameter to the DrawCursor sub, for example:
    Code:
    'in the Paint event handler:
    DrawCursor(e) '(you don't need Call in VB.Net)
    
    'in the DrawCursor sub:
    Private Sub DrawCursor(e As PaintEventArgs)
       e.Graphics.DrawLine '... etc.
    However, if you are doing something else with eGraphics besides the code you have shown, it could be the cause of the problem.

    BB

  8. #8
    You don't want to know.
    Join Date
    Aug 2010
    Posts
    4,578

    Re: Slow graphics

    I saw this thread a few days ago and didn't get a chance to post, now I remember it.

    You definitely do need to use Invalidate() instead of Refresh(). Invalidate() is a polite, "Please repaint when you can." Refresh() is more like, "Repaint NOW, I'm not going ANYWHERE until you do." There is almost never a good reason to use Refresh() instead of Invalidate(), which is why it's popular in internet tutorials.

    But that's not going to solve the problem outright.

    "Draw lots of lines" is always going to take "lots of time", where "time" is how long it takes to draw one line. So long as you're doing that every time, it's going to take a long time. So what can you do?

    I'm assuming the user draws these lines less frequently than the "lots of lines" updates. I'm assuming it only takes them a second or two. That means you can cheat. The reason you're redrawing now is you want to hide the old line, then display the new line, and the only way to do that is to redraw all of the old lines. There's normally two ways to draw a line like this quickly, but because your 'background' is complicated you can't use the second.

    1) Draw "lots of lines" to a bitmap and redraw that.
    In this technique, you draw the "lots of lines" to a bitmap if and only if you need to draw them. The paint logic ends up looking like:
    Code:
    If lines need to be updated:
        Clear the bitmap.
        Redraw the lines to the bitmap.
    End If
    
    Draw the bitmap.
    
    Draw the cursor.
    Copying a bitmap to the screen's faster than drawing a lot of lines, so long as "a lot" is a fairly large number. The details of figuring out when you need to update the lines might get complex, and you know more about your app than I do.
    This answer is wrong. You should be using TableAdapter and Dictionaries instead.

  9. #9
    Fanatic Member Spooman's Avatar
    Join Date
    Mar 2017
    Posts
    868

    Re: Slow graphics

    Yes, the image does help .. sort of

    The "thing" you labelled as "MY Cursor" .. does it consist of the small square,
    the large square, and the "cross-hairs"?

    Further, does is also include the segment of the colored image, and if so, is this
    segment of the colored image being "dragged" along with the "My Cursor"?

  10. #10

    Thread Starter
    Hyperactive Member
    Join Date
    Apr 2010
    Posts
    350

    Re: Slow graphics

    Thanks for your comments.

    1. As the amount of detail is variable and the user can zoom in, I think that drawing to a bitmap may add too much complexity to the program. I will try changing the level of graphics detail depending on the zoom level. When I'm zoomed in (and there is a lot of graphics) I will try to simplify what is drawn (i.e. instead of drawing a complex symbol just draw a circle) and when zoomed out (and there is less graphics and the cursor works as expected) draw the complex symbol in full. I will still consider drawing to a bitmap.

    2. "My cursor" is the two squares and the "cross hair". It moves over the coloured image "below".

  11. #11
    PowerPoster boops boops's Avatar
    Join Date
    Nov 2008
    Location
    Holland/France
    Posts
    3,201

    Re: Slow graphics

    Hi David,

    Sitten is absolutely right that drawing a bitmap can be much faster than drawing lines. Create the bitmap in memory and then drawing it takes about the same amount of time, but if you can re-use the bitmap it's considerably quicker.

    I did some testing and found that drawing a bitmap of about 1000 x 800 pixels with 2 thousand random straight lines was about 6 times as fast as drawing the lines separately. The smaller the image rendered, the greater the difference. The actual timing depend on the hardware and OS, but this indicates how much difference a pre-rendered bitmap can make.

    You didn't mention before that you were zooming the view of "lots of lines". I've noticed that a scaled-up image can cause a considerable slow-down when you draw over it. I haven't checked, but maybe the same applies when you draw lines separately using a scaled Graphics. The bigger the scaling factor, the worse the slowdown and it wouldn't surprise me if that is the cause of your problem.

    In that case it would make a lot of sense to pre-generate a LotsOfLines bitmap whenever the actual drawing changes. Then make a scaled/cropped copy to match the current PictureBox (or other target control) client size. Use that for painting at 1:1 size. I doubt if it would add much complexity to your present code, and I can offer some more detailed suggestions if you like.

    BB

  12. #12
    You don't want to know.
    Join Date
    Aug 2010
    Posts
    4,578

    Re: Slow graphics

    There's not a lot of choices. Your drawing code is what takes a lot of time, so if you want to reduce the time you spend you have to find a way to minimize the amount of drawing you do.

    There are levels of sophistication, and it's important you don't try to skip a level. Doing too much at once risks breaking something that works. But you're going to need a fairly high degree of sophistication if you want this to be fast. If you don't want to deal with bitmaps or sophisticated techniques, your app is going to be slow and there's nothing that can be done. There's no magic bullet to make "draw a lot of lines" take less than (time to draw 1 line) * (a lot), other than "buy faster machines".

    Level 1 is where you're at. At level 1 we just naively draw whatever we need every frame. If it takes you less than about 300ms to draw a frame, most users won't notice a delay. Somewhere around 500-700ms is when users start to notice a delay, and most people consider >= 2s annoying if frequent. So when we hit those thresholds it's time to consider adding sophistication. But it's stupid to ever start at a level that isn't Level 1, because it's easy to implement and if it's "fast enough" we won't waste time on sophistication.

    One way to next-level your code is to consider using invalidation rectangles. Invalidate() can take a parameter that indicates a rectangle that needs to be redrawn. Windows respects this, and there are properties of PaintEventArgs that will pass that information along to you. If you invalidate multiple rectangles before the Paint event happens, Windows is smart enough to resize the rectangle to include all "invalid" areas. You can check this and opt to ignore any lines that don't fall within the rectangle. This can dramatically decrease the amount of time spent drawing. But it also requires you to think about restructuring your data structures to help you figure out what to draw in a hurry.

    You want to be able to zoom, and it turns out that's a natural evolution of invalidation rectangles. One way to think about zoom is to imagine you are mapping a small rectangle of a large image to a larger rectangle. So you have to do some coordinate space re-mapping, but the logic's very much like using an invalidation rectangle. Let's say you zoom in on a 50-pixel square at (10, 10) and your window is 100 pixels square. That means the window's bounds { (0, 0), (100, 100) } now map to the image's { (10, 10), (60, 60) }. So you're only drawing lines within that rectangle, and you have to apply some scaling/translation to the coordinate systems. In other words, "It's just an extra bit of logic on top of using validation rectangles".

    So at this point, you're able to draw specific rectangles from the image and also remap the coordinates to the screen. It's super trivial to do this to an off-screen bitmap when needed. The only times you need to draw to the bitmap are:
    • The lines have changed within the current region the bitmap represents.
    • The zoom/pan has changed.


    To be clear, the scenarios in increasing complexity are:
    • There is no zoom, the entirety of the image is being displayed. You draw all lines to a bitmap only when the lines change.
    • The ability to redraw just part of the image is implemented. You can now draw some lines to the bitmap when they change.
    • Zoom is implemented. When the user zooms, the bitmap has to be redrawn to accomodate the new viewport.
    • Pan is implemented. When the user pans, the bitmap has to be redrawn to accomodate the new viewport.


    At each of these levels, the complexity of maintaining a bitmap of the last rendered results is trivial compared to the complexity of implementing the feature. It's not always easy to get from one level to the next, but I don't believe it's hard to switch to a bitmap at any of these levels.

    There are further levels of sophistication. You can cache bitmaps for certain zoom/pan levels. You can subdivide the image into tiles to assist with panning performance. Tiles can allow progressive quality increases to make zooming look faster (Google Maps does this.) You have to do anything you can to make "I need to redraw this one line" not require "I have to redraw every line."
    This answer is wrong. You should be using TableAdapter and Dictionaries instead.

  13. #13
    Fanatic Member Spooman's Avatar
    Join Date
    Mar 2017
    Posts
    868

    Re: Slow graphics

    Guys

    I must confess to being confused as to OP's actual issue.

    I thought the issue involved moving the "My Cursor" (2 squares + cross-hairs) as the
    MousePointer moves. Where does "lots of lines" enter the (ahem) picture?
    "My Cursor" consists of only 12 lines.

    Spoo

  14. #14
    You don't want to know.
    Join Date
    Aug 2010
    Posts
    4,578

    Re: Slow graphics

    Quote Originally Posted by Spooman View Post
    Guys

    I must confess to being confused as to OP's actual issue.

    I thought the issue involved moving the "My Cursor" (2 squares + cross-hairs) as the
    MousePointer moves. Where does "lots of lines" enter the (ahem) picture?
    "My Cursor" consists of only 12 lines.

    Spoo
    To draw the cursor in a new location, you have to repaint the form.

    The original "repaint the form" logic is:
    Code:
    * Draw all of the lines.
    * Draw the cursor.
    You can't skip "draw all of the lines" because the old image of the form still has the old cursor on it. So you have to at least erase the part of the form the cursor was covering, then redraw it. That's the "invalidation rectangle" approach I suggested, something like:
    Code:
    * Figure out where the cursor used to be.
    * Clear that region.
    * Figure out which lines were in that region and redraw only those.
    * Draw the cursor.
    But ultimately I'm proposing something more like this:
    Code:
    * If the lines have changed:
        * Redraw the cached bitmap.
    * Draw the cached bitmap.
    * Draw the cursor.
    This answer is wrong. You should be using TableAdapter and Dictionaries instead.

  15. #15
    Fanatic Member Spooman's Avatar
    Join Date
    Mar 2017
    Posts
    868

    Re: Slow graphics

    Sitten

    To draw the cursor in a new location, you have to repaint the form.
    Hmmm..

    Here is a potential alternative. Note that it is written in VB6 (not .Net)
    It loads the image into a PictureBox and "draws" the "cursor" with 6 Line objects.

    As the mouse is moved, only the 6 lines move.
    No delay or need to repaint the form.

    Basics:
    1. Form
    2. CommandButton
    3. PictureBox named PB1
    4. Line named Line1 with Index set to 1

    Here is the code

    Code:
    Public curTop, curLeft
    
    Private Sub Command1_Click()
        v = 6
        '
        If v = 6 Then
            With PB1
                .Width = 8000
                .Height = 3000
                .Top = 1000
                .ZOrder 0
            End With
            fname = "D:\VBForums\DG1.bmp"
            Set PB1.Picture = LoadPicture(fname)
            '
            Load Line1(2)
            Load Line1(3)
            Load Line1(4)
            Load Line1(5)
            Load Line1(6)
            oo = 1500
            For ii = 1 To 6
                With Line1(ii)
                    .Visible = True
                    .BorderColor = vbRed
                    .BorderWidth = 2
                    ' 1. left vert
                    If ii = 1 Then
                        .X1 = 500
                        .X2 = 500
                        .Y1 = 500
                        .Y2 = 500 + oo
                    ' 2. right vert
                    ElseIf ii = 2 Then
                        .X1 = 500 + oo
                        .X2 = 500 + oo
                        .Y1 = 500
                        .Y2 = 500 + oo
                    ' 3. top horiz
                    ElseIf ii = 3 Then
                        .X1 = 500
                        .X2 = 500 + oo
                        .Y1 = 500
                        .Y2 = 500
                    ' 4. bot horiz
                    ElseIf ii = 4 Then
                        .X1 = 500
                        .X2 = 500 + oo
                        .Y1 = 500 + oo
                        .Y2 = 500 + oo
                    ' 5. center vert
                    ElseIf ii = 5 Then
                        .X1 = 500 + oo / 2
                        .X2 = 500 + oo / 2
                        .Y1 = 500
                        .Y2 = 500 + oo
                    ' 6. center horiz
                    ElseIf ii = 6 Then
                        .X1 = 500
                        .X2 = 500 + oo
                        .Y1 = 500 + oo / 2
                        .Y2 = 500 + oo / 2
                    End If
                End With
            Next ii
            curTop = 500
            curLeft = 500
        End If
    End Sub
    
    Private Sub PB1_MouseMove(button As Integer, shift As Integer, x As Single, y As Single)
        '
        oo = 1500
        pp = 750
        '
        zzx = curLeft - x + pp
        zzy = curTop - y + pp
        '
        For ii = 1 To 6
            With Line1(ii)
                .Visible = True
                .BorderColor = vbRed
                .BorderWidth = 2
                If ii = 1 Then
                    .X1 = 500 - zzx
                    .X2 = 500 - zzx
                    .Y1 = 500 - zzy
                    .Y2 = 500 + oo - zzy
                ElseIf ii = 2 Then
                    .X1 = 500 + oo - zzx
                    .X2 = 500 + oo - zzx
                    .Y1 = 500 - zzy
                    .Y2 = 500 + oo - zzy
                ElseIf ii = 3 Then
                    .X1 = 500 - zzx
                    .X2 = 500 + oo - zzx
                    .Y1 = 500 - zzy
                    .Y2 = 500 - zzy
                ElseIf ii = 4 Then
                    .X1 = 500 - zzx
                    .X2 = 500 + oo - zzx
                    .Y1 = 500 + oo - zzy
                    .Y2 = 500 + oo - zzy
                ' 5. center vert
                ElseIf ii = 5 Then
                    .X1 = 500 + oo / 2 - zzx
                    .X2 = 500 + oo / 2 - zzx
                    .Y1 = 500 - zzy
                    .Y2 = 500 + oo - zzy
                ' 6. center horiz
                ElseIf ii = 6 Then
                    .X1 = 500 - zzx
                    .X2 = 500 + oo - zzx
                    .Y1 = 500 + oo / 2 - zzy
                    .Y2 = 500 + oo / 2 - zzy
                End If
            End With
        Next ii
    End Sub
    Here is a snap.

    Name:  DG2.jpg
Views: 1631
Size:  21.4 KB

    Note that "my" cursor is only 6 lines, shown in red
    The mouse arrow does not appear (dunno why), but it is located at the
    intersection of the "cross-hairs"

    Can this be accomplished in .Net?

    Dave

    If so, does this accomplish what the you wanted?

    Spoo
    Last edited by Spooman; May 17th, 2017 at 05:25 PM.

  16. #16

    Thread Starter
    Hyperactive Member
    Join Date
    Apr 2010
    Posts
    350

    Re: Slow graphics

    Thanks everybody for you suggestions.

    I tried simplifying the graphics by drawing a circle instead of a drawing a complex symbol and reducing the number of points in the line - but it did not make a significant difference - I would have had to reduce the amount of drawing to say 1% of the original.

    If the image has changed I save the PictureBox image to a bitmap and then if the image has not changed recalling the save image and drawing the cursor on top. The cursor is now very responsive.

    The problems are:
    1. Saving the bitmap is slow.
    2. It is saving the form and not just the PictureBox - see the attached image - I think the Rectangle parameters need changing.
    3. When saving the image a second time (i.e. after a zoom or pan) I get a generic error occurred in GDI+ error message - this occurs on the bmp.save statement - I don't know why.

    The code I'm using to save the bitmap is:

    Code:
    Dim bmp As New Bitmap(PicBox.Width, PicBox.Height)
    DrawToBitmap(bmp, New Rectangle(0, 0, PicBox.Width, PicBox.Height))
    bmp.Save(ImageFile, Imaging.ImageFormat.Png)
    bmp.Dispose()
    I think that the bitmap method will not work in practice. Saving the bitmap takes time - I assume it's the same time for images with a few or a lot of lines, and this will occur every time the user zooms or pans. Moving 'My cursor' is only slow if there are a lot of lines. The case I'm testing there are a very large number of line, normally there are significantly less.

    I'm trying to rewrite a Visual Fortran program - there was not this problem, I did not have to redraw the whole image, I could just draw the cursor using XOR draw mode - I don't think this is possible in VB.net
    Attached Images Attached Images  

  17. #17
    You don't want to know.
    Join Date
    Aug 2010
    Posts
    4,578

    Re: Slow graphics

    Quote Originally Posted by Spooman View Post

    Can this be accomplished in .Net?
    Yes. I suggested "draw to an image, draw the cursor over it". Your code draws an image, then draws the cursor over it. It can be accomplished in .NET, that's why I suggested it.

    @DavidGraham167: There is no reason to save the image. I can't see what DrawToBitmap() does, so I can't comment on why it might be not working. On the other hand, because there's no reason to save the image to a file, chasing that error's kind of a waste of time.

    On the other hand, you mentioned drawing with a reversible pen/XOR mode. Have a look at ControlPaint.DrawReversibleLine().

    I'm not at work just yet, but can demonstrate either technique. Gonna be a while, though. Busy day.
    This answer is wrong. You should be using TableAdapter and Dictionaries instead.

  18. #18
    Fanatic Member Spooman's Avatar
    Join Date
    Mar 2017
    Posts
    868

    Re: Slow graphics

    Quote Originally Posted by Sitten Spynne View Post
    Yes. I suggested "draw to an image, draw the cursor over it". Your code draws an image, then draws the cursor over it. It can be accomplished in .NET, that's why I suggested it.
    Thanks for confirmation.
    Sorry I missed the fact that you'd already suggested it ..

    Spoo

  19. #19

    Thread Starter
    Hyperactive Member
    Join Date
    Apr 2010
    Posts
    350

    Re: Slow graphics

    Sorry, I seemed to be misunderstanding the process. I thought it was:

    Step 1. Draw the lines on the picture box and then save the picture box to a bitmap and save it to a file.

    Step 2. Load the bitmap to the picture box and draw the cursor on top.

    Step 3. Move the cursor - only 'My cursor' is drawn and it moves on top of the image

    Step 4. If the image changes (zoom or pan) repeat from step 1.


    I will have a look at ControlPaint.DrawReversibleLine().

  20. #20
    Sinecure devotee
    Join Date
    Aug 2013
    Location
    Southern Tier NY
    Posts
    6,582

    Re: Slow graphics

    It is just Step 1 and 2 that are wrong.
    Don't draw the lines on the picture box. Draw the lines in a bitmap so it resides in memory.
    You then draw this bitmap on the picture box as needed and draw your cursor on top.
    When you need to redraw the "lots of lines", you redraw it in the bitmap, so you cache your drawing, and then use the bitmap to update the picturebox.

  21. #21
    PowerPoster boops boops's Avatar
    Join Date
    Nov 2008
    Location
    Holland/France
    Posts
    3,201

    Re: Slow graphics

    There's no need to save the bitmap to a file every time you zoom or pan, is there? I think it will be more efficient to use the stored memory bitmap (bmp) and regenerate it from the original image when zooming or panning. Saving to a file should be a separate function.

    BB

    edit: I think this amounts to the same as Passel is recommending.

  22. #22
    Sinecure devotee
    Join Date
    Aug 2013
    Location
    Southern Tier NY
    Posts
    6,582

    Re: Slow graphics

    Quote Originally Posted by Spooman View Post
    Quote Originally Posted by Sitten Spynne View Post
    Yes. I suggested "draw to an image, draw the cursor over it". Your code draws an image, then draws the cursor over it. It can be accomplished in .NET, that's why I suggested it.
    Thanks for confirmation.
    Sorry I missed the fact that you'd already suggested it ..

    Spoo
    FYI, that isn't really the same thing as Spoo was suggesting from VB6
    VB6 allows drawing using different Raster OPs, so you can draw a line using an XOR raster op to show a line, then redraw the same line again using the XOR raster op to erase it.
    You can't do that in .Net unless you switch your drawing over to using GDI and use the older API to draw (which is a little tricky).

    The DrawReversibleLine method may be wrapping that old XOR drawing capability and providing it to .Net users. Of course it could be using a different method (not XOR), but doing it in some fairly efficient manner to provide the same, or perhaps better looking, result.

    Edit:!!!
    I just looked back and realized that spoo was suggesting using Line Shape controls, not XOR drawing.
    That capability is provided in .Net through the Microsoft.VisualBasic.PowerPacks namespace, but how well it would work I don't know. It may be a bit flashy. I don't know as I normally avoid using any of the PowerPacks namespace which exists to emulate some VB6 capabilities, i.e. the Shape controls, which you were referring to from VB6. I generally avoided using the shape controls in VB6, just did the drawing myself.
    Last edited by passel; May 18th, 2017 at 10:58 AM.

  23. #23
    PowerPoster boops boops's Avatar
    Join Date
    Nov 2008
    Location
    Holland/France
    Posts
    3,201

    Re: Slow graphics

    Quote Originally Posted by Sitten Spynne View Post
    you mentioned drawing with a reversible pen/XOR mode. Have a look at ControlPaint.DrawReversibleLine().
    Unfortunately DrawReversibleLine is fairly useless because all it does is draw a line in the inverse of a single specified colour. So you might as well just choose a suitable system pen. For what it's worth, I posted a rather roundabout way of drawing anything in "XOR mode" in .Net here. Even that is of limited use because the inverse of anything around middle gray will be invisible.

    I've never heard of a way of drawing a cursor or other contrasting image which can be relied on to stand out against any conceivable background. Suppose you made a cursor consisting of alternating black, red and white dots, someone would come along with an image which consists exclusively of black, red and white dots.

    BB

  24. #24
    You don't want to know.
    Join Date
    Aug 2010
    Posts
    4,578

    Re: Slow graphics

    Quote Originally Posted by boops boops View Post
    Unfortunately DrawReversibleLine is fairly useless because all it does is draw a line in the inverse of a single specified colour. So you might as well just choose a suitable system pen. For what it's worth, I posted a rather roundabout way of drawing anything in "XOR mode" in .Net here. Even that is of limited use because the inverse of anything around middle gray will be invisible.

    I've never heard of a way of drawing a cursor or other contrasting image which can be relied on to stand out against any conceivable background. Suppose you made a cursor consisting of alternating black, red and white dots, someone would come along with an image which consists exclusively of black, red and white dots.

    BB
    Oh. Duh. You're right. I thought about this the other day, then promptly forgot why I didn't mention DrawReversibleLine(). It only works vs. a solid background color.
    This answer is wrong. You should be using TableAdapter and Dictionaries instead.

  25. #25
    Sinecure devotee
    Join Date
    Aug 2013
    Location
    Southern Tier NY
    Posts
    6,582

    Re: Slow graphics

    Quote Originally Posted by Sitten Spynne View Post
    Oh. Duh. You're right. I thought about this the other day, then promptly forgot why I didn't mention DrawReversibleLine(). It only works vs. a solid background color.
    That isn't quite true, and what boops boops said isn't fully correct either.
    As you noted in the other post, DrawReversibleLine does use the XOR raster OP to draw the line, so it works as well as any XOR raster op drawn line does.
    The color passed is used to generate/select a high contrast value to that color, which is then used in the XOR operation when the line is drawn.

    For instance if you pass in Black or White to the method, the highest contrasting value to use for the XOR operation would be White, so White will be used in the Raster OP.
    When the line draws over a multicolored background, the colors will be inverted, i.e. the line pixels will be Cyan over the Red pixels, yellow over blue, and magneta over green.
    The line over a gray background would be a very close gray, so wouldn't be discernible to the human eye so would appear not drawn.

    Since the method chooses a contrasting color, then perhaps passing gray to the method would be the best choice (if you were to use the method on a multi-colored image) as the line would show up as black on a gray background and various shades of gray tinted with color on the other colors, but should always be visible, I think. This is assuming you have patches of gray that are large enough to be an issue. If not, then passing White to the method would be my default.

    p.s. That said, I don't know that I would use it in this case since it is defined as being usable outside the paint event of the control, so seems that it might be suitable for a dynamic short lived indication, i.e. a rubberband line, but not something that may be expected to always be present, like a cursor.
    Last edited by passel; May 18th, 2017 at 01:49 PM.

  26. #26
    You don't want to know.
    Join Date
    Aug 2010
    Posts
    4,578

    Re: Slow graphics

    Well, either way I'm stuck on the example for now.

    I have it drawing 200 lines to a bitmap with a 500ms delay, and only redrawing the bitmap when I ask. I have it drawing a square around the mouse to represent the cursor. I can see some hiccups in rendering even in this state. So I started working on invalidation regions.

    I screwed up in a lot of different ways trying to implement those invalidation regions. I think what happened was I got order of operations wrong. I mean, it's working, but I'm not always fully erasing the cursor, especially if you move it quickly. What I want is something like:
    Code:
    When mouse moves:
        * Calculate where the new cursor will be.
        * Invalidate a region that encloses both the new and old cursors.
    
    When painting:
        * Clear the entire invalidation rectangle.
        * Figure out what parts of the bitmap overlap the cleared region.
        * Redraw those parts of the bitmap.
        * Draw the new cursor.
    I think what I screwed up and did wsa:
    Code:
    When mouse moves:
        * SAME
    
    When painting:
        * Try to clear where the old cursor was, BUT
            * The only thing I am storing is where the new cursor is
            * Jeez I'm stupid
    I'm going to try to fix that but can't spend more than about another half-hour on it today
    This answer is wrong. You should be using TableAdapter and Dictionaries instead.

  27. #27
    Sinecure devotee
    Join Date
    Aug 2013
    Location
    Southern Tier NY
    Posts
    6,582

    Re: Slow graphics

    {edit: perhaps your example is not using the DrawReversibleLine, in which case my comment below is not valid. You are probably just trying a method of invalidating smaller regions for the paint event.}

    It seems like it might be unreliable because windows will sometimes choose to cause a paint event for some reason which will mess up the XOR two step dance. If a paint event came along, your "old" line will already be erased, so if you "erase" it, you'll end up drawing it instead.

    I think you would need to coordinate with the paint event, so that you know not to "erase" the line if a paint event occurred.
    Last edited by passel; May 18th, 2017 at 02:02 PM.

  28. #28
    PowerPoster boops boops's Avatar
    Join Date
    Nov 2008
    Location
    Holland/France
    Posts
    3,201

    Re: Slow graphics

    @Sitten:
    It helps to inflate the dirty rectangle by a few pixels before invalidating it. Don't worry about combining rectangles to form the region. The framework does that automatically when clearing the invalid queue, so it doesn't matter if you invalidate the same area repeatedly.

    I use a pattern like this:
    Code:
    Dim r as Rectangle = cursor bounds
    r.Inflate(2, 2)
    control.Invalidate(r) 'invalidate old cursor bounds
    cursor bounds.Offset(dx, dy) 'draw cursor in new position
    r.Offset(dx, dy)
    control.Invalidate(r) 'invalidate new cursor bounds
    BB

  29. #29
    You don't want to know.
    Join Date
    Aug 2010
    Posts
    4,578

    Re: Slow graphics

    Huh. I'm incorporating that. It's a lot more elegant than my "try to remember the last position but also keep the current position in mind and oh crud what if multiple MouseMove events happen before Paint" approach.
    This answer is wrong. You should be using TableAdapter and Dictionaries instead.

  30. #30
    You don't want to know.
    Join Date
    Aug 2010
    Posts
    4,578

    Re: Slow graphics

    Below is a Form file, it sort of sets itself up so you can paste the code into a new project, push play, then go. Let's talk about parts of it.

    The application draws 200 lines in a grid pattern in a 400x400 pixel square. That didn't really introduce a delay on my system, so there's a Thread.Sleep() to make it wait 500ms for some visible delay when it happens. This is a protip for evaluating if you invalidate more than you should when prototyping. There is a "Redraw in 3" button that waits 3 seconds, then initiates a redrawing of the lines. This obviously causes the 500ms delay. A 3-pixel thick green square surrounds the cursor. I made it 3 pixels because if it's 1 pixel it creates the illusion of a lot of flicker as it passes over the grid.

    So if you run it and poke around, it should look pretty smooth. When the mouse moves, a rectangle surrounding the old cursor and the new cursor is invalidated. When the form draws, it checks if it needs to redraw all of the lines. If it does, it draws the lines to a Bitmap that's stored in the field _lastLines. If it doesn't, it checks if any of the lines are inside the invalid rectangle. If so, it redraws that chunk of the bitmap, then redraws the cursor. It's pretty smooth on my machine.

    One glitch: when the mouse moves over the button, the button gets the MouseMove event. This causes some artifacts from the old cursor border to not always get erased. I can think of a few ways to handle it, but left it because example code. There's a lot of other problems, like "I create 200 pens despite only needing 7 total". Example code.

    Here's the tricky bits.

    The code needs to understand how to translate between two coordinate spaces. The bitmap's top-left corner is (0, 0) in its coordinate space, but that is located at (10, 10) in the Form's coordinate space. So when GetBitmapAreaToDraw() does some translation. The intersectingRegion variable is in Form coordinates. So when calculating the return value, the X and Y coordinate are shifted left by 10.
    Code:
    Return New Rectangle(intersectingRegion.X - 10, intersectingRegion.Y - 10, intersectingRegion.Width, intersectingRegion.Height)
    The next most important part is the MouseMove handler. I based it on boops boops' suggestion, with some tweaks:
    Code:
    ' Invalidate the area around the cursor.
    Dim cursorPosition = Cursor.Position
    Dim clientCursorPosition = Me.PointToClient(cursorPosition)
    
    ' Thanks to boops boops for this snippet, it's better than anything I came up with.
    _cursorBounds.Inflate(5, 5)
    Invalidate(_cursorBounds)
    _cursorBounds = GetCursorBounds(clientCursorPosition)
    Invalidate(_cursorBounds)
    The field _cursorBounds stores the rectangle used to draw the cursor on the last rendering pass. The border is 3 pixels wide. So I invalidate a 5-pixel region around it to account for the 2-3 extra pixels of height and width the Pen's thickness represents. The double-Invalidate() looks wasteful to the untrained eye, but it's important to tell Windows both rectangles are invalid. Behind the scenes, Windows creates a larger rectangle that includes both of these rectangles and only one call to OnPaint is made.

    I feel like everything else is self-exlpanatory. I added the "Redraw" button to help highlight you can't get around "repainting the entire thing is slow". The purpose of this demonstration is to showcase, "You don't have to repaint everything."

    Code:
    Public Class Form1
    
        Private Const DrawDelay As Integer = 500
    
        Private _shouldRedrawLines As Boolean = True
        Private ReadOnly _lines As New List(Of LineInfo)()
    
        Private _lastLines As Bitmap
    
        Private _cursorBounds As Rectangle
    
        Private _rng As New Random()
        Private ReadOnly _colors() As Color = {Color.Red, Color.Orange, Color.Yellow, Color.Green, Color.Blue, Color.Indigo, Color.Violet}
    
    
        Public Sub New()
    
            ' This call is required by the designer.
            InitializeComponent()
    
            ' Add any initialization after the InitializeComponent() call.
            Me.DoubleBuffered = True
        End Sub
    
        Private Sub Form1_Shown(sender As Object, e As EventArgs) Handles MyBase.Shown
            Me.ClientSize = New Size(600, 450)
    
            Dim redrawButton As New Button() With {
                .Text = "Redraw in 3 seconds",
                .Location = New Point(430, 10)
            }
    
            ' This is ugly as heck and would look nicer with some features of VS2013. Consider upgrading!
            ' Also, this is NEVER how I'd write this in a real application.
            AddHandler redrawButton.Click, Sub(s, args)
                                               Threading.ThreadPool.QueueUserWorkItem(
                                                    Sub(state)
                                                        Threading.Thread.Sleep(3000)
    
                                                        Me.Invoke(Sub()
                                                                      _shouldRedrawLines = True
                                                                      Me.Invalidate()
                                                                  End Sub)
                                                    End Sub
                                               )
                                           End Sub
    
            Me.Controls.Add(redrawButton)
        End Sub
    
        Private Sub Form1_Paint(sender As Object, e As PaintEventArgs) Handles MyBase.Paint
            e.Graphics.DrawRectangle(Pens.Black, New Rectangle(10, 10, 400, 400))
    
            'GetBitmapAreaToDraw(e.ClipRectangle)
            If _shouldRedrawLines Then
                _shouldRedrawLines = False
                RedrawLines()
            End If
    
            DrawImage(e.Graphics)
            DrawCursor(e.Graphics)
        End Sub
    
        Private Sub DrawCursor(ByVal g As Graphics)
            Using stroke As New Pen(Color.Green, 3)
                g.DrawRectangle(stroke, _cursorBounds)
            End Using
        End Sub
    
        Private Sub DrawImage(ByVal g As Graphics)
            Dim imageArea = GetBitmapAreaToDraw(Rectangle.Round(g.ClipBounds))
            If Not imageArea.HasValue Then
                Return
            Else
                Dim redrawBounds = imageArea.Value
                Dim clientBounds = New Rectangle(redrawBounds.X + 10, redrawBounds.Y + 10, redrawBounds.Width, redrawBounds.Height)
                g.DrawImage(_lastLines, clientBounds, redrawBounds, GraphicsUnit.Pixel)
            End If
    
        End Sub
    
        ' Determines which parts, if any, of the bitmap need to be redrawn.
        Private Function GetBitmapAreaToDraw(ByVal invalidBounds As Rectangle) As Nullable(Of Rectangle)
            ' This is the area of the bitmap in terms of the Form's coordinates.
            Dim clientBounds As New Rectangle(10, 10, 400, 400)
            ' This is the area of the bitmap in terms of its own coordinates.
            Dim bitmapBounds As New Rectangle(10, 10, 400, 400)
    
            ' The intersection of the two is what part of the Bitmap needs to be drawn.
            Dim intersectingRegion = clientBounds
            intersectingRegion.Intersect(invalidBounds)
    
            If intersectingRegion.Height = 0 OrElse intersectingRegion.Width = 0 Then
                Return Nothing
            Else
                Return New Rectangle(intersectingRegion.X - 10, intersectingRegion.Y - 10, intersectingRegion.Width, intersectingRegion.Height)
            End If
    
        End Function
    
        Private Sub RedrawLines()
            GenerateLines()
    
            Dim newBitmap As New Bitmap(400, 400)
    
            Using g As Graphics = Graphics.FromImage(newBitmap)
                For Each line As LineInfo In _lines
                    Using linePen As New Pen(line.Color, 1)
                        g.DrawLine(linePen, line.Start, line.End)
                    End Using
                Next
            End Using
    
            If _lastLines IsNot Nothing Then
                _lastLines.Dispose()
            End If
    
            _lastLines = newBitmap
    
            Threading.Thread.CurrentThread.Sleep(DrawDelay)
        End Sub
    
        Private Sub GenerateLines()
            _lines.Clear()
    
            ' Horizontal
            For row = 1 To 99
                Dim thisLine = New LineInfo
                thisLine.Start = New Point(0, row * 4)
                thisLine.End = New Point(399, row * 4)
                Dim colorIndex As Integer = _rng.Next(0, _colors.Length)
                thisLine.Color = _colors(colorIndex)
    
                _lines.Add(thisLine)
            Next
    
            ' Vertical
            For column = 1 To 99
                Dim thisLine = New LineInfo
                thisLine.Start = New Point(column * 4, 0)
                thisLine.End = New Point(column * 4, 400)
                Dim colorIndex As Integer = _rng.Next(0, _colors.Length)
                thisLine.Color = _colors(colorIndex)
    
                _lines.Add(thisLine)
            Next
        End Sub
    
        Private Class LineInfo
            Public Property Start As Point
            Public Property [End] As Point
            Public Property Color As Color
        End Class
    
        Private Sub Form1_MouseMove(sender As Object, e As MouseEventArgs) Handles MyBase.MouseMove
            ' Invalidate the area around the cursor.
            Dim cursorPosition = Cursor.Position
            Dim clientCursorPosition = Me.PointToClient(cursorPosition)
    
            ' Thanks to boops boops for this snippet, it's better than anything I came up with.
            _cursorBounds.Inflate(5, 5)
            Invalidate(_cursorBounds)
            _cursorBounds = GetCursorBounds(clientCursorPosition)
            Invalidate(_cursorBounds)
        End Sub
    
        Private Function GetCursorBounds(ByVal position As Point) As Rectangle
            Return New Rectangle(position.X - 20, position.Y - 20, 40, 40)
        End Function
    
    End Class
    This answer is wrong. You should be using TableAdapter and Dictionaries instead.

  31. #31
    Fanatic Member Spooman's Avatar
    Join Date
    Mar 2017
    Posts
    868

    Re: Slow graphics

    Sitten

    Could you post an (some) image(s).

    Spoo

  32. #32
    You don't want to know.
    Join Date
    Aug 2010
    Posts
    4,578

    Re: Slow graphics

    I don't think images are going to demonstrate "see how the animation is smooth". But if you can imagine a green, square cursor moving smoothly over a field of plaid, that's what the images would look like.
    This answer is wrong. You should be using TableAdapter and Dictionaries instead.

  33. #33
    Fanatic Member Spooman's Avatar
    Join Date
    Mar 2017
    Posts
    868

    Re: Slow graphics

    Sitten

    The reason I asked is that I don't have .NET loaded (yes, I know, lame excuse),
    and have never created a .NET app.

    I just wanted to see what your form, 200 lines, and thick green square looked
    like for comparison purposes with OP's situation.

    Spoo

  34. #34

    Thread Starter
    Hyperactive Member
    Join Date
    Apr 2010
    Posts
    350

    Re: Slow graphics

    Thanks to everybody for their help, especially to 'Sitten Spynne' for posting the code. I couldn't have managed without you.

    My example has over 17,000 being drawn and My cursor now moves smoothly over the image. I couldn't get the MouseMove part of invalidating the two rectangles to work, maybe I've got my coordinates wrong - I have just invalidated the whole area. My cursor is slightly behind the arrow but it doesn't jerk and the user wouldn't complain.

    Thanks again for all your help.
    David

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  



Click Here to Expand Forum to Full Width