Search code examples
pythonbitmapwxpythonpanel

wxPython: Update image / bitmap in sub panel


I'm a beginner at python and programming for that matter and trying to implement a rudimentary image viewer and having trouble to update the sub panel, in which the image should be shown. When I feed an image to the panel at the start of the program, it shows correctly. However, when I try to update the panel with another image via the open file- or open directory-dialog it does not display correctly (see screenshot).

From what I read so far about updating panels (wxpython refresh window on button press,wxPython - change panel by button,How do you force refresh of a wx.Panel?) it is not enough to just recall the function responsible for creating the sub panel, but I seem to oversee or miss something elementary because I cannot get any of the proposed solutions to work in my code.

I also had a look at this photo viewer tutorial at Mouse vs Python, but I could't get it to work in my program.

Below is the code I have so far. It also reproduces the faulty result which you can see on the screenshot. I appreciate any constructive help and feedback you can give me.

import glob
import wx
import os

#====================================================================
class textEditor(wx.Panel):
    #----------------------------------------------------------------------
    def __init__(self, parent):

        wx.Panel.__init__(self, parent)

        sizer = wx.BoxSizer(wx.VERTICAL)

        speeBubImg = wx.StaticBox(self, wx.ID_ANY, "Text Editor")
        sizer.Add(speeBubImg, wx.ID_ANY, flag=wx.EXPAND|wx.ALL)

        self.SetSizer(sizer)

#====================================================================
class comicPagePanel(wx.Panel):
    #----------------------------------------------------------------------
    def __init__(self, parent, imgsPath):

        wx.Panel.__init__(self, parent)

#        print('comicPagePanel', imgsPath)

        # create the static box with the panel description...
        comPageStatBox = wx.StaticBox(self, wx.ID_ANY, "Comic Page")
        # ...and asign a sizer to it
        comPageStatBoxSizer = wx.StaticBoxSizer(comPageStatBox, wx.VERTICAL)

         # Feeding the panel with an image when starting the program works
#        imgsPath.append('someImage.jpg')

        if imgsPath:

#            print('comicPagePanel_if-imgPath', imgsPath)

            # create the image box
            comPageBox = wx.Bitmap(wx.Image(imgsPath[0], wx.BITMAP_TYPE_ANY))

            img = comPageBox.ConvertToImage()

            iW = img.GetWidth()
            iH = img.GetHeight()
            imgMaxSize = 1000

            if iW > iH:
                NewW = imgMaxSize
                NewH = imgMaxSize * iH / iW
            else:
                NewH = imgMaxSize
                NewW = imgMaxSize * iW / iH

            img = wx.Bitmap(img.Scale(NewW,NewH))

            # create another sizer for the actual image box
            comPageBoxSizer = wx.StaticBitmap(self, wx.ID_ANY, img)

            # add the image box sizer to the sizer of the
            # static box with the panel description
            comPageStatBoxSizer.Add(comPageBoxSizer, wx.ID_ANY, wx.EXPAND|wx.ALL)

        # create a main sizer which stretches all other sizers to the
        # size of the subpanel
        main_sizer = wx.BoxSizer()

        # add the static box with the image box that is nested in it
        # to the main sizer
        main_sizer.Add(comPageStatBoxSizer, wx.ID_ANY, wx.EXPAND|wx.ALL)

        # fit the main sizer to the subpanel
        self.SetSizer(main_sizer)

#====================================================================
class comicPageViewer(wx.Panel):
    #----------------------------------------------------------------------
    def __init__(self, parent, imgsPath):

        wx.Panel.__init__(self, parent)

        # laying out the grid for the image panel and the ctrl-buttons
        sizer = wx.GridBagSizer(2, 4)

        # the image viewing panel
        comPage = comicPagePanel(self, imgsPath)
        sizer.Add(comPage, pos=(0, 0), span=(1, 4),
                  flag=wx.EXPAND|wx.ALL, border=5)

        # the ctrl-buttons
        butPrev = wx.Button(self, label="Previous Page")
        butNext = wx.Button(self, label="Next Page")
        butOCR = wx.Button(self, label="Find Text/OCR")
        butSaveTxt = wx.Button(self, label="Save Current Text(s)")


        sizer.Add(butPrev, pos=(1, 0), flag=wx.EXPAND)
        sizer.Add(butNext, pos=(1, 1), flag=wx.EXPAND)
        sizer.Add(butOCR, pos=(1, 2), flag=wx.EXPAND)
        sizer.Add(butSaveTxt, pos=(1, 3), flag=wx.EXPAND)


        sizer.AddGrowableCol(0)
        sizer.AddGrowableCol(1)
        sizer.AddGrowableCol(2)
        sizer.AddGrowableCol(3)

        sizer.AddGrowableRow(0)

        self.SetSizer(sizer)

#====================================================================
class mainPanel(wx.Panel):
    #----------------------------------------------------------------------
    def __init__(self, parent, imgsPath):

        wx.Panel.__init__(self, parent)

#        print(imgsPath)

        # Create a sub panel left and right
        lSubPan = comicPageViewer(self, imgsPath)
        rSubPan = textEditor(self)

        sizer = wx.BoxSizer(wx.HORIZONTAL)
        sizer.Add(lSubPan, wx.ID_ANY, wx.EXPAND|wx.ALL)
        sizer.Add(rSubPan, wx.ID_ANY, wx.EXPAND|wx.ALL)
        self.SetSizer(sizer)

#====================================================================
class mainFrame(wx.Frame):
    def __init__(self, *args, **kwargs):
        wx.Frame.__init__(self, *args, **kwargs)

        self.createPanel()
        self.Maximize(True)
        self.Show()

    #----------------------------------------------------------  
    def createPanel(self):

        imgsPath = []

        self.CreateStatusBar()
        self.createMenu()
        panel = mainPanel(self, imgsPath)
    #----------------------------------------------------------
    def createMenu(self):      

        # create the menu bar
        menuBar = wx.MenuBar()

        # create a file menu
        fileMenu = wx.Menu()

        opFileBut = wx.MenuItem(fileMenu, wx.ID_ANY, '&Open File', 'Open a Single File')
        fileMenu.Append(opFileBut)

        # bind the open button to the on_open_directory event
        fileMenu.Bind(wx.EVT_MENU, self.onOpenFile, opFileBut)

        # add an open directory Button to the file menu
        opDirBut = wx.MenuItem(fileMenu, wx.ID_ANY, 'Open &Directory', 'Open Working Directory')
        fileMenu.Append(opDirBut)

        # bind the open button to the on_open_directory event
        fileMenu.Bind(wx.EVT_MENU, self.onOpenDirectory, opDirBut)

        # add a line separator to the file menu
        fileMenu.AppendSeparator()

        # add a quit button to fileMenu
        quitBut = wx.MenuItem(fileMenu, wx.ID_EXIT, '&Quit', 'Exit the Programm')
        fileMenu.Append(quitBut)

        # connect the quit button to the actual event of quitting the app
        fileMenu.Bind(wx.EVT_MENU, self.onQuit, quitBut)

        # call onQuit if the app is closed via x in title bar (in order to do some cleaning up)
        self.Bind(wx.EVT_CLOSE, self.onQuit)

        # give the menu a title
        menuBar.Append(fileMenu, "&File(s)")

        # connect the menu bar to the frame        
        self.SetMenuBar(menuBar)

    #----------------------------------------------------------
    def onOpenFile(self, event):

        with wx.FileDialog(self, "Choose a File",
                          style=wx.FD_DEFAULT_STYLE) as fDlg:
            if fDlg.ShowModal() == wx.ID_OK:

                imgPath = glob.glob(os.path.join(fDlg.GetPath()))

            if imgPath:
                comicPagePanel(self, imgPath)

    #----------------------------------------------------------
    def onOpenDirectory(self, event):

        with wx.DirDialog(self, "Choose a Directory",
                          style=wx.DD_DEFAULT_STYLE) as dDlg:
            if dDlg.ShowModal() == wx.ID_OK:

                imgsPath = glob.glob(os.path.join(dDlg.GetPath(), '*.jpg'))

            if imgsPath:
                comicPagePanel(self, imgsPath)

    #----------------------------------------------------------
    def onQuit(self, event):
        # get the frame's top level parent and close it
        wx.GetTopLevelParent(self).Destroy()


#======================
# Start GUI
#======================

if __name__ == '__main__':
    app = wx.App()
    mainFrame(None, title="Text from Comic Pages")
    app.MainLoop()

Solution

  • First of all, thanks again to pyrrhoofcam for pointing me into the right direction. I worked out a solution that works, but I don't know if it is "good" code. I simply defined the image as an static class attribute which can be changed through the additional updateImage() method within the same class. The code of the panel class comicPagePanel() looks like this now:

    #====================================================================
    # the upper left panel that shows the image of the comic page
    class comicPagePanel(wx.Panel):
    
        # create an "empty" image as placeholder;
        # it will be replaced by an actual image via the open file dialog
        comPageImg = wx.Image(1,1)
    
        #----------------------------------------------------------------------
        def __init__(self, parent):
            # Constructor
            wx.Panel.__init__(self, parent)
    
            # create the static box with the panel description...
            comPageStatBox = wx.StaticBox(self, wx.ID_ANY, "Comic Page")
            # ...and asign a sizer to it
            comPageStatBoxSizer = wx.StaticBoxSizer(comPageStatBox, wx.VERTICAL)
    
            # get the background color of the panel
            [panR, panG, panB] = self.GetBackgroundColour()[:3]
    
            # set the color of the image placeholder to the background color of the panel
            comicPagePanel.comPageImg.Replace(0,0,0,panR,panG,panB)
    
            # convert the image placeholder to a static bitmap
            comicPagePanel.comPageImg = wx.StaticBitmap(self, wx.ID_ANY, wx.Bitmap(comicPagePanel.comPageImg))
    
            # add the image placeholder to the sizer of the
            # static box with the panel description; meaning, placing it within the static box
            comPageStatBoxSizer.Add(comicPagePanel.comPageImg, wx.ID_ANY, wx.EXPAND|wx.ALL)
    
            # create a main sizer which stretches all other sizers to the
            # size of the subpanel
            main_sizer = wx.BoxSizer()
    
            # add the static box with the image that is nested in it
            # to the main sizer
            main_sizer.Add(comPageStatBoxSizer, wx.ID_ANY, wx.EXPAND|wx.ALL)
    
            # fit the main sizer to the subpanel
            self.SetSizer(main_sizer)
    
        #----------------------------------------------------------------------
        # the update method of the comic page image viewer
        def updateImage(self, imgsPath):
    
            # get the new image
            newImg = wx.Image(imgsPath[0], wx.BITMAP_TYPE_ANY)
    
            # get the display resolution in order to fit the image into the panel
            [disX, disY] = wx.GetDisplaySize()
    
            # determine the approximate size of the image panel
            disX = disX/2-50
            disY = disY-175
    
            # get the size of the new image
            [iW, iH] = newImg.GetSize()
    
            # scale the image proportionally        
            if not iH < disY and iW > iH:
                NewW = disX
                NewH = disX * iH / iW
                # scaling the page image
                newImg = newImg.Scale(NewW,NewH)
            elif not iW < disX:
                NewH = disY
                NewW = disY * iW / iH
                # scaling the page image
                newImg = newImg.Scale(NewW,NewH)
    
            # replace the old image in the panel with the new one
            comicPagePanel.comPageImg.SetBitmap(wx.Bitmap(newImg))
    

    Now, if the user wants to open a new image via the file dialog, the updateImage() method is called. This call looks like this:

    #----------------------------------------------------------
    # open a file dialog
    def onOpenFile(self, event):
    
        with wx.FileDialog(self, "Choose a File",
                          style=wx.FD_DEFAULT_STYLE) as fDlg:
            if fDlg.ShowModal() == wx.ID_OK:
    
                imgPath = glob.glob(os.path.join(fDlg.GetPath()))
    
            if imgPath:
                comicPagePanel.updateImage(self, imgPath)
            else:
                return