wx.Bitmap, wx.GraphicsContext and bitmap/alpha stacking

I am stuck with the problem of generating bitmaps with alpha easily. I
want to generate bitmaps with alpha/mask. I want antialiased drawing.
The problem is to let the background shine through properly.

Using wx.GCDC for painting is too slow using it in a wx.PaintDC (too
many elements). I used a GraphicsContext on a MemoryDC instead.

I need to perform the following steps:

1) generate the wx.Bitmap (see createarrow). This generates a BMP with
a yellow, bordered triangle which has the outer pixels not set
(wx.TRANSPARENT_BRUSH). When you look at it in e.g. GIMP, the
checkered background shows through, so it seems to have 'holes' in it
(but no alpha channel).

2) Create another bitmap for a wx.StaticBitmap. On this bitmap all
sorts of pre-generated bitmaps are stacked atop (two yellow triangles
in this example).

This leads to my problem: When you run the attached sample, in the
lines

        dc.DrawBitmap(subbmp, 10, 1, True)
        dc.DrawBitmap(subbmp, 20, 1, True)

the green background shows through around the stacked arrow instead
the bitmap, which has been drawn first. Also the

        dc.DrawRectangle(0, 0, self.statbmpwidth, self.statbmpheight)

seems to have no effect?

My question:
How to properly generate in-memory-bitmaps *with* smooth drawing which
can be stacked showing the underlying bitmaps where there are 'holes'?

With best regards,

# -*- coding: utf-8 -*-
# tested on wxPython 2.8.12./Win7
import wx

class testframe(wx.Frame):
    def __init__(self, *args, **kwds):
        wx.Frame.__init__(self, *args, **kwds)

        pnl = wx.Panel(self, -1)
        self.pnl = pnl
        pnl.SetBackgroundColour(wx.GREEN)

        self.statbmpheight = 32
        self.statbmpwidth = 128

        self.initbmp()

        sz = wx.BoxSizer(wx.VERTICAL)
        sz.Add(self.statbmpm, 0, wx.EXPAND|wx.ALL, 0)
        sz.Add((1,1), 1, wx.EXPAND|wx.ALL, 4)

        szmain = wx.BoxSizer(wx.HORIZONTAL)
        szmain.Add(sz, 1, wx.EXPAND, 0)
        szmain.Add((1,1), 1, wx.EXPAND, 0)
        pnl.SetSizer(szmain)
        szmain.Fit(self)

        self.Bind(wx.EVT_SIZE, self.onsize)

    def initbmp(self):

        self.arrow = self.createarrow()

        bmp = wx.EmptyBitmap(self.statbmpwidth, self.statbmpheight)
        self.paintonbmp(bmp, self.arrow)

        self.statbmpm = wx.StaticBitmap(self.pnl, -1, bmp)

        self.SetMinSize((self.statbmpwidth + 16, self.statbmpheight +
10))

    def paintonbmp(self, bmp, subbmp):
        dc = wx.MemoryDC()
        dc.SelectObject(bmp)

        gc = wx.GraphicsContext.Create(dc)

        systemgrey =
wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE)

        dc.SetBrush(wx.Brush(systemgrey))
        gc.SetBrush(wx.Brush(systemgrey))

        dc.SetPen(wx.Pen(systemgrey))
        dc.DrawRectangle(0, 0, self.statbmpwidth, self.statbmpheight)

        dc.DrawBitmap(subbmp, 10, 1, True)
        dc.DrawBitmap(subbmp, 20, 1, True)

        dc.SelectObject(wx.NullBitmap)

    def onsize(self, evt):
        self.Refresh()
        evt.Skip()

    def createarrow(self):
        bmp = wx.EmptyBitmap(22, 22)

        dc = wx.MemoryDC()
        dc.SelectObject(bmp)

        gc = wx.GraphicsContext.Create(dc)
        dc.Clear()

        gc.SetBrush(wx.TRANSPARENT_BRUSH)
        gc.DrawRectangle(0,0,32,32)

        mypath = gc.CreatePath()
        mypath.MoveToPoint(1, 11)
        mypath.AddLineToPoint(21, 1)
        mypath.AddLineToPoint(21, 21)
        mypath.CloseSubpath()

        gc.StrokePath(mypath)
        gc.SetPen(wx.Pen('black', 2))
        gc.SetBrush(wx.Brush('yellow'))
        gc.StrokePath(mypath)
        gc.FillPath(mypath)

        dc.SelectObject(wx.NullBitmap)

        bmp.SaveFile('arrow.bmp', wx.BITMAP_TYPE_BMP)

        return bmp

if __name__ == '__main__':
    app = wx.App(0)
    frame1 = testframe(None, -1, 'mytestframe')
    frame1.Show()

# import wx.lib.inspection as wxli
# wxli.InspectionTool().Show()
    app.MainLoop()

Hi,

I am stuck with the problem of generating bitmaps with alpha easily. I
want to generate bitmaps with alpha/mask. I want antialiased drawing.
The problem is to let the background shine through properly.

Using wx.GCDC for painting is too slow using it in a wx.PaintDC (too
many elements). I used a GraphicsContext on a MemoryDC instead.

I need to perform the following steps:

1) generate the wx.Bitmap (see createarrow). This generates a BMP with
a yellow, bordered triangle which has the outer pixels not set
(wx.TRANSPARENT_BRUSH). When you look at it in e.g. GIMP, the
checkered background shows through, so it seems to have 'holes' in it
(but no alpha channel).

2) Create another bitmap for a wx.StaticBitmap. On this bitmap all
sorts of pre-generated bitmaps are stacked atop (two yellow triangles
in this example).

This leads to my problem: When you run the attached sample, in the
lines

       dc.DrawBitmap(subbmp, 10, 1, True)
       dc.DrawBitmap(subbmp, 20, 1, True)

the green background shows through around the stacked arrow instead
the bitmap, which has been drawn first. Also the

       dc.DrawRectangle(0, 0, self.statbmpwidth, self.statbmpheight)

seems to have no effect?

My question:
How to properly generate in-memory-bitmaps *with* smooth drawing which
can be stacked showing the underlying bitmaps where there are 'holes'?

Make sure you're creating the wx.Bitmap with a depth of 32 bits, otherwise it won't have an alpha channel. And since you're on 2.8, I think on Windows you need to also call bitmap.UseAlpha() after creating it to make sure it initializes the alpha channel. (In 2.9, this is no longer needed.)

Regards,

Kevin

···

On Sep 14, 2012, at 5:23 AM, nepix32 wrote:

With best regards,

# -*- coding: utf-8 -*-
# tested on wxPython 2.8.12./Win7
import wx

class testframe(wx.Frame):
   def __init__(self, *args, **kwds):
       wx.Frame.__init__(self, *args, **kwds)

       pnl = wx.Panel(self, -1)
       self.pnl = pnl
       pnl.SetBackgroundColour(wx.GREEN)

       self.statbmpheight = 32
       self.statbmpwidth = 128

       self.initbmp()

       sz = wx.BoxSizer(wx.VERTICAL)
       sz.Add(self.statbmpm, 0, wx.EXPAND|wx.ALL, 0)
       sz.Add((1,1), 1, wx.EXPAND|wx.ALL, 4)

       szmain = wx.BoxSizer(wx.HORIZONTAL)
       szmain.Add(sz, 1, wx.EXPAND, 0)
       szmain.Add((1,1), 1, wx.EXPAND, 0)
       pnl.SetSizer(szmain)
       szmain.Fit(self)

       self.Bind(wx.EVT_SIZE, self.onsize)

   def initbmp(self):

       self.arrow = self.createarrow()

       bmp = wx.EmptyBitmap(self.statbmpwidth, self.statbmpheight)
       self.paintonbmp(bmp, self.arrow)

       self.statbmpm = wx.StaticBitmap(self.pnl, -1, bmp)

       self.SetMinSize((self.statbmpwidth + 16, self.statbmpheight +
10))

   def paintonbmp(self, bmp, subbmp):
       dc = wx.MemoryDC()
       dc.SelectObject(bmp)

       gc = wx.GraphicsContext.Create(dc)

       systemgrey =
wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE)

       dc.SetBrush(wx.Brush(systemgrey))
       gc.SetBrush(wx.Brush(systemgrey))

       dc.SetPen(wx.Pen(systemgrey))
       dc.DrawRectangle(0, 0, self.statbmpwidth, self.statbmpheight)

       dc.DrawBitmap(subbmp, 10, 1, True)
       dc.DrawBitmap(subbmp, 20, 1, True)

       dc.SelectObject(wx.NullBitmap)

   def onsize(self, evt):
       self.Refresh()
       evt.Skip()

   def createarrow(self):
       bmp = wx.EmptyBitmap(22, 22)

       dc = wx.MemoryDC()
       dc.SelectObject(bmp)

       gc = wx.GraphicsContext.Create(dc)
       dc.Clear()

       gc.SetBrush(wx.TRANSPARENT_BRUSH)
       gc.DrawRectangle(0,0,32,32)

       mypath = gc.CreatePath()
       mypath.MoveToPoint(1, 11)
       mypath.AddLineToPoint(21, 1)
       mypath.AddLineToPoint(21, 21)
       mypath.CloseSubpath()

       gc.StrokePath(mypath)
       gc.SetPen(wx.Pen('black', 2))
       gc.SetBrush(wx.Brush('yellow'))
       gc.StrokePath(mypath)
       gc.FillPath(mypath)

       dc.SelectObject(wx.NullBitmap)

       bmp.SaveFile('arrow.bmp', wx.BITMAP_TYPE_BMP)

       return bmp

if __name__ == '__main__':
   app = wx.App(0)
   frame1 = testframe(None, -1, 'mytestframe')
   frame1.Show()

# import wx.lib.inspection as wxli
# wxli.InspectionTool().Show()
   app.MainLoop()

--
To unsubscribe, send email to wxPython-users+unsubscribe@googlegroups.com
or visit http://groups.google.com/group/wxPython-users?hl=en

Thanks for leading me in the proper direction, however, my problems are not solved yet:

Strange things happen using the different methods (see attached bmpalphastack_demo.png, enlarged 4x):

In all cases bmp.UseAlpha() has been used to create bitmap (however, no explicit depth set in wx.EmptyBitmap(…)

There are 4 pairs of superimposed arrows on a green background), from left to right:

  1. dc.DrawBitmap(…, True) → wrong (with white background)
  2. gc.DrawBitmap(…) → almost correct, strong white seams around the bitmap
  3. gc.DrawBitmap(…) with bitmap loaded from arrow.bmp (saved in file creation routine as bmp, see code line 104) → the only correct result, nicely blended seams. Also works without bmp.UseAlpha(), the bitmap gets its alpha channel on saving automagically
  4. gc.DrawBitmap(…) with bitmap loaded from arrow.png (saved in file creation routine as png, see code line 105) → only almost correct, weaker white seams. The PNG file has no automagically created alpha

So my questions:

  1. How to do the magic done by saving as BMP and loading it back without actually saving the file to disk?
  2. Am I doing something wrong by using Bitmap/GraphicsContext/MemoryDC?

Thanks in advance,

testbmpalpha3.py (3.22 KB)

···

On Friday, September 14, 2012 5:44:52 PM UTC+2, kevin...@theolliviers.com wrote:

Make sure you’re creating the wx.Bitmap with a depth of 32 bits, otherwise it won’t have an alpha channel. And since you’re on 2.8, I think on Windows you need to also call bitmap.UseAlpha() after creating it to make sure it initializes the alpha channel. (In 2.9, this is no longer needed.)

Hi,

Make sure you’re creating the wx.Bitmap with a depth of 32 bits, otherwise it won’t have an alpha channel. And since you’re on 2.8, I think on Windows you need to also call bitmap.UseAlpha() after creating it to make sure it initializes the alpha channel. (In 2.9, this is no longer needed.)

Thanks for leading me in the proper direction, however, my problems are not solved yet:

Strange things happen using the different methods (see attached bmpalphastack_demo.png, enlarged 4x):

In all cases bmp.UseAlpha() has been used to create bitmap (however, no explicit depth set in wx.EmptyBitmap(…)

There are 4 pairs of superimposed arrows on a green background), from left to right:

  1. dc.DrawBitmap(…, True) → wrong (with white background)
  2. gc.DrawBitmap(…) → almost correct, strong white seams around the bitmap
  3. gc.DrawBitmap(…) with bitmap loaded from arrow.bmp (saved in file creation routine as bmp, see code line 104) → the only correct result, nicely blended seams. Also works without bmp.UseAlpha(), the bitmap gets its alpha channel on saving automagically
  4. gc.DrawBitmap(…) with bitmap loaded from arrow.png (saved in file creation routine as png, see code line 105) → only almost correct, weaker white seams. The PNG file has no automagically created alpha

So my questions:

  1. How to do the magic done by saving as BMP and loading it back without actually saving the file to disk?

Well, the only thing I can think of is that you’re not declaring the createarrow bmp as 32-bit. (it seems you tested this with the paintonbmp bitmap, but not the createarrow one) It’s possible that this works when you save and load it because as you noted, on save, it notices the bmp has alpha data and automagically saves it as a 32-bit image in that case.

Another minor thing, though I suspect it is not related to the problem you are seeing - once you’ve created a wx.GraphicsContext, try not to call any wxDC APIs until you’ve destroyed the wxGraphicsContext. On Windows, the original wxDC’s state becomes managed by the wx.GraphicsContext and attempts to manipulate it directly using wxDC APIs can lead to strange behaviors. You only do a Clear() call and then you just paint over the entire canvas anyway, so it’s quite unlikely that’s related to the problem at hand, but I thought I would mention it just in case.

  1. Am I doing something wrong by using Bitmap/GraphicsContext/MemoryDC?

Aside from the potential 32-bit problem, nothing is jumping out at me.

Regards,

Kevin

···

On Sep 17, 2012, at 1:13 AM, nepix32 wrote:

On Friday, September 14, 2012 5:44:52 PM UTC+2, kevin...@theolliviers.com wrote:

Thanks in advance,

To unsubscribe, send email to wxPython-users+unsubscribe@googlegroups.com

or visit http://groups.google.com/group/wxPython-users?hl=en<bmpalphastack_demo.png><testbmpalpha3.py>

Thanks, Kevin, to cut things short, just uncommenting
dc.Clear()
in the createarrow method did the trick (see attached, now working, testbmpalpha3.py attached). For some rants, read on.

Well, the only thing I can think of is that you’re not declaring the createarrow bmp as 32-bit. (it seems you tested this with the paintonbmp bitmap, but not the createarrow one) It’s possible that this works when you save and load it because as you noted, on save, it notices the bmp has alpha data and automagically saves it as a 32-bit image in that case.

It is not necessary to set 32-bit depth explicitly. I inserted a call bmp.GetDepth() after creation of the EmptyBitmap() and after bmp.UseAlpha(). In both cases, the color depth was 32 already on my platform. However, the alpha gets stripped off when saving as PNG! Most interestingly loading the PNG back and doing DrawBitmap(…), respects the ‘holes’ in the PNG. Oh my…

Another minor thing, though I suspect it is not related to the problem you are seeing - once you’ve created a wx.GraphicsContext, try not to call any wxDC APIs until you’ve destroyed the wxGraphicsContext. On Windows, the original wxDC’s state becomes managed by the wx.GraphicsContext and attempts to manipulate it directly using wxDC APIs can lead to strange behaviors. You only do a Clear() call and then you just paint over the entire canvas anyway, so it’s quite unlikely that’s related to the problem at hand, but I thought I would mention it just in case.

Unlikely, but it was the culprit…

[Doing right and wrong things on DC and GC]
UseAlpha is somehow undocumented. Where to get information about this kind of stuff and when micing GC/DC works and when not…??

With thanks,

testbmpalpha3.py (3.28 KB)

···

On Monday, September 17, 2012 6:38:28 PM UTC+2, kevin...@theolliviers.com wrote: