Search code examples
imagewxpython

wxPython - Fixing buttons to a given position in a image


I'm building a software where I need to display buttons over a image and make it stay somewhat in the same position (the best as possible) when the sizer the image is in gets resized.

The button-display-on-image is already done. I just need to get the math working now. I've tried a bunch of stuff with no luck yet. I guess the magic need to happen in the updateButtons function.

Thanks!

import wx
import wx.lib.platebtn as pb

class MainFrame(wx.Frame):
    def __init__(self, parent):
        super().__init__(parent)
        self.bInfoSizerVisibility = False

        self.images = []
        self.initUI()
        self.CenterOnScreen()
        self.Bind(wx.EVT_SIZE, self.OnResizing)

    def initUI(self):
        self.imageSizer = wx.BoxSizer(wx.VERTICAL)
        self.imageSizer.SetMinSize((800, 600))
        self.bitmap = None
        self.image = None
        self.aspect = None

        self.bmpImage = wx.StaticBitmap(self, wx.ID_ANY)
        self.imageSizer.Add(self.bmpImage, 1, wx.EXPAND)

        self.btn = pb.PlateButton(self.bmpImage, -1, 'Click Me!', style=pb.PB_STYLE_NOBG)
        self.btn.Bind(wx.EVT_BUTTON, self.test)
        self.btn.Position = 250, 250

        self.SetSizerAndFit(self.imageSizer)
        self.frameImage()

    def test(self, event):
        print('Button Pressed!')

    def updateButtons(self):
        w, h = self.bmpImage.GetSize()
        u, v = 0.3, 0.7
        self.btn.Position = int(u * w), int(v * h)

    def frameImage(self, isJustResize=False):
        if not isJustResize:
            self.bitmap = wx.Bitmap('image.jpg', wx.BITMAP_TYPE_ANY)
            self.image = wx.Bitmap.ConvertToImage(self.bitmap)
            self.aspect = self.image.GetSize()[1] / self.image.GetSize()[0]

        self.Layout()

        sW, sH = self.imageSizer.GetSize()
        newW = sW
        newH = int(newW * self.aspect)

        if newH > sH:
            newH = sH
            newW = int(newH / self.aspect)

        image = self.image.Scale(newW, newH)
        self.bmpImage.SetBitmap(image.ConvertToBitmap())

        self.Layout()
        self.Refresh()
        self.updateButtons()

        # print(f"Image New Size: ({newW}, {newH})")
        # print(f"App Size: {self.GetSize()}")
        # print(f"imageSizer Size: {self.imageSizer.GetSize()}\n")

    def OnResizing(self, event):
        self.frameImage(True)
        event.Skip()

app = wx.App()
frame = MainFrame(None)
frame.Show()
app.MainLoop()

Solution

  • You need to take into account the fact that as you resize the image, white space appears, either to the left and right, or on top and bottom. Remember, the button position is relative to the frame and not the image.

    I have rewritten your updateButtons method to implement this.

    I have assumed that your button's position will be 0.3 * the width of the image from the left and 0.7 * the height of the image from the top

    import wx
    import wx.lib.platebtn as pb
    
    IMAGE_MINIMUM_SIZE = (800, 600)
    BUTTON_POSITION_RATIO = (0.3, 0.7)
    
    class MainFrame(wx.Frame):
        def __init__(self, parent):
            super().__init__(parent)
            self.bInfoSizerVisibility = False
    
            self.images = []
            self.initUI()
            self.CenterOnScreen()
            self.Bind(wx.EVT_SIZE, self.OnResizing)
    
        def initUI(self):
            self.imageSizer = wx.BoxSizer(wx.VERTICAL)
            self.imageSizer.SetMinSize(IMAGE_MINIMUM_SIZE)
            self.bitmap = None
            self.image = None
            self.image_aspect = None
    
            self.bmpImage = wx.StaticBitmap(self, wx.ID_ANY)
            self.imageSizer.Add(self.bmpImage, 1, wx.EXPAND)
    
            self.btn = pb.PlateButton(self, -1, 'Click Me!', style=pb.PB_STYLE_NOBG)
            self.btn.Bind(wx.EVT_BUTTON, self.test)
    
            self.SetSizerAndFit(self.imageSizer)
            self.frameImage()
    
        def test(self, event):
            print('Button Pressed!')
    
        def updateButtons(self):
            frame_aspect = self.Size[0] / self.Size[1]
    
            button_horizontal = int(self.Size[0] * BUTTON_POSITION_RATIO[0])
            button_vertical = int(self.Size[1] * BUTTON_POSITION_RATIO[1])
            if self.image_aspect <= frame_aspect:
                # Frame is wider than image so find the horizontal white space size to add
                image_width = self.Size[1] * self.image_aspect
                horizontal_offset = (self.Size[0] - image_width)/2
                button_horizontal = int(horizontal_offset + image_width * BUTTON_POSITION_RATIO[0])
    
            elif self.image_aspect > frame_aspect:
                # Frame is higher than image so find the vertical white space size to add
                image_height = self.Size[0] / self.image_aspect
                vertical_offset = (self.Size[1] - image_height)/2
                button_vertical = int(vertical_offset + image_height * BUTTON_POSITION_RATIO[1])
            self.btn.Position = (button_horizontal, button_vertical)
    
        def frameImage(self, isJustResize=False):
            if not isJustResize:
                self.bitmap = wx.Bitmap('image.jpg', wx.BITMAP_TYPE_ANY)
                self.image = wx.Bitmap.ConvertToImage(self.bitmap)
                self.image_aspect = self.image.GetSize()[0] / self.image.GetSize()[1]
    
            image_width, image_height = self.imageSizer.GetSize()
            new_image_width = image_width
            new_image_height = int(new_image_width / self.image_aspect)
    
            if new_image_height > image_height:
                new_image_height = image_height
                new_image_width = int(new_image_height * self.image_aspect)
    
            image = self.image.Scale(new_image_width, new_image_height)
    
            self.bmpImage.SetBitmap(image.ConvertToBitmap())
    
            self.Layout()
            self.Refresh()
            self.updateButtons()
    
        def OnResizing(self, event):
            self.frameImage(True)
            event.Skip()
    
    app = wx.App()
    frame = MainFrame(None)
    frame.Show()
    app.MainLoop()