Reduce flicker in wx.ScrolledWindow

Hi!

I have a ScrolledWindow that holds a big map and on it I need to draw different kind of information (shapes and texts). The thing is that the bitmap map is static, but the other graphics drawn onto it are dynamically changing.
How can I paint the window without causing flicker?
I know that I have to implement double buffering, but how. If I repaint the whole map with the other graphics, I still get flicker when using double buffer.

Thanks in advance
Romi

Romi Agar wrote:

Hi!

I have a ScrolledWindow that holds a big map and on it I need to draw different kind of information (shapes and texts). The thing is that the bitmap map is static, but the other graphics drawn onto it are dynamically changing.
How can I paint the window without causing flicker?
I know that I have to implement double buffering, but how. If I repaint the whole map with the other graphics, I still get flicker when using double buffer.

The key to eliminating flicker is to reduce the drawing of the window to a single operation, which is usually a blit of a bitmap that holds the entire drawn state of the window. Because of the design and history of the windows platform it is possible to momentarily see the intermediate steps of painting the window, and the transitions between those steps and the final result are what we see as flicker. So reducing the whole thing to a single operation that simply fills the whole window with the final result means that there is no intermediate steps visible.

If you are already using a buffer bitmap and your paint handler is simply drawing that bitmap, but you are still seeing flicker, then you probably also need to eliminate the erasure of the background of the window. To do that bind a handler to the EVT_ERASE_BACKGROUND event and have the handler do nothing but "pass".

···

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

Romi Agar wrote:

I have a ScrolledWindow that holds a big map and on it I need to draw different kind of information (shapes and texts). The thing is that the bitmap map is static, but the other graphics drawn onto it are dynamically changing.
How can I paint the window without causing flicker?

I know that I have to implement double buffering, but how.

Have you read this:

http://wiki.wxpython.org/DoubleBufferedDrawing

If I repaint the whole map with the other graphics, I still get flicker when using double buffer.

I think you are probably inadvertently clearing the window first, then re-painting.

Also, depending non how complex that addition stuff is you may want blit the map to the windows, then draw the stuff on it, or blit the map to another buffer, then draw the other stuff on that, then blit the whole thing to the window -- kind of a triple buffer.

You also might want to check out wx.lib.floatcanvas -- if you put your map in as a ScaledBitmap, you can put all sorts of other stuff on top of it, and zooming, panning, buffer are all taken care of for you.

-Chris

···

--
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

Christopher Barker wrote:

...
-Chris

I managed to fix flicker on Windows with this single LOC:

self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM)

(in the scrolledpanel's class). I was having no issues with gtk

Robin Dunn wrote:

Romi Agar wrote:

Hi!

I have a ScrolledWindow that holds a big map and on it I need to draw different kind of information (shapes and texts). The thing is that the bitmap map is static, but the other graphics drawn onto it are dynamically changing.
How can I paint the window without causing flicker?
I know that I have to implement double buffering, but how. If I repaint the whole map with the other graphics, I still get flicker when using double buffer.

The key to eliminating flicker is to reduce the drawing of the window to a single operation, which is usually a blit of a bitmap that holds the entire drawn state of the window. Because of the design and history of the windows platform it is possible to momentarily see the intermediate steps of painting the window, and the transitions between those steps and the final result are what we see as flicker. So reducing the whole thing to a single operation that simply fills the whole window with the final result means that there is no intermediate steps visible.

If you are already using a buffer bitmap and your paint handler is simply drawing that bitmap, but you are still seeing flicker, then you probably also need to eliminate the erasure of the background of the window. To do that bind a handler to the EVT_ERASE_BACKGROUND event and have the handler do nothing but "pass".

Background erasure has already been dealt with and it didn't give a noticeable difference, neither did SetBackgroundStyle(wx.BG_STYLE_CUSTOM). I have read the article DoubleBufferedDrawing - wxPyWiki and have adapted it a bit for scrolled window. I also added buffer flipping. This way it does not cause noticeable flickers any more, but the buffers get filled with old draws and soon the whole thing is a mess.
So the question still remains, how could I keep at least one buffer clean with the base bitmap. Because if I used dc.DrawBitmap with every draw, it would still cause flicker. Maybe there is a quick way to copy the "clean" buffer to another that will get drawn on?

P.S. I might port the code to wx.lib.floatcanvas sometime in the future, but right now I seem to be close to the final solution with ScrolledWindow. Learning and porting to floatcanvas would seem to take more time that I currently would like to spend.

Hi,

Robin Dunn wrote:

Romi Agar wrote:

Hi!

I have a ScrolledWindow that holds a big map and on it I need to draw
different kind of information (shapes and texts). The thing is that the
bitmap map is static, but the other graphics drawn onto it are dynamically
changing.
How can I paint the window without causing flicker?
I know that I have to implement double buffering, but how. If I repaint
the whole map with the other graphics, I still get flicker when using double
buffer.

The key to eliminating flicker is to reduce the drawing of the window to a
single operation, which is usually a blit of a bitmap that holds the entire
drawn state of the window. Because of the design and history of the
windows platform it is possible to momentarily see the intermediate steps of
painting the window, and the transitions between those steps and the final
result are what we see as flicker. So reducing the whole thing to a single
operation that simply fills the whole window with the final result means
that there is no intermediate steps visible.

If you are already using a buffer bitmap and your paint handler is simply
drawing that bitmap, but you are still seeing flicker, then you probably
also need to eliminate the erasure of the background of the window. To do
that bind a handler to the EVT_ERASE_BACKGROUND event and have the handler
do nothing but "pass".

Background erasure has already been dealt with and it didn't give a
noticeable difference, neither did SetBackgroundStyle(wx.BG_STYLE_CUSTOM).
I have read the article DoubleBufferedDrawing - wxPyWiki and
have adapted it a bit for scrolled window. I also added buffer flipping.
This way it does not cause noticeable flickers any more, but the buffers get
filled with old draws and soon the whole thing is a mess.
So the question still remains, how could I keep at least one buffer clean
with the base bitmap. Because if I used dc.DrawBitmap with every draw, it
would still cause flicker. Maybe there is a quick way to copy the "clean"
buffer to another that will get drawn on?

I am not sure it will work or it will reduce flicker, but you may try
drawing the big map *inside* the EVT_ERASE_BACKGROUND and then using
the code in the wxPython demo called "DragImage" ("Using Images" =>
"DragImage"). So, your events may look like this:

    # Clears the background, then redraws it. If the DC is passed, then
    # we only do so in the area so designated. Otherwise, it's the whole thing.
    def OnEraseBackground(self, event):

        dc = event.GetDC()

        if not dc:
            dc = wx.ClientDC(self)
            rect = self.GetUpdateRegion().GetBox()
            dc.SetClippingRect(rect)

        self.DrawMyBigMap(dc)

    # Fired whenever a paint event occurs
    def OnPaint(self, event):

        dc = wx.PaintDC(self)
        self.PrepareDC(dc)
        self.DrawAllMyOtherGraphics(dc)

HTH.

Andrea.

"Imagination Is The Only Weapon In The War Against Reality."
http://xoomer.alice.it/infinity77/

···

On Wed, Apr 22, 2009 at 3:07 PM, Romi Agar wrote:

Romi Agar wrote:

This way it does not cause noticeable flickers any more, but the buffers get filled with old draws and soon the whole thing is a mess.

It may be time for:

http://wiki.wxpython.org/MakingSampleApps

So the question still remains, how could I keep at least one buffer clean with the base bitmap. Because if I used dc.DrawBitmap with every draw, it would still cause flicker.

You need another buffer:

one holds the basemap -- that's it, and you never draw to that one.

one is the "window buffer".

When you need to change what's there, you draw the basemap to the window buffer, then you draw the other stuff on top of it, then you draw the window buffer to the Window.

Are your buffers the full size of your image, so that the ScrolledWindow only has to display a portion of it? If you have enough memory for that, is should be speedy.

Learning and porting to floatcanvas would seem to take more time that I currently would like to spend.

It would probably only make sense if it has other features that you need, too -- zooming, captuing clicks on objects, etc.

-Chris

···

--
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

Christopher Barker wrote:

Romi Agar wrote:

This way it does not cause noticeable flickers any more, but the buffers get filled with old draws and soon the whole thing is a mess.

It may be time for:

http://wiki.wxpython.org/MakingSampleApps

So the question still remains, how could I keep at least one buffer clean with the base bitmap. Because if I used dc.DrawBitmap with every draw, it would still cause flicker.

You need another buffer:

one holds the basemap -- that's it, and you never draw to that one.

one is the "window buffer".

When you need to change what's there, you draw the basemap to the window buffer, then you draw the other stuff on top of it, then you draw the window buffer to the Window.

Are your buffers the full size of your image, so that the ScrolledWindow only has to display a portion of it? If you have enough memory for that, is should be speedy.

Learning and porting to floatcanvas would seem to take more time that I currently would like to spend.

It would probably only make sense if it has other features that you need, too -- zooming, captuing clicks on objects, etc.

-Chris

A sample application can be found at http://rmg.planet.ee/python/
About my platform:
    Python 2.6.1, wxPython 2.8.9.2 (msw-ansi) on Vista 32bit
The flicker is best reproduced when quickly panning the image.

Romi Agar wrote:

A sample application can be found at http://rmg.planet.ee/python/
About my platform:
   Python 2.6.1, wxPython 2.8.9.2 (msw-ansi) on Vista 32bit
The flicker is best reproduced when quickly panning the image.

Testing on OS-X, there is no flicker, but neither can you quickly do anything -- the whole thing is very pokey. dragging the scrollbars it can't keep up or resizing the window, or...

I think part of the problem is that when scrolling or re-sizing there are a lot of events, and the drawing can't keep up. One solution to this os to put in a time -- when there is an event that requires a re-draw, wait a bit, and only re-draw if another event doesn't come though right away. That way it doesn't try to re-draw 100 times a second.

A few other comments:

         self.dc = wx.MemoryDC()

It's generally considered not a good idea to keep dcs around -- you only need to keep the buffer bitmap -- you can re-create the dc when you need to.

I'm not sure what the point of the backbuffer is -- isn't that what self.bmp already is?

I've enclosed a slightly simplified version.

Just for fun, I've enclosed a version using FloatCanvas -- it uses the somwhat experimental "ScaledBitmap2", though it works fine for this. It can be sped up some if you use lower quality scaling for highly zoomed in views (that required going into the FloatCanvas code).

ScaledBitmap2 is better for large bitmaps, as it only scales the part of the image that you can see -- the old ScaledBitmap scales the whole thing -- fine for little icons and the like, but not a good idea for large images.

SampleApp.py (5.73 KB)

ScaledBitmapDemo.py (1.83 KB)

···

--
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

Somehow your edited (simplified) version of SimpleApp has around 45MB larger memory usage on my pc. Flicker still remains.

Christopher Barker wrote:

···

Romi Agar wrote:

A sample application can be found at http://rmg.planet.ee/python/
About my platform:
   Python 2.6.1, wxPython 2.8.9.2 (msw-ansi) on Vista 32bit
The flicker is best reproduced when quickly panning the image.

Testing on OS-X, there is no flicker, but neither can you quickly do anything -- the whole thing is very pokey. dragging the scrollbars it can't keep up or resizing the window, or...

I think part of the problem is that when scrolling or re-sizing there are a lot of events, and the drawing can't keep up. One solution to this os to put in a time -- when there is an event that requires a re-draw, wait a bit, and only re-draw if another event doesn't come though right away. That way it doesn't try to re-draw 100 times a second.

A few other comments:

        self.dc = wx.MemoryDC()

It's generally considered not a good idea to keep dcs around -- you only need to keep the buffer bitmap -- you can re-create the dc when you need to.

I'm not sure what the point of the backbuffer is -- isn't that what self.bmp already is?

I've enclosed a slightly simplified version.

Just for fun, I've enclosed a version using FloatCanvas -- it uses the somwhat experimental "ScaledBitmap2", though it works fine for this. It can be sped up some if you use lower quality scaling for highly zoomed in views (that required going into the FloatCanvas code).

ScaledBitmap2 is better for large bitmaps, as it only scales the part of the image that you can see -- the old ScaledBitmap scales the whole thing -- fine for little icons and the like, but not a good idea for large images.

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

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

Romi Agar wrote:

Somehow your edited (simplified) version of SimpleApp has around 45MB larger memory usage on my pc.

that's odd -- I thought I'd removed an extra bitmap...

Flicker still remains.

I think some of the issue may be trying to re-draw faster than it can be done -- when the scrollbars are used, or the window resized, there can be a lot of events -- if they come faster than the drawing can happen, things get ugly.

As I mentioned:

One solution to this is to put in a timer -- when there is an event that requires a re-draw, wait a bit, and only re-draw if another event doesn't come though right away. That way it doesn't try to re-draw 100 times a second.

You can see how this is done in wx.lib.floatcanvas.FloatCanvas.py in the OnSize method.

-Chris

···

--
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