Adding and fixing buttons to a given position in a image

Hi guys,

I’m working in a project which requires very basic image processing. Due to pandemic, all classes in my university are online only. This project is for an engineering class that teaches how water systems works. Students should manipulate water bombs, read data from screens, open / close pumps, etc.

So, there will be a image and transparent buttons (or things that can behaive like a button) on top of very specific spots on the image. The buttons will be over the instruments where the user can interact with. The interactions and such I can do with no problem.

What I have now is a very basic image viewer software. There is a eye button on the toolbar that shows a right sizer which I will show relevant information based on what button was clicked. What I need now is way to save the position of a button and to keep it consistent to the image when the sizer the image is in gets resized.

As for the saving of the initial coordinate, I can dump to a .json file, but I don’t know the math and how to set the buttons on top of the image. The code below I made it copying here and there.
Icons and image below:

eye left right

import wx, os

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

        self.bInfoSizerVisibility = False
        self.images = []
        self.index = 0

        self.toolbar = self.CreateToolBar()

        self.initToolbar()
        self.initUI()

        self.CenterOnScreen()

        self.Bind(wx.EVT_KEY_DOWN, self.OnKey)
        self.Bind(wx.EVT_SIZE, self.OnResizing)

    def initToolbar(self):
        ''' Initialize the toolbar. '''

        left_arrow = self.toolbar.AddTool(wx.ID_ANY, 'Left', wx.Bitmap('images/icons/left.png'))
        right_arrow = self.toolbar.AddTool(wx.ID_ANY, 'Right', wx.Bitmap('images/icons/right.png'))
        show_icon = self.toolbar.AddTool(wx.ID_ANY, 'See / Unsee', wx.Bitmap('images/icons/eye.png'))

        self.Bind(wx.EVT_TOOL, self.OnNext, right_arrow)
        self.Bind(wx.EVT_TOOL, self.OnPrevious, left_arrow)
        self.Bind(wx.EVT_TOOL, self.OnInfoSizer, show_icon)

        self.toolbar.Realize()

    def initUI(self):
        ''' Initialize the UI. '''

        mainSizer = wx.BoxSizer(wx.HORIZONTAL)
        self.imageSizer = wx.BoxSizer(wx.VERTICAL)
        self.imageSizer.SetMinSize((800, 600))

        self.infoSizer = wx.BoxSizer(wx.VERTICAL)
        self.infoSizer.Add( wx.StaticText(self, -1, 'Placeholder Text') )
        self.infoSizer.ShowItems(self.bInfoSizerVisibility)

        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, 0)
        mainSizer.Add(self.imageSizer, 1, wx.EXPAND, 0)
        mainSizer.Add(self.infoSizer)

        self.images = self.getFolderImages('images/system1')

        self.SetSizerAndFit(mainSizer)
        self.frameImage(self.images[self.index])

        self.Layout()

    def frameImage(self, path, isJustResize=False):
        ''' Recivies the path and updates the image on the screen. '''

        if not isJustResize:
            self.bitmap = wx.Bitmap(path, wx.BITMAP_TYPE_ANY)
            self.image = wx.Bitmap.ConvertToImage(self.bitmap)
            self.aspect = self.image.GetSize()[1] / self.image.GetSize()[0]

        self.Layout()   # To update the size of self.imageSizer
        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()

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

    def getFolderImages(self, path):
        ''' Recivies the path of a given folder and returns a list with the images paths. '''

        jpgs = [f for f in os.listdir(path) if f[-4:] == ".jpg"]
        return  [os.path.join(path, f) for f in jpgs]

    def OnNext(self, event):

        self.index += 1
        if self.index >= len(self.images):
            self.index = 0

        self.frameImage(self.images[self.index])

    def OnPrevious(self, event):

        self.index -= 1
        if self.index < 0:
            self.index = len(self.images) - 1

        self.frameImage(self.images[self.index])

    def OnKey(self, event):

        if event.GetKeyCode() == wx.WXK_LEFT:
            self.OnPrevious(None)

        if event.GetKeyCode() == wx.WXK_RIGHT:
            self.OnNext(None)

    def OnResizing(self, event):

        self.frameImage(self.images[self.index], True)
        event.Skip()

    def OnInfoSizer(self, event):
        ''' Shows / hides the infoSizer panel. Will contain more complicated widgets in the future. '''

        value = self.bInfoSizerVisibility
        self.infoSizer.ShowItems(not value)
        self.bInfoSizerVisibility = not value

        self.Layout()

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

Thank you very much!

Hi,

Looking into some demo codes in “wxdemo/Using Images/”, it seems that there are several ways to do that. I think that placing widgets on images is interesting and may have many uses (Is it common sense in pygame?).

I made a simple example of just placing one button on a bitmap image.

import wx
import wx.lib.platebtn as pb

class Frame(wx.Frame):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        
        self.panel = wx.Panel(self)
        
        self.image = wx.Image("sample.png")
        self.update_bitmap()
        
        self.btn = pb.PlateButton(self.panel,
            label='Press me!', style=pb.PB_STYLE_NOBG)
        self.update_btns()
        
        self.SetSizer(wx.BoxSizer())
        self.Sizer.Add(self.panel, 1, wx.EXPAND)
        
        self.panel.Bind(wx.EVT_SIZE, self.OnSize)
        self.panel.Bind(wx.EVT_PAINT, self.OnPaint)
    
    def update_bitmap(self):
        w, h = self.image.GetSize()
        H = self.ClientSize[1]
        W = H * w // h
        self.bitmap = self.image.Scale(W, H).ConvertToBitmap()
    
    def update_btns(self):
        w, h = self.bitmap.GetSize()
        u, v = 0.5, 0.5
        self.btn.Position = int(u * w), int(v * h)
    
    def OnSize(self, evt):
        self.update_bitmap()
        self.update_btns()
        self.Refresh()
        evt.Skip()
    
    def OnPaint(self, evt):
        dc = wx.PaintDC(self.panel)
        dc.DrawBitmap(self.bitmap, 0, 0, False)

if __name__ == "__main__":
    app = wx.App()
    frm = Frame(None)
    frm.Show()
    app.MainLoop()

I hope it helps you.


EDIT It would be even nice if the font colour is automatically selected.

1 Like

Thanks so much for your help, @komoto48g
I’ve made some changes to the code, but for some reason, the button isn’t clickable.

Anyone have any ideia of what the problem could be?
PS: I could use the link of that repository! :slight_smile:

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, -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.5, 0.5
        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()

You need to create the button as a child of StaticBitmap like this:

-        self.btn = pb.PlateButton(self, -1, 'Click Me!', style=pb.PB_STYLE_NOBG)
+        self.btn = pb.PlateButton(self.bmpImage, -1, 'Click Me!', style=pb.PB_STYLE_NOBG)
1 Like

Thanks!
Silly me! :smiley:

Edit: Now, I just need to figure it out how to make the button stay in place when the window gets resized.