Draw wx.StaticBitmap, which is inside a sizer, with wx.PaintDC (wx.EVT_PAINT)

Hi,

I need to draw a .png image with transparency on the top of a normal .jpg image. I have looked it up and I have to use wx.PaintDC binded to wx.EVT_PAINT event. The problem is my program needs that “base image .jpg” to be in a sizer so it doens’t fill the entire frame and to show a panel on the left side.

Example of my actual program. That left panel can be showed or hided:

Furthermore, the images needs to have the ability to scale itself and be replaced.
The image with the transparency will be some mascot with a text baloon on top of the base image, which will suposelly help the students how to operate the program.

How can I achieve this?
The only image control which I know of that can be inside a sizer is a wx.StaticBitmap, but when drawn with wx.PaintDC (so it can have transparency), it seems to disrespect the boundaries of the sizer. The image glitches even more when I manually resize the window. Here’s my minimal code so far.

Thank you!

import wx

class Test(wx.Panel):
    def __init__(self, parent):
        super().__init__(parent)

        self.base = wx.StaticBitmap(self, -1)
        self.png = wx.StaticBitmap(self.base, -1)

        self.base.SetBitmap(wx.Bitmap('base_image.jpg', wx.BITMAP_TYPE_ANY))
        self.png.SetBitmap(wx.Bitmap('transparent_image.png', wx.BITMAP_TYPE_PNG))

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

    def initSizers(self):
        master = wx.BoxSizer(wx.HORIZONTAL)
        left = wx.BoxSizer(wx.VERTICAL)
        right = wx.BoxSizer(wx.VERTICAL)

        left.Add(wx.StaticText(self, -1, 'aaaaaaaaaaaaaa'))
        right.Add(self.base)

        master.Add(left)
        master.Add(right)
        self.SetSizer(master)

    def OnPaint(self, e):
        dc = wx.PaintDC(self)

        dc.DrawBitmap(self.base.GetBitmap(), 0, 0, True)
        dc.DrawBitmap(self.png.GetBitmap(), 0, 0, True)

class Frame(wx.Frame):
    def __init__(self, parent):
        super().__init__(parent)
        Test(self).Show()

app = wx.App()
Frame(None).Show()
app.MainLoop()

Hi, Neo

Your code is painting on the client area of the frame, but there are also StaticBitmap’s that repaint their client area, which results in a glitchy look.

This seems to be related to your previous post. I think the easiest way is to set bitmaps on your transparent buttons <wx.lib.platebtn> by just adding,

    self.btn.SetBitmap(your_png)

As far as I can think of, two other ways to achieve your goals:

  1. Draw a bitmap on the base bitmap to make one bitmap image.
    Suppose A and B are fore and back bitmap images,
    dc = wx.MemoryDC(B)
    dc.DrawBitmap(A, x, y, useMask=True)
    del dc
    self.base = wx.StaticBitmap(self, bitmap=B)

EDIT In this case, you don’t need to handle EVT_PAINT

  1. If you are planning to implement pan (zoom and drag), wx.lib.floatcanvas.FloatCanvas could be a good alternative to wx.StaticBitmap.
    Please see my previous posts: this and this
1 Like

The solutuion 1. didn’t work for me somehow, but I did a test with the pb.PlateButton with style=pb.PB_STYLE_NOBG and size=((x, y)) a while ago and it worked pretty well. My only complaint was that I couldn’t find a way to remove the “hover” effect. I made it to disabled, but it removed all the color. But now, I guess the hover effect could come in handy. The user will see that it’s a button so I can make use of the clicks on it to add some functionality. I’m still accepting tips in how to achieve this.

The good thing about the “draw a picture over the other” was that I could specify exactly where the top image will go and would it seen to remain locked in place if I resized the base image.

I’ll take a look at those topics.
Thanks so much for your help. :smiley:

For now, I’ll stick with the pb.PlateButton.

Hi, I’m sorry posting about this again, but the button solution seems it will not work for this case. The app window have a minimum size to acomodate people using regular HD monitor resolution. In this case, the buttons appear quite large on the screen and when the user opens the left panel, it gets pushed almost out of the frame. So now I’m going with drawing a bitmap on the base bitmap to make one bitmap image as you said.

The thing is I couldn’t make the code to work. This is what I’ve got:

import wx

class Test(wx.Panel):
    def __init__(self, parent):
        super().__init__(parent)

        self.base = wx.StaticBitmap(self, -1)
        self.png = wx.StaticBitmap(self.base, -1)

        self.base.SetBitmap(wx.Bitmap('base_image.jpg', wx.BITMAP_TYPE_ANY))
        self.png.SetBitmap(wx.Bitmap('transparent_image.png', wx.BITMAP_TYPE_PNG))

        self.initSizers()
        self.Bind(wx.EVT_SIZE, self.Draw)

    def initSizers(self):
        master = wx.BoxSizer(wx.HORIZONTAL)
        left = wx.BoxSizer(wx.VERTICAL)
        right = wx.BoxSizer(wx.VERTICAL)

        left.Add(wx.StaticText(self, -1, 'aaaaaaaaaaaaaa'))
        right.Add(self.base)

        master.Add(left)
        master.Add(right)
        self.SetSizer(master)

    def Draw(self, e):
        dc = wx.MemoryDC(self.base)
        dc.DrawBitmap(self.png, 0, 0, useMask=True)
        del dc
        self.base = wx.StaticBitmap(self, bitmap=self.base)

class Frame(wx.Frame):
    def __init__(self, parent):
        super().__init__(parent)
        Test(self).Show()

app = wx.App()
Frame(None).Show()
app.MainLoop()

Thank you!

Hi, Neo

The argument of DrawBitmap is a Bitmap, not a StaticBitmap.
The first step is to draw a bitmap on the base bitmap.

        self.back = wx.Bitmap('phoenix-fire-md.png')
        self.fore = wx.Bitmap('Vippi.png')

        dc = wx.MemoryDC(self.back)
        dc.DrawBitmap(self.fore, 100, 100, True)
        del dc
        self.base = wx.StaticBitmap(self, bitmap=self.back)

Next, convert the bitmap to wx.Image object to resize it, and convert back to a wx.Bitmap object using interpolation if needed each time of sizing event.

    def Draw(self, e):
        w, h = self.back.Size
        W, H = self.ClientSize
        W = w * H // h
        self.base.SetBitmap(self.back.ConvertToImage()
                                .Scale(W, H, wx.IMAGE_QUALITY_NEAREST)
                                .ConvertToBitmap())
        e.Skip()

Clipboard02 Have fun!

1 Like

Thanks so much. After quite a few attempts, it could translate it to my working app. :smiley:

Hi, sorry to bother you again, but is there anything that can be done to increase the foreground image quality?

It seems when the fore image is drawn over the base one, the quality is great (full_res.jpeg), but when the image is scaled down to fit the frame (scaled.jpeg, scaled_fullscreen.jpeg), the quality for the fore image seems to be lost a bit and I don’t know why. I use a 1080p monitor.

I used the .SaveFile() function of the bitmap class to get the images. The full resolution was taken when the fore image was just drawn over the base one. The scaled ones were taken after the the scaling, conversion to Image / back to Bitmap.

The referenced images are in the .zip file, as the originals back the fore images too.

images.zip (780.0 KB)

Hi, Neo

If you reduce the image size, the information in the image will be lost, so the result you obtained is natural for me. However, using interpolation may improve the appearance.

import wx

class Test(wx.Panel):
    def __init__(self, parent):
        super().__init__(parent)
        
        self.back = wx.Bitmap('base.jpg')
        self.fore = wx.Bitmap('fore.png')
        
        dc = wx.MemoryDC(self.back)
        dc.DrawBitmap(self.fore, 100, 100, True)
        del dc
        
        self.base = wx.StaticBitmap(self, bitmap=self.back)
        self.Bind(wx.EVT_SIZE, self.Draw)
    
    def Draw(self, e):
        """Draw bitmap with interpolation

        wx.Image.Scale(width, height, quality=IMAGE_QUALITY_NORMAL) -> Image
        Select a method from the following:
        wx.IMAGE_QUALITY_BICUBIC                2
        wx.IMAGE_QUALITY_BILINEAR               1
        wx.IMAGE_QUALITY_BOX_AVERAGE            3
        wx.IMAGE_QUALITY_HIGH                   4
        wx.IMAGE_QUALITY_NEAREST                0
        wx.IMAGE_QUALITY_NORMAL                 0
        """
        w, h = self.back.Size
        W, H = self.ClientSize
        W = w * H // h
        self.base.SetBitmap(self.back.ConvertToImage()
                                .Scale(W, H, wx.IMAGE_QUALITY_NEAREST)
                                .ConvertToBitmap())
        e.Skip()

app = wx.App()
frm = wx.Frame(None)
frm.panel = Test(frm)
frm.Show()
app.MainLoop()

The higher the quality, the higher the cost you have to pay.

1 Like

Thanks so much!
The wx.IMAGE_QUALITY_BILINEAR was just perfect!

I have tested all of them (.zip file below) and I guess the option above is the best.

testing_quality_modes.zip (943.4 KB)