When to use wx.MemoryDC, wxBitmap, wx.Image

Hi all, I have a few questions regarding how and when to use wxMemoryDC, wxBitmap, and wxImage.

I'm not looking for any specific recipe, but a general approach to the following set of animation goals (all files are '.bmp' files, and this is on a Windows system; all timers are a wx.Timer):

1. I need to take a .bmp file and scroll it horizontally in a panel, each frame responding to a timer event
2. I need to take a series of .bmp files as sprite frames, and flip through them quickly, each frame to a timer event
3. I need to associate a binary mask with one bitmap, then draw it on top of another bitmap, scrolling the top (masked) bitmap a little bit each frame while keeping the background still. Again, one frame per timer event.
4. I need to take a background image, then draw an alpha image on top, and scroll the background behind the alpha image.
5. I need to blend two alpha images back and forth
6. Maybe other stuff as 2d animations, generally involving mixing of bitmaps, alpha, and transformations, without a whole lot of wx.Pen and wx.Brush usage.

So far what I've done is:
1. Create a wx.Panel subclass with a wx.MemoryDC.
2. For each 1-5 above, create an animation_manager class which owns n+1 wx.Bitmaps and n wx.MemoryDC's, where n is the number of original BMP files required. n wx.Bitmaps are loaded up with the BMP file and associated with the n wx.MemoryDC's in the class, and the third wx.Bitmap is associated (wx.DC.SelectObject()) with the wx.MemoryDC in the panel subclass.
3. Create a wx.Timer to click every 16ms
4. During OnTimer call the animation_manager to generate the next frame and build up the newly rendered image with wx.DC.DrawBitmap() or wx.DC.Blit(), on the scratch-pad bitmap. Now the panel's wxMemoryDC points to the final scratch-pad bitmap.
5. Call Refresh() from OnTimer()
6. In OnPaint() call wx.DC.Blit from the panel's wx.MemoryDC (which points to the scratch pad) to the wx.PaintDC which is created locally.

So this is very confusing and I'm not sure when to use wx.DC .Blit versus wx.DC.DrawBitmap. They both seem to work, except neither seems to do well with the alpha image, and in one case mixing the two around actually seems to crash the program (when used with an alpha-enabled .bmp bitmap). I read that only png supports alpha channels.

Although I am (I think) doing double-buffering by only blitting or drawbitmap'ing a single buffer during the OnPaint event, the animation still looks rough. I have not seen any place where I can force the thing to synchronize to the monitor for smoothest effect. I am capturing and then ignoring the OnErase() event as recommended in various places.

It seems at this point I think maybe I need to use wx.Image and do the alpha myself, but I'm not sure if that's the best approach.

So can a wx guru please advise? When ought I use wx.MemoryDC, wx.Bitmap, and wx.Image?

Thanks a lot
michael

Thanks, I’ll try this (False) argument later today.

M.S.

(((BTW, since I’m new to this list, how do I make sure my email address
doesn’t appear anywhere? I risked using my real email address since
it was a bit tedious to set up an anonymous outgoing account from my
email client, (since the from:

field is required to match the subscription email), and I clicked “yes”
for obscuring my email from the roster, but I’m not sure that obscures
my real email from the individual messages…)))

Gre7g Luterman wrote:

···

wxpython-users@lists.wxwidgets.orghttp://lists.wxwidgets.org/mailman/listinfo/wxpython-users

Michael wrote:

BTW, since I'm new to this list, how do I make sure my email address doesn't appear anywhere? I risked using my real email address since it was a bit tedious to set up an anonymous outgoing account from my email client, (since the from:
field is required to match the subscription email), and I clicked "yes" for obscuring my email from the roster, but I'm not sure that obscures my real email from the individual messages...

No, it doesn't. We should always have the option of sending you a reply privately; indeed, many lists set the private reply as the default.

If you have selected the "obscure" option, then your email address can only be seen by list members.

···

--
Tim Roberts, timr@probo.com
Providenza & Boekelheide, Inc.

DrawBitmap is simpler, I always use it where I can. Where you can't is:

When you want to scale the bitmap, or take a subset of the bitmap. Also,
as someone pointed out, there are problems using DrawBitmap when the
bitmap is currently selected into a MemoryDC -- however, you shouldn't
keep those around anyway, so it's not a common problem.

Thanks, this actually helped a lot. My code is simpler and cleaner
without keeping the wx.MemoryDCs around as member objects.

I'm curious how efficient it is to create and destroy DCs all the time.
I'm also wondering how the wxPython system creates derivative classes in
Python of classes that are defined in C++.

Now I need to figure out why my BMP is not working with alpha.
Unfortunately, the BMP format is what I must work with, as I am not
generating the original images.

Michael

It seems at this point I think maybe I need to use wx.Image and do the
alpha myself, but I'm not sure if that's the best approach.

probably not, as to draw a wx.Image, it needs to be converted to a
wx.Bitmap anyway.

I hope this helps.

I do suggest you make this as small as possible, but still a complete
app, and post it here for more feedback, if needed.

I figured out how to do the alpha stuff finally. It took about 2 hours to
generate like 4 lines of code in SetAlpha(self, bmp): below. Turns out
the .chm help file installed on my machine is not complete, and I found
some more stuff here:
wxPython API Documentation — wxPython Phoenix 4.2.2 documentation which totally
helped.

Anywhere here is the new class (file names changed to protect the guilty).
Thanks for your help. (BTW list comprehensions RULE!)

Michael

class AlphaScroller(AnimationManager):
  # Move the background under the foreground.
  def __init__(self):
    AnimationManager.__init__(self)
    self.fore_filename = 'C:/Device_Benchmark_2_2.bmp'
    self.back_filename = 'C:/Device_Benchmark_2_1.bmp'
    self.fore_bmp = LoadBitmapBMP(self.fore_filename)
    self.back_bmp = LoadBitmapBMP(self.back_filename)
    w,h = (320, 240)
    self.mix_bmp = wx.EmptyBitmap(w, h, self.back_bmp.GetDepth())
    self.x_bouncer = make_bouncer(0, 320, step=5)
    self.fore_bmp = self.SetAlpha(self.fore_bmp)

  def SetAlpha(self, bmp):
    # Create image from bitmap first, do stuff, then convert back
    # Use green channel data as alpha channel
    # Use list comprehension to select every third element in RGB sequence
    # Requires string for data stream
    # These four lines took about 2 hours to figure out!
    im = wx.ImageFromBitmap(bmp)
    grn = ''.join([gg for i,gg in enumerate(im.GetData()) if not (i+2)%3])
    im.SetAlphaData(grn)
    return im.ConvertToBitmap()

  def DrawNextFrame(self):
    # Must render image here, as follows:
    # Create local DC for mix bmp
    # Draw the background as a sub-bitmap of the background bitmap
    # Draw the foreground with transparency turned on (not used for alpha
apparently)

    dc = wx.MemoryDC(self.mix_bmp)
    w,h = tuple(self.back_bmp.GetSize())
    xx = self.x_bouncer.next()
    fb = self.fore_bmp
    bb = self.back_bmp
    w,h = (320,240)
    dc.DrawBitmap(bb.GetSubBitmap(wx.Rect(xx, 0, w, h)), 0, 0)
    dc.DrawBitmap(fb, 0, 0, False) #<-- use transparency mask, doesn't
affect alpha, apparently
    return True

  def GetBitmap(self):
    return self.mix_bmp
  def GetWidth(self):
    return self.back_bmp.Size.x
  def GetHeight(self):
    return self.back_bmp.Size.y

Christopher Barker wrote:

1) Call Refresh() and Update() in the timer, and do the drawing in the Paint handler. This is "supposed" to be the best way to do it -- it allows drawing to be synchronized properly with the rest of the system drawing. However, I've found that it can be not all that responsive, at least on OS-X

Using Refresh with Update should be about equivalent to drawing with a wx.ClientDC. The paint event handler will be called from within the Update call and Update won't return until the paint handler is finished. In both cases the display pipeline needs to be aborted and restarted to get the "draw NOW" type of functionality. On the other hand, just calling Refresh will allow the system to keep its display pipeline active and intact, and whatever optimizations it is doing for system-wide double buffering, synchronized screen updates, etc. will not be interrupted, and the redraw of the window can be done in an optimized manner. Since most systems are doing something like that these days it is considered a better practice to not use Update or immediate drawing with a wx.ClientDC and let the system take care of it.

···

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

Robin Dunn wrote:

Using Refresh with Update should be about equivalent to drawing with a wx.ClientDC.

Should be, maybe, but that hasn't been the case for me, at least under OS-X. I haven't tested with the very latest version of wx, though.

just calling Refresh will allow the system to keep its display pipeline active and intact, and whatever optimizations it is doing for system-wide double buffering, synchronized screen updates, etc. will not be interrupted, and the redraw of the window can be done in an optimized manner. Since most systems are doing something like that these days it is considered a better practice to not use Update or immediate drawing with a wx.ClientDC and let the system take care of it.

This approach simply hasn't worked for me, at least with animation-like applications, where you want the drawing done more than a few times per second.

Maybe it's been improved with recent changes to wx -- I'll have to write up a simple test code some day.

So I guess my advise is:

1) Try doing all your final drawing from an EVT_PAINT handler, using just Refresh(), and see if that meets your needs.

2) If not, then add an Update() and see if that does it.

3) if it's still not fast enough for you, re-factor to use a wx.ClientDC when you need stuff drawn.

-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