-
Mar 29th, 2014, 11:02 AM
#1
Thread Starter
Fanatic Member
[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.
-
Mar 29th, 2014, 11:15 AM
#2
Hyperactive Member
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.
-
Mar 29th, 2014, 11:42 AM
#3
Thread Starter
Fanatic Member
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
-
Mar 29th, 2014, 02:20 PM
#4
Addicted Member
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.
-
Mar 29th, 2014, 03:22 PM
#5
Thread Starter
Fanatic Member
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.
-
Mar 29th, 2014, 03:59 PM
#6
Addicted Member
Re: Doing window drawing efficiently
Originally Posted by bergerkiller
@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
-
Forum Rules
|
Click Here to Expand Forum to Full Width
|