Рубрики
Без рубрики

Создание простого зрителя фото с wxpython

Получите практические, реальные навыки Python на наших ресурсах и пути

Автор оригинала: Mike Driscoll.

На днях я общался с новичками WXPYPHON на канале WXPYPHON IRC, и один из них хотел знать, как отображать изображения в WX. Есть много разных способов сделать это, но у меня было предварительно сделанное решение, которое я мочался вместе пару лет назад на работу. Поскольку это довольно популярная тема, я думал, что будет разумно позволить вам, дорогой читатель в секрете.

Image Viewer принимает один

Один из самых простых способов отображения изображения в WxPython – использовать WX.StaticBitMap, чтобы сделать грязную работу. В этом примере мы хотим заполнителю для изображения, поэтому мы будем использовать WX. WempeyImage для этого. Наконец, нам нужен способ масштабировать изображение вниз, если он слишком велик для нашего разрешения или нашего приложения. Для этого мы будем использовать совет, который я получил от прославленной Андреа Гвана (создатель библиотеки AGW). Этого достаточно для введения, давайте посмотрим на код:

import os
import wx

class PhotoCtrl(wx.App):
    def __init__(self, redirect=False, filename=None):
        wx.App.__init__(self, redirect, filename)
        self.frame = wx.Frame(None, title='Photo Control')
                          
        self.panel = wx.Panel(self.frame)

        self.PhotoMaxSize = 240
        
        self.createWidgets()
        self.frame.Show()
        
    def createWidgets(self):
        instructions = 'Browse for an image'
        img = wx.EmptyImage(240,240)
        self.imageCtrl = wx.StaticBitmap(self.panel, wx.ID_ANY, 
                                         wx.BitmapFromImage(img))
        
        instructLbl = wx.StaticText(self.panel, label=instructions)
        self.photoTxt = wx.TextCtrl(self.panel, size=(200,-1))
        browseBtn = wx.Button(self.panel, label='Browse')
        browseBtn.Bind(wx.EVT_BUTTON, self.onBrowse)
        
        self.mainSizer = wx.BoxSizer(wx.VERTICAL)
        self.sizer = wx.BoxSizer(wx.HORIZONTAL)
        
        self.mainSizer.Add(wx.StaticLine(self.panel, wx.ID_ANY),
                           0, wx.ALL|wx.EXPAND, 5)
        self.mainSizer.Add(instructLbl, 0, wx.ALL, 5)
        self.mainSizer.Add(self.imageCtrl, 0, wx.ALL, 5)
        self.sizer.Add(self.photoTxt, 0, wx.ALL, 5)
        self.sizer.Add(browseBtn, 0, wx.ALL, 5)        
        self.mainSizer.Add(self.sizer, 0, wx.ALL, 5)
        
        self.panel.SetSizer(self.mainSizer)
        self.mainSizer.Fit(self.frame)

        self.panel.Layout()
        
    def onBrowse(self, event):
        """ 
        Browse for file
        """
        wildcard = "JPEG files (*.jpg)|*.jpg"
        dialog = wx.FileDialog(None, "Choose a file",
                               wildcard=wildcard,
                               style=wx.OPEN)
        if dialog.ShowModal() == wx.ID_OK:
            self.photoTxt.SetValue(dialog.GetPath())
        dialog.Destroy() 
        self.onView()

    def onView(self):
        filepath = self.photoTxt.GetValue()
        img = wx.Image(filepath, wx.BITMAP_TYPE_ANY)
        # scale the image, preserving the aspect ratio
        W = img.GetWidth()
        H = img.GetHeight()
        if W > H:
            NewW = self.PhotoMaxSize
            NewH = self.PhotoMaxSize * H / W
        else:
            NewH = self.PhotoMaxSize
            NewW = self.PhotoMaxSize * W / H
        img = img.Scale(NewW,NewH)

        self.imageCtrl.SetBitmap(wx.BitmapFromImage(img))
        self.panel.Refresh()
        
if __name__ == '__main__':
    app = PhotoCtrl()
    app.MainLoop()

Вы можете думать, что этот пример – это сложный комплекс. Ну, это на самом деле не так уж плохо, так как код длиной всего 76 строк! Давайте перейдем на различные методы, чтобы увидеть, что происходит.

def __init__(self, redirect=False, filename=None):
        wx.App.__init__(self, redirect, filename)
        self.frame = wx.Frame(None, title='Photo Control')
                          
        self.panel = wx.Panel(self.frame)

        self.PhotoMaxSize = 500
        
        self.createWidgets()
        self.frame.Show()

__Init__ инициализирует объект wx.app, создает раму и добавляет панель как только ребенок кадра. Мы устанавливаем максимальный размер фото до 500 пикселей, чтобы их было легко увидеть, но не будет слишком большим для нашего монитора. Тогда мы называем нашими Createwidgets Способ и, наконец, отобразите раму, вызывая Show (). Так как мы уже прошли через виджет пустого вращения ранее, я думаю, что безопасно пропустить Createwidgets Способ и двигаться дальше с одним предостережением. Обратите внимание на последние три линии от Createwidgets :

self.panel.SetSizer(self.mainSizer)
self.mainSizer.Fit(self.frame)
self.panel.Layout()

Это установит Sizer Panel, а затем установит кадр к виджетам, содержащимся в Sizer. Это будет сохранять приложение, которое выглядит аккуратно, так как у нас не будет куча дополнительных пикселей, стоящих в панели, торчащих в нечетных местах. Попробуйте это без этих строк и посмотрите, что произойдет, если вы не можете его сделать!

Наше OnBrowse Метод следующий:

def onBrowse(self, event):
    """ 
    Browse for file
    """
    wildcard = "JPEG files (*.jpg)|*.jpg"
    dialog = wx.FileDialog(None, "Choose a file",
                           wildcard=wildcard,
                           style=wx.OPEN)
    if dialog.ShowModal() == wx.ID_OK:
        self.photoTxt.SetValue(dialog.GetPath())
    dialog.Destroy() 
    self.onView()

Во-первых, мы создаем подстановочный знак, который указывает только файлы JPEG, а затем мы передаем его на наш конструктор WX.FileDialog. Это ограничит диалог таким образом, чтобы он только показывает файлы JPEG. Если пользователь нажимает кнопку OK (или OPEN), то мы устанавливаем Phototxt Значение управления на путь выбранного файла. Мы, вероятно, должны добавить некоторую проверку ошибок, чтобы убедиться, что файл является действительным файлом JPEG, но мы оставим это как упражнение для читателя. После установки значения диалоговое окно разрушается и OnView называется, что покажет картину. Давайте посмотрим, как это работает:

def onView(self):
    filepath = self.photoTxt.GetValue()
    img = wx.Image(filepath, wx.BITMAP_TYPE_ANY)
    # scale the image, preserving the aspect ratio
    W = img.GetWidth()
    H = img.GetHeight()
    if W > H:
        NewW = self.PhotoMaxSize
        NewH = self.PhotoMaxSize * H / W
    else:
        NewH = self.PhotoMaxSize
        NewW = self.PhotoMaxSize * W / H
    img = img.Scale(NewW,NewH)

    self.imageCtrl.SetBitmap(wx.BitmapFromImage(img))
    self.panel.Refresh()
    self.mainSizer.Fit(self.frame)

Во-первых, мы снимаем путь образа от текстового элемента управления и создайте экземпляр WX.Image, используя эту информацию. Далее мы получаем ширину и высоту файла и используем простые расчеты для масштабирования изображения до размера, который мы указали в начале. Затем мы называем методом нашего SETBITMAP () STIATITBITMAP, чтобы показать изображение. Наконец, мы обновляем панель так, чтобы все перекрашивали, и мы называем методом нашего метода Sizer Sizer, чтобы рамка изменена изменением новой фотографии в соответствующей моде. Теперь у нас есть полностью функциональный зритель изображения!

Создание лучшего зрителя фото

Если вы одни из моих проницательных читателей, вы заметите, что каждый раз, когда вы хотите просмотреть новое изображение в вашей папке Picture, вам нужно будет просматривать его. Это не хороший опыт пользователя, не так ли? Попробуем расширить наш контроль, чтобы мы могли сделать следующее:

  • Добавьте предыдущую и следующую кнопку, чтобы вернуться назад и пересылать на картинки
  • Создайте кнопку, которая может «играть» фотографии (I.E. Измените фотографии каждый так часто)
  • Сделайте предыдущие и следующие кнопки «Smart» достаточно, чтобы начать все, когда они достигают конца списка файлов

Для нашего первого трюка мы поставьте рамку и панель в отдельные классы и просто используйте PysimpleApp вместо приложения. Это делает наш код немного более упорядоченным, хотя он также имеет некоторые недостатки (например, общение между рамкой и панелью). Панель проведет большинство наших виджетов, поэтому давайте сначала посмотрим на класс кадров, поскольку он намного проще:

import glob
import wx

from wx.lib.pubsub import Publisher

########################################################################
class ViewerFrame(wx.Frame):
    """"""

    #----------------------------------------------------------------------
    def __init__(self):
        """Constructor"""
        wx.Frame.__init__(self, None, title="Image Viewer")
        panel = ViewerPanel(self)
        self.folderPath = ""
        Publisher().subscribe(self.resizeFrame, ("resize"))
        
        self.initToolbar()
        self.sizer = wx.BoxSizer(wx.VERTICAL)
        self.sizer.Add(panel, 1, wx.EXPAND)
        self.SetSizer(self.sizer)
        
        self.Show()
        self.sizer.Fit(self)
        self.Center()
                
    #----------------------------------------------------------------------
    def initToolbar(self):
        """
        Initialize the toolbar
        """
        self.toolbar = self.CreateToolBar()
        self.toolbar.SetToolBitmapSize((16,16))
        
        open_ico = wx.ArtProvider.GetBitmap(wx.ART_FILE_OPEN, wx.ART_TOOLBAR, (16,16))
        openTool = self.toolbar.AddSimpleTool(wx.ID_ANY, open_ico, "Open", "Open an Image Directory")
        self.Bind(wx.EVT_MENU, self.onOpenDirectory, openTool)
        
        self.toolbar.Realize()
        
    #----------------------------------------------------------------------
    def onOpenDirectory(self, event):
        """
        Opens a DirDialog to allow the user to open a folder with pictures
        """
        dlg = wx.DirDialog(self, "Choose a directory",
                           style=wx.DD_DEFAULT_STYLE)
        
        if dlg.ShowModal() == wx.ID_OK:
            self.folderPath = dlg.GetPath()
            print self.folderPath
            picPaths = glob.glob(self.folderPath + "\\*.jpg")
            print picPaths
        Publisher().sendMessage("update images", picPaths)
        
    #----------------------------------------------------------------------
    def resizeFrame(self, msg):
        """"""
        self.sizer.Fit(self)

Если вы прочитали много кода Wxpython, то вышесказанное должно быть довольно знакомым для вас. Мы построим кадр как обычно, тогда мы создаем экземпляр нашего ViewerPanel и пропустите это Я Так что панель будет иметь ссылку на кадр. Следующий важный бит – это создание нашего слушателя Pubsub Singleton. Это позвонит рамку ResizeFrame Метод, который называется только при отображении новой картинки. Следующий важный кусок – это звонок inittoolbar , который создает панель инструментов на нашем кадре. Другой метод кадра – Onopendirectore , который очень похож на функцию просмотра из нашего первого приложения. В этом случае мы хотим выбрать всю папку и вытащить только пути файлов JPEG. Таким образом, мы используем файл Glob Python для этого. Как только это сделано, он отправляет сообщение PUBSUB на панель вместе со списком путей изображения.

Теперь мы можем взглянуть на самый важный кусок кода: ViewerPanel Отказ

import wx

from wx.lib.pubsub import Publisher

########################################################################
class ViewerPanel(wx.Panel):
    """"""

    #----------------------------------------------------------------------
    def __init__(self, parent):
        """Constructor"""
        wx.Panel.__init__(self, parent)
        
        width, height = wx.DisplaySize()
        self.picPaths = []
        self.currentPicture = 0
        self.totalPictures = 0
        self.photoMaxSize = height - 200
        Publisher().subscribe(self.updateImages, ("update images"))

        self.slideTimer = wx.Timer(None)
        self.slideTimer.Bind(wx.EVT_TIMER, self.update)
        
        self.layout()
        
    #----------------------------------------------------------------------
    def layout(self):
        """
        Layout the widgets on the panel
        """
        
        self.mainSizer = wx.BoxSizer(wx.VERTICAL)
        btnSizer = wx.BoxSizer(wx.HORIZONTAL)
        
        img = wx.EmptyImage(self.photoMaxSize,self.photoMaxSize)
        self.imageCtrl = wx.StaticBitmap(self, wx.ID_ANY, 
                                         wx.BitmapFromImage(img))
        self.mainSizer.Add(self.imageCtrl, 0, wx.ALL|wx.CENTER, 5)
        self.imageLabel = wx.StaticText(self, label="")
        self.mainSizer.Add(self.imageLabel, 0, wx.ALL|wx.CENTER, 5)
        
        btnData = [("Previous", btnSizer, self.onPrevious),
                   ("Slide Show", btnSizer, self.onSlideShow),
                   ("Next", btnSizer, self.onNext)]
        for data in btnData:
            label, sizer, handler = data
            self.btnBuilder(label, sizer, handler)
            
        self.mainSizer.Add(btnSizer, 0, wx.CENTER)
        self.SetSizer(self.mainSizer)
            
    #----------------------------------------------------------------------
    def btnBuilder(self, label, sizer, handler):
        """
        Builds a button, binds it to an event handler and adds it to a sizer
        """
        btn = wx.Button(self, label=label)
        btn.Bind(wx.EVT_BUTTON, handler)
        sizer.Add(btn, 0, wx.ALL|wx.CENTER, 5)
        
    #----------------------------------------------------------------------
    def loadImage(self, image):
        """"""
        image_name = os.path.basename(image)
        img = wx.Image(image, wx.BITMAP_TYPE_ANY)
        # scale the image, preserving the aspect ratio
        W = img.GetWidth()
        H = img.GetHeight()
        if W > H:
            NewW = self.photoMaxSize
            NewH = self.photoMaxSize * H / W
        else:
            NewH = self.photoMaxSize
            NewW = self.photoMaxSize * W / H
        img = img.Scale(NewW,NewH)

        self.imageCtrl.SetBitmap(wx.BitmapFromImage(img))
        self.imageLabel.SetLabel(image_name)
        self.Refresh()
        Publisher().sendMessage("resize", "")
        
    #----------------------------------------------------------------------
    def nextPicture(self):
        """
        Loads the next picture in the directory
        """
        if self.currentPicture == self.totalPictures-1:
            self.currentPicture = 0
        else:
            self.currentPicture += 1
        self.loadImage(self.picPaths[self.currentPicture])
        
    #----------------------------------------------------------------------
    def previousPicture(self):
        """
        Displays the previous picture in the directory
        """
        if self.currentPicture == 0:
            self.currentPicture = self.totalPictures - 1
        else:
            self.currentPicture -= 1
        self.loadImage(self.picPaths[self.currentPicture])
        
    #----------------------------------------------------------------------
    def update(self, event):
        """
        Called when the slideTimer's timer event fires. Loads the next
        picture from the folder by calling th nextPicture method
        """
        self.nextPicture()
        
    #----------------------------------------------------------------------
    def updateImages(self, msg):
        """
        Updates the picPaths list to contain the current folder's images
        """
        self.picPaths = msg.data
        self.totalPictures = len(self.picPaths)
        self.loadImage(self.picPaths[0])
        
    #----------------------------------------------------------------------
    def onNext(self, event):
        """
        Calls the nextPicture method
        """
        self.nextPicture()
    
    #----------------------------------------------------------------------
    def onPrevious(self, event):
        """
        Calls the previousPicture method
        """
        self.previousPicture()
    
    #----------------------------------------------------------------------
    def onSlideShow(self, event):
        """
        Starts and stops the slideshow
        """
        btn = event.GetEventObject()
        label = btn.GetLabel()
        if label == "Slide Show":
            self.slideTimer.Start(3000)
            btn.SetLabel("Stop")
        else:
            self.slideTimer.Stop()
            btn.SetLabel("Slide Show")        

Вместо того, чтобы перейти на каждый метод в этом классе, мы просто сделаем тур по основным моментам. Не волнуйся! Методы, которые мы пропускаем, – это супер легко понять, и если вы просто не понимаете, не стесняйтесь комментировать эту статью или задать вопрос о Список рассылки wxpython Отказ Наш первый порядок бизнеса состоит в том, чтобы посмотреть на init. По большей части это довольно нормальная инициализация, но у нас также есть пара глазовых линий:

width, height = wx.DisplaySize()
self.photoMaxSize = height - 200

Что это за все? Ну, идея здесь состоит в том, чтобы получить разрешение монитора пользователя, а затем разместить приложение, чтобы быть только правой высотой. Мы хотим, чтобы он был подходит над панелью задач, а ниже верхней части экрана. Это все, что мы здесь делаем. Обратите внимание, что это было проверено на Windows 7, поэтому вам может потребоваться настроить соответственно для вашей ОС выбора. LoadImage почти точно так же, как тот, который мы видели в нашем первом примере. Единственное отличие состоит в том, что мы используем PubSub, чтобы сообщить кадра для изменения изменений. Технически, вы также можете сделать что-то вроде: self.frame.sizer.fit (self.frame). Это известно как плохой код. Вместо этого используйте метод PUBSUB.

TallyPicture и предыдущий PROVESTURE Методы очень похожи и, вероятно, должны быть объединены, но на данный момент мы оставим их как есть. В обоих этих методах мы используем свойство CurrentPicture и добавление или вычесть по мере необходимости для перехода на следующую или предыдущую картинку. Мы также проверяем, найдут ли мы на верхнюю или нижнюю границу (то есть нулевые или тотальныеPTRETURE) и соответствующим образом сбрасываете CENTEPICTURE. Это позволяет нам верить все фотографии навсегда. Теперь, когда мы накрыли езды на велосипеде, нам нужно выяснить, как сделать слайд-шоу. Это на самом деле довольно легко.

Во-первых, мы создаем объект wx.timer и связывают его с Обновить метод. Когда нажата кнопка «слайд-шоу», таймер запускается или остановлен. Если начнется, то таймер будет стрелять каждые 3 секунды секунды) и что позвонит Обновить Метод, который вызывает TallyPicture метод. Как видите, Python делает все очень просто. Остальные способы представляют собой простые методы утилиты, которые называются одним из остальных.

Обертывание

Я надеюсь, что вы нашли это интересно. Я буду работать над расширением этого примера в чем-то даже кулере, и я обязательно поделился тем, что я придумал здесь. На днях, кто-то на канале WXPYPHON IRC думал, что было бы круто, если бы была серия статей на простых работающих приложениях WXPYPHON. Рассмотрим это первым (или вторым, если вы рассчитываете WxPymail). Надеюсь, я могу придумать некоторые другие интересные.

Примечание. Этот код был протестирован на главном (32-битный) Windows 7 (32-битный), WxPython 2.8.10.1, Python 2.6

Загрузки

  • ImageViewer.zip
  • Imageviewer.tar.tar.tar