Search code examples
wxwidgetswxpython

How can I dynamically downscale the bitmap of a wxBitmapButton to fit?


Lets say I have a wxBitmapButton and a text label wxButton in the same horizontal wxBoxSizer. The bitmap assigned to the wxBitmapButton is too big for the button (and stretches it larger).

What I have

I want the bitmap to be downscaled so that it fits the button exactly, no longer stretching the button size, while the button itself is no smaller than the "standard" button size.

What I want

I want this downscaling to happen automatically and dynamically. How can I accomplish this?

I'm using macOS Catalina 10.15.4, Python 3.8.2, and wxPython 4.1.0a1 from the snapshots folder. I have a normal DPI monitor (although I'd want my code to run on both high-DPI and normal monitors).


Minimal example:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

from base64 import b64decode  
from wx import *


class TestFrame(Frame):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self._sizer = BoxSizer(HORIZONTAL)


        button1 = BitmapButton(self)  # I want button1 to be the same size as the other buttons in the sizer, and for the bitmap to downscale to fit

        data = b64decode(''.join([  # 64x64 px PNG
            'iVBORw0KGgoAAAANSUhEUgAAAEAAAABA',
            'CAMAAACdt4HsAAAANlBMVEUAAAAAAAAA',
            'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
            'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC3',
            'dmhyAAAAEXRSTlMArNkZmwdF04UT9+zS',
            'pndhKrYPNwAAAABsSURBVFjD7ZZJCoAw',
            'EAQ1Olnc5/+fVfAaJFAHCXTdqyCZSw9C',
            '9IDFaMQvyT1NIJD9IYDC7rBwOi3MKqig',
            'wh+FksdvwltYr7pvmzeyWDUQvZkDB/AT',
            '6CfSM8qXLx/4cOLQkUVnHh2aeOoK0QE3',
            'Edkb36fiI5sAAAAASUVORK5CYII='
        ]))

        bmp = Bitmap.NewFromPNGData(data, len(data))
        button1.SetBitmap(bmp)

        # If uncommented, this bit of code will simulate the effect I want
        # img = bmp.ConvertToImage()
        # scaled = img.Scale(17, 17, IMAGE_QUALITY_HIGH)
        # button1.SetBitmap(scaled.ConvertToBitmap())


        button2 = Button(self, label='MyButton')
        button3 = Button(self, label='MyButton')
        button4 = Button(self, label='MyButton')


        self._sizer.Add(button1, 0, ALL, 5)
        self._sizer.Add(button2, 0, ALL, 5)
        self._sizer.Add(button3, 0, ALL, 5)
        self._sizer.Add(button4, 0, ALL, 5)


        self.SetSizer(self._sizer)


def main():
    app = App()

    frm = TestFrame(None)

    frm.Show()
    app.MainLoop()


if __name__ == '__main__':
    main()

Solution

  • wxButton won't do image scaling for you, you need to resize the bitmap yourself. This is not terribly difficult, as you can just use wxImage::Size() or wxImage::Resize() for this, but it might give non-ideal results for small images with straight lines like the ones you use, which is why it's usually better to have a few pre-rendered bitmaps and choose the most suitable among them instead.

    In any case, you still need to know the size to use. It should normally be returned by wxArtProvider::GetSizeHint(), although I admit that I'm not sure if it works correctly when using non-default DPI under macOS (but if it doesn't, it should be reported as a bug and fixed).