Avoiding flickering when updating images is slow

I have an interface showing some images that are computed on the fly, e.g. when the user resize the window. The computations are not quick, i.e. each takes a fraction of second. Because of that, their is some significant flickering when the window is resized.

I am providing an example below. The time.sleep() simulates the computation time required to generate the images.

I am looking for a way to avoid this flicker. Meaning, the content would refresh at once, when all computations are done. Interestingly, this is exactly what happens when images are not involved: if I keep the time.sleep() in the example below but remove relevant lines, the entire content of the window is updated at once, no matter the differences in computatino time of the subframes.

To be more precise, if I comment out the bitmap drawing part only (“dc.DrawBitmap(bitmap, 0, 0)”), the flickering still occurs. The flickering remains till I remove the call to image.ConvertToBitmap(). I do not know why.

Anyway, any help would be appreciated.

I am running wxpython 2.8.9.1 (gtk2-unicode) on Ubuntu 9.04.

Paul

···

print sys.version
2.6.2 (release26-maint, Apr 19 2009, 01:56:41)

[GCC 4.3.3]

print wx.version()
2.8.9.1 (gtk2-unicode)

import Image
import random
import time
import wx

def pilToImage(pil):
image = wx.EmptyImage(pil.size[0], pil.size[1])

image.SetData(pil.convert(‘RGB’).tostring())
return image

def imageToBitmap(image):
return image.ConvertToBitmap()

def pilToBitmap(pil):
return imageToBitmap(pilToImage(pil))

class Item(wx.Panel):
“”“Displays an image that is computed on the fly”“”

def init(self, parent):
wx.Panel.init(self, parent, style=wx.SIMPLE_BORDER | wx.FULL_REPAINT_ON_RESIZE)

self.Bind(wx.EVT_PAINT, self.OnPaint)

def OnPaint(self, event):
dc = wx.PaintDC(self)
dc.BeginDrawing()
# the following accounts for computation time
time.sleep(0.1 + 0.1 * random.random())

image = Image.new('F', dc.GetSizeTuple())
# remove the following two lines, and the flicker disappears
bitmap = pilToBitmap(image)
dc.DrawBitmap(bitmap, 0, 0)
dc.EndDrawing()

class MyFrame(wx.Frame):
“”“A display of three Item objects”“”

def init(self, *args, **kwargs):

wx.Frame.__init__(self, *args, **kwargs)

self.view = [Item(self),Item(self),Item(self)];
self.view[0].SetBackgroundColour(wx.Colour(100,0,0))
self.view[1].SetBackgroundColour(wx.Colour(0,100,0))

self.view[2].SetBackgroundColour(wx.Colour(0,0,100))

sizerVer = wx.BoxSizer(wx.VERTICAL)

sizerVer.Add(self.view[2], proportion = 1, flag = wx.EXPAND | wx.BOTTOM, border = 5)

sizerVer.Add(self.view[1], proportion = 1, flag = wx.EXPAND , border = 5)



sizerHor = wx.BoxSizer(wx.HORIZONTAL)

sizerHor.Add(self.view[0], proportion = 1, flag = wx.EXPAND | wx.ALL, border = 5)

sizerHor.Add(sizerVer,     proportion = 1, flag = wx.EXPAND | wx.TOP | wx.BOTTOM | wx.RIGHT, border = 5)

self.SetSizer(sizerHor)

myApp = wx.App(0)

frame = MyFrame(None, wx.ID_ANY, “Main Frame”)

frame.Show()

myApp.MainLoop()

user790 user790 wrote:

I have an interface showing some images that are computed on the fly, e.g. when the user resize the window. The computations are not quick, i.e. each takes a fraction of second. Because of that, their is some significant flickering when the window is resized.

Th trick usually used here is to make sure that you don't try to update too fast. This can be accomplished by:

On the Size event, start a wxTimer for slight delay -- maybe 0.1 s.
Do the re-drawing when the timer fires.
On each new size event, restart the timer.

This way, if there are a lot of fast-coming size events, it'll only try to draw when the user stops re-sizing.

There is an example of this in wx.lib.floatcanvas. Look at the FloatCanvas.py Here is an exgtract that should make it clear:

     def OnSize(self, event=None):
         self.SizeTimer.Start(50, oneShot=True)

     def OnSizeTimer(self, event=None):
         self.MakeNewBuffers()
         self.Draw()

-Chris

···

I am providing an example below. The time.sleep() simulates the computation time required to generate the images.

I am looking for a way to avoid this flicker. Meaning, the content would refresh at once, when all computations are done. Interestingly, this is exactly what happens when images are not involved: if I keep the time.sleep() in the example below but remove relevant lines, the entire content of the window is updated at once, no matter the differences in computatino time of the subframes.

To be more precise, if I comment out the bitmap drawing part only ("dc.DrawBitmap(bitmap, 0, 0)"), the flickering still occurs. The flickering remains till I remove the call to image.ConvertToBitmap(). I do not know why.

Anyway, any help would be appreciated.

I am running wxpython 2.8.9.1 (gtk2-unicode) on Ubuntu 9.04.

Paul

--

>>> print sys.version
2.6.2 (release26-maint, Apr 19 2009, 01:56:41)
[GCC 4.3.3]
>>> print wx.version()
2.8.9.1 (gtk2-unicode)

--

import Image
import random
import time
import wx

def pilToImage(pil):
  image = wx.EmptyImage(pil.size[0], pil.size[1])
  image.SetData(pil.convert('RGB').tostring())
  return image

def imageToBitmap(image):
  return image.ConvertToBitmap()

def pilToBitmap(pil):
  return imageToBitmap(pilToImage(pil))
class Item(wx.Panel):
  """Displays an image that is computed on the fly"""
   def __init__(self, parent):
    wx.Panel.__init__(self, parent, style=wx.SIMPLE_BORDER | wx.FULL_REPAINT_ON_RESIZE)
    self.Bind(wx.EVT_PAINT, self.OnPaint)

  def OnPaint(self, event):
    dc = wx.PaintDC(self)
    dc.BeginDrawing()
    # the following accounts for computation time
    time.sleep(0.1 + 0.1 * random.random())
    image = Image.new('F', dc.GetSizeTuple())
    # remove the following two lines, and the flicker disappears
    bitmap = pilToBitmap(image)
    dc.DrawBitmap(bitmap, 0, 0)
    dc.EndDrawing()

class MyFrame(wx.Frame):
  """A display of three Item objects"""
   def __init__(self, *args, **kwargs):
    wx.Frame.__init__(self, *args, **kwargs)

    self.view = [Item(self),Item(self),Item(self)];
    self.view[0].SetBackgroundColour(wx.Colour(100,0,0))
    self.view[1].SetBackgroundColour(wx.Colour(0,100,0))
    self.view[2].SetBackgroundColour(wx.Colour(0,0,100))

    sizerVer = wx.BoxSizer(wx.VERTICAL)
    sizerVer.Add(self.view[2], proportion = 1, flag = wx.EXPAND | wx.BOTTOM, border = 5)
    sizerVer.Add(self.view[1], proportion = 1, flag = wx.EXPAND , border = 5)
        sizerHor = wx.BoxSizer(wx.HORIZONTAL)
    sizerHor.Add(self.view[0], proportion = 1, flag = wx.EXPAND | wx.ALL, border = 5)
    sizerHor.Add(sizerVer, proportion = 1, flag = wx.EXPAND | wx.TOP > wx.BOTTOM | wx.RIGHT, border = 5)
    self.SetSizer(sizerHor)

myApp = wx.App(0)
frame = MyFrame(None, wx.ID_ANY, "Main Frame")
frame.Show()
myApp.MainLoop()

------------------------------------------------------------------------

_______________________________________________
wxpython-users mailing list
wxpython-users@lists.wxwidgets.org
http://lists.wxwidgets.org/mailman/listinfo/wxpython-users

--
Christopher Barker, Ph.D.
Oceanographer

Emergency Response Division
NOAA/NOS/OR&R (206) 526-6959 voice
7600 Sand Point Way NE (206) 526-6329 fax
Seattle, WA 98115 (206) 526-6317 main reception

Chris.Barker@noaa.gov

user790 user790 wrote:

I have an interface showing some images that are computed on the fly, e.g. when the user resize the window. The computations are not quick, i.e. each takes a fraction of second. Because of that, their is some significant flickering when the window is resized.

I am providing an example below. The time.sleep() simulates the computation time required to generate the images.

I am looking for a way to avoid this flicker. Meaning, the content would refresh at once, when all computations are done. Interestingly, this is exactly what happens when images are not involved: if I keep the time.sleep() in the example below but remove relevant lines, the entire content of the window is updated at once, no matter the differences in computatino time of the subframes.

To be more precise, if I comment out the bitmap drawing part only ("dc.DrawBitmap(bitmap, 0, 0)"), the flickering still occurs. The flickering remains till I remove the call to image.ConvertToBitmap(). I do not know why.

Anyway, any help would be appreciated.

Probably the easiest thing to do is to not compute the bitmaps in the EVT_PAINT handler. Have them already available (i.e. move the computation to an EVT_IDLE or timer handler, or maybe trigger it from the EVT_SIZE handler) and then in the paint handler do nothing but draw them.

···

--
Robin Dunn
Software Craftsman
http://wxPython.org Java give you jitters? Relax with wxPython!

This is great. One thing that I had to add to make it work, is to make sure I do not redraw the image twice at the same size, because with this modification, the paint event is called three times for each item, instead of one before.

Paul

···

2009/5/8 Robin Dunn robin@alldunn.com

Probably the easiest thing to do is to not compute the bitmaps in the EVT_PAINT handler. Have them already available (i.e. move the computation to an EVT_IDLE or timer handler, or maybe trigger it from the EVT_SIZE handler) and then in the paint handler do nothing but draw them.

import Image
import random
import time
import wx

def pilToImage(pil):
image = wx.EmptyImage(pil.size[0], pil.size[1])
image.SetData(pil.convert(‘RGB’).tostring())

return image

def imageToBitmap(image):
return image.ConvertToBitmap()

def pilToBitmap(pil):
return imageToBitmap(pilToImage(pil))

class Item(wx.Panel):
“”“Displays an image that is computed on the fly”“”

def init(self, parent, itemid):
wx.Panel.init(self, parent, style=wx.SIMPLE_BORDER | wx.FULL_REPAINT_ON_RESIZE)
self.Bind(wx.EVT_PAINT, self.OnPaint)
self.Bind(wx.EVT_SIZE, self.OnSize)

self.itemid = itemid
self.bitmapsize = (-1,-1)

def UpdateBitmap(self):
if self.GetClientSize() != self.bitmapsize:
self.bitmapsize = self.GetClientSize()
print "update " + str(self.itemid) + " " + str(self.GetClientSize())

  time.sleep(0.1 + 0.1 * random.random())
  image = Image.new('F', self.GetClientSize())
  self.bitmap = pilToBitmap(image)
else:
  print "skip " + str(self.itemid)

def OnSize(self, event):
# Update bitmap
self.UpdateBitmap()
# Trigger paint event
cmd = wx.CommandEvent(wx.EVT_PAINT.evtType[0])
cmd.SetEventObject(self)
cmd.SetId(self.GetId())

self.GetEventHandler().ProcessEvent(cmd)

def OnPaint(self, event):
dc = wx.PaintDC(self)
dc.BeginDrawing()
dc.DrawBitmap(self.bitmap, 0, 0)
dc.EndDrawing()

class MyFrame(wx.Frame):

“”“A display of three Item objects”“”

def init(self, *args, **kwargs):

wx.Frame.__init__(self, *args, **kwargs)

self.view = [Item(self,0),Item(self,1),Item(self,2)];
self.view[0].SetBackgroundColour(wx.Colour(100,0,0))
self.view[1].SetBackgroundColour(wx.Colour(0,100,0))

self.view[2].SetBackgroundColour(wx.Colour(0,0,100))

sizerVer = wx.BoxSizer(wx.VERTICAL)

sizerVer.Add(self.view[2], proportion = 1, flag = wx.EXPAND | wx.BOTTOM, border = 5)

sizerVer.Add(self.view[1], proportion = 1, flag = wx.EXPAND , border = 5)



sizerHor = wx.BoxSizer(wx.HORIZONTAL)

sizerHor.Add(self.view[0], proportion = 1, flag = wx.EXPAND | wx.ALL, border = 5)

sizerHor.Add(sizerVer,     proportion = 1, flag = wx.EXPAND | wx.TOP | wx.BOTTOM | wx.RIGHT, border = 5)

self.SetSizer(sizerHor)

myApp = wx.App(0)

frame = MyFrame(None, wx.ID_ANY, “Main Frame”)

frame.Show()

myApp.MainLoop()

user790 user790 wrote:

2009/5/8 Robin Dunn <robin@alldunn.com <mailto:robin@alldunn.com>>

    Probably the easiest thing to do is to not compute the bitmaps in
    the EVT_PAINT handler. Have them already available (i.e. move the
    computation to an EVT_IDLE or timer handler, or maybe trigger it
    from the EVT_SIZE handler) and then in the paint handler do nothing
    but draw them.

This is great. One thing that I had to add to make it work, is to make sure I do not redraw the image twice at the same size, because with this modification, the paint event is called three times for each item, instead of one before.

     def OnSize(self, event):
    # Update bitmap
    self.UpdateBitmap()

    # Trigger paint event
    cmd = wx.CommandEvent(wx.EVT_PAINT.evtType[0])
    cmd.SetEventObject(self)
    cmd.SetId(self.GetId())
    self.GetEventHandler().ProcessEvent(cmd)

Do not do this. The paint event will happen naturally from the system after a size event anyway. If you ever want to trigger the natural sending of a paint event then use Refresh(). The system will collect the refresh events and combine the update regions (if any are specified) and then send a single event when it is time to paint. If you ever want to cause a paint to happen immediately then also call Update.

···

--
Robin Dunn
Software Craftsman
http://wxPython.org Java give you jitters? Relax with wxPython!