Results 1 to 6 of 6

Thread: [RESOLVED] Doing window drawing efficiently

  1. #1

    Thread Starter
    Fanatic Member
    Join Date
    Jul 2009
    Posts
    629

    Resolved [RESOLVED] Doing window drawing efficiently

    So, I've written an image viewer that allows zooming, moving it around while zooming and Async-image loading to keep everything responsive. It works well with very little lag problems...except when I start displaying animated GIF images. In my program I store images in my own format, where all frames in the GIF are stored as bitmaps of ARGB32 (fastest format). A thread runs to increment the displayed frame and force a redraw using control.invalidate. Some excerpts:

    Image frame is incremented, firing an (Async, not UI!) event handled by any listener (controls):
    Code:
        Private Sub UpdateAnimation()
            Dim now As Date = Date.Now
            Dim lastFrameChange As Date = now
            While Not disposedValue AndAlso Not Me.m_animateThread.IsAborting
                now = Date.Now
                If now.Subtract(lastFrameChange).TotalMilliseconds >= Me.m_frameDelays(Me.m_frameIndex) Then
                    lastFrameChange = now
                    Me.CurrentFrame += 1
                End If
                Me.m_animateThread.WaitOne(5)
            End While
        End Sub
    Which in turn...:
    Code:
        Private Sub OnFrameChanged(ByVal sender As System.Object, ByVal e As System.EventArgs)
            Me.Invalidate()
        End Sub
    And then to redraw the image this is called by onPaint in the control the image is drawn in:
    Code:
        Public Sub Draw(ByVal g As Graphics, ByVal interpolation As Drawing2D.InterpolationMode, ByVal screenSize As Size, ByVal Offset As Point, ByVal Zoom As Double)
            If Me.IsDisposed Then
                Return
            End If
            Dim srcRect As New RectangleF(0, 0, Me.Width, Me.Height)
            Dim destRect As New RectangleF(-Offset.X, -Offset.Y, Width * Zoom, Height * Zoom)
    
            ' Draw background gradient image if destRect does not cover the entire screen
            If destRect.Left > 0 OrElse destRect.Top > 0 OrElse destRect.Right < Me.Width OrElse destRect.Height < Me.Height Then
                If BackgroundImage.Width > 1 AndAlso BackgroundImage.Height > 1 Then
                    Dim thumbSrcRect As New RectangleF(0.0F, 0.0F, BackgroundImage.Width - 1.0F, BackgroundImage.Height - 1.0F)
                    g.InterpolationMode = Drawing2D.InterpolationMode.Low
                    g.DrawImage(BackgroundImage, New RectangleF(0, 0, screenSize.Width, screenSize.Height), thumbSrcRect, GraphicsUnit.Pixel)
                End If
            End If
    
            ' Calculate the offset and zoom, and draw the image in the resulting rectangle
            If destRect.Left < 0 Then
                srcRect.X -= destRect.X / Zoom
                srcRect.Width -= srcRect.X
                destRect.Width += destRect.X
                destRect.X = 0
            End If
            If destRect.Top < 0 Then
                srcRect.Y -= destRect.Y / Zoom
                srcRect.Height -= srcRect.Y
                destRect.Height += destRect.Y
                destRect.Y = 0
            End If
            If destRect.Right > screenSize.Width Then
                srcRect.Width -= ((destRect.Right - screenSize.Width) / Zoom) - 1.0
                destRect.Width = (screenSize.Width - destRect.Left) + Zoom
            End If
            If destRect.Bottom > screenSize.Height Then
                srcRect.Height -= ((destRect.Bottom - screenSize.Height) / Zoom) - 1.0
                destRect.Height = (screenSize.Height - destRect.Top) + Zoom
            End If
            g.InterpolationMode = interpolation
            g.DrawImage(Image, destRect, srcRect, GraphicsUnit.Pixel)
        End Sub
    This all works excellently. The image animates at the right speed. But there is one major problem: the continuous invalidation of the image displaying control causes all other menus and controls to redraw poorly or not at all. When hovering over buttons, they don't change state. When resizing the window the controls hardly redraw and glitch out. Similarly in menus they don't pop up or take very long to change state when hovering over a menu item. It gives the appearance that the program is unresponsive. I don't see this happening in webbrowsers and the like, and at the same time I am aware that you can not perform screen drawing on another thread. So what to do? What can I do to prevent the entire application from freezing all drawing operations? Is this a problem caused by 'Invalidate'?

    Using a timer instead of a thread to routinely call invalidate during animation did not help.

  2. #2
    Hyperactive Member Vexslasher's Avatar
    Join Date
    Feb 2010
    Posts
    429

    Re: Doing window drawing efficiently

    If your displaying a ton of animated gif images at once the problem is probably that there is not enough ram to load all of the images at once. I'm pretty sure that's why windows doesn't show animated gif thumbnails. Maybe you could just make the images quality & size very low for gif thumbnails that might prevent the running out of ram problem.

  3. #3

    Thread Starter
    Fanatic Member
    Join Date
    Jul 2009
    Posts
    629

    Re: Doing window drawing efficiently

    I do have thumbnails elsewhere, but they don't store all frames and are a lower resolution. Unlike the default loading system, my implementation 'pre-loads' the full GIF into memory. So if there are memory issues, they would show up while loading the image before displaying, not while rendering. Reason I pre-load GIF images is that Windows keeps a lock on the Image file when you do that, and in my application I do enable the user to delete, rename or move the image while it's displayed.

    In case it is relevant:
    Code:
        Public Sub New(ByVal ImageFile As Path, ByVal FileSize As Long, ByVal Image As Image)
            Me.New(ImageFile, FileSize)
            Me.m_validImage = Image IsNot Nothing
            Me.m_frameTotal = 1
            Me.m_frameIndex = 0
            If Not Me.m_validImage Then
                ' Use error icon instead
                Image = My.Resources.Sign_Error_icon
            ElseIf ImageAnimator.CanAnimate(Image) Then
                ' Read frame count for animated GIF images
                Me.m_frameTotal = Image.GetFrameCount(FrameDimension.Time)
            End If
    
            ' Set up the image
            Me.m_dim = Image.Size
            ReDim Me.m_frames(Me.m_frameTotal - 1)
            ReDim Me.m_frameDelays(Me.m_frameTotal - 1)
    
            ' Initialize the frames with default frame delay of 50 ms
            For i As Integer = 0 To Me.m_frameTotal - 1
                Image.SelectActiveFrame(FrameDimension.Time, i)
                Dim frame As New Bitmap(Width, Height, FAST_PIXEL_FORMAT)
                Using g As Graphics = Graphics.FromImage(frame)
                    g.DrawImage(Image, 0, 0, Width, Height)
                End Using
                Me.m_frames(i) = frame
                Me.m_frameDelays(i) = 50
            Next
    
            ' Initialize delays and start animation in case of multiple frames
            If Me.m_frameTotal > 1 Then
                Const PropertyTagFrameDelay As Integer = &H5100
                Dim frameDelayItem As PropertyItem = Image.GetPropertyItem(PropertyTagFrameDelay)
    
                ' If the image does not have a frame delay, we just return 0. 
                If frameDelayItem IsNot Nothing Then
                    ' Convert the frame delay from byte[] to int
                    Dim values As Byte() = frameDelayItem.Value
                    If values.Length = 4 * Me.m_frameTotal Then
                        Dim vi As Integer
                        Dim last As Integer = 50
                        For i As Integer = 0 To Me.m_frameTotal - 1
                            vi = i << 2
                            Me.m_frameDelays(i) = 10 * (values(vi) + values(vi + 1) << 8 + values(vi + 2) << 16 + values(vi + 3) << 24)
                            If Me.m_frameDelays(i) = 0 Then
                                Me.m_frameDelays(i) = last
                            End If
                            last = Me.m_frameDelays(i)
                        Next
                    End If
                End If
                InitAnimation()
            End If
        End Sub

  4. #4
    Addicted Member
    Join Date
    Oct 2012
    Location
    Springfield, IL
    Posts
    142

    Re: Doing window drawing efficiently

    Invalidate would definitely be something to investigate. It's taking place on the UI thread and drawing operations are about the most CPU intensive things you can do on winforms. I assume you have DoubleBuffer enabled? Possibly calling Invalidate with an invalid region rectangle might help a bit e.g.

    Me.Invalidate(Me.ClientRectangle)
    or passing the bitmap rectangle if it doesn't fill the entire client area.

    Interesting.. from MSDN: Calling the Invalidate method does not force a synchronous paint; to force a synchronous paint, call the Update method after calling the Invalidate method. When this method is called with no parameters, the entire client area is added to the update region.

    I've never used Update before so I don't know if that would help but it should be easy to give it a try.

    Edit:
    I just realized -- Me.Invalidate(Me.ClientRectangle) = Me.Invalidate() so there isn't any difference there.
    Last edited by OICU812; Mar 29th, 2014 at 03:23 PM.

  5. #5

    Thread Starter
    Fanatic Member
    Join Date
    Jul 2009
    Posts
    629

    Re: Doing window drawing efficiently

    @OICU812
    Looks like that was indeed the problem. I added a timer and called invalidate, followed by update. Now the amount of drawing distortions is visibly less, and it doesn't seem to forget to draw parts of the screen any more. I used invalidate() because that was advertised in the 'animated drawing' demos. Clearly it's not the best way to do it, and it's being used wrongly in many places. Marking it as resolved, but if people know even better ways to force a redraw, don't feel hesitant to post. I'm always looking for ways to make it a little better.

  6. #6
    Addicted Member
    Join Date
    Oct 2012
    Location
    Springfield, IL
    Posts
    142

    Re: Doing window drawing efficiently

    Quote Originally Posted by bergerkiller View Post
    @OICU812
    Looks like that was indeed the problem. I added a timer and called invalidate, followed by update. Now the amount of drawing distortions is visibly less, and it doesn't seem to forget to draw parts of the screen any more. I used invalidate() because that was advertised in the 'animated drawing' demos. Clearly it's not the best way to do it, and it's being used wrongly in many places. Marking it as resolved, but if people know even better ways to force a redraw, don't feel hesitant to post. I'm always looking for ways to make it a little better.
    Awesome! Glad I could help. I learned something new myself.

Tags for this Thread

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