понедельник, 29 октября 2012 г.

Создаём QR коды на Python (Перевод)


Как-то раз я задумался: было бы забавно создать программу при помощи wxPython, которая генерирует QR коды и затем отображает их на экране. Поскольку, как понятно, я хотел написать её на Python, то после недолгих поисков я обнаружил трёх кандидатов на это дело:
Я воспользовался python-qrcode и pyqrnative, так как они прекрасно работают как на Windows, так и на Mac и Linux. Кроме того, они не требуют ничего больше, только Python Imaging Library. Проект pyqrcode требует дополнительных модулей и не работает на Windows, так что я даже не стал с ним заморачиваться. В итоге я взял старый код моего  Photo Viewer и немного его переиначил, чтобы получить генератор QR кодов. Если интересно - давайте поподробнее!

Приступаем

Как я уже упомянул в начале, Вам понадобится Python Imaging Library. Мы будем использовать wxPython для создания GUI, так что без него тоже не обойтись. И кроме того Вам понадобится загрузить python-qrcode и pyqrnative. Основное отличие, которое я обнаружил между ними - python-qrcode гораздо быстрее создаёт изображение и изображение именно привычного нам вида. По каким-то причинам, pyqrnative требует больше времени и создаёт более плотно выглядящие QR коды. Возможно, у каждого из проектов есть опции, меняющие вид кодов, но у обоих проектов отвратительная документация. В итоге пришлось копаться в исходниках больше, чем когда-либо.

Создаём QR коды

Итак, как только мы установили всё, что нам нужно, мы можем запустить следующий код и посмотреть, на что же способен Python:
import os
import wx
 
try:
    import qrcode
except ImportError:
    qrcode = None
 
try:
    import PyQRNative
except ImportError:
    PyQRNative = None
 
########################################################################
class QRPanel(wx.Panel):
    """"""
 
    #----------------------------------------------------------------------
    def __init__(self, parent):
        """Constructor"""
        wx.Panel.__init__(self, parent=parent)
        self.photo_max_size = 240
        sp = wx.StandardPaths.Get()
        self.defaultLocation = sp.GetDocumentsDir()
 
        img = wx.EmptyImage(240,240)
        self.imageCtrl = wx.StaticBitmap(self, wx.ID_ANY,
                                         wx.BitmapFromImage(img))
 
        qrDataLbl = wx.StaticText(self, label="Текст для перевода в QR код:")
        self.qrDataTxt = wx.TextCtrl(self, value="http://www.mousevspython.com", size=(200,-1))
        instructions = "Имя файла с QR кодом"
        instructLbl = wx.StaticText(self, label=instructions)
        self.qrPhotoTxt = wx.TextCtrl(self, size=(200,-1))
        browseBtn = wx.Button(self, label='Изменить место для сохранения')
        browseBtn.Bind(wx.EVT_BUTTON, self.onBrowse)
        defLbl = "Сохраняется по умолчанию в: " + self.defaultLocation
        self.defaultLocationLbl = wx.StaticText(self, label=defLbl)
 
        qrcodeBtn = wx.Button(self, label="Создать QR при помощи qrcode")
        qrcodeBtn.Bind(wx.EVT_BUTTON, self.onUseQrcode)
        pyQRNativeBtn = wx.Button(self, label="Создать QR при помощи PyQRNative")
        pyQRNativeBtn.Bind(wx.EVT_BUTTON, self.onUsePyQR)
 
        # компоновщик
        self.mainSizer = wx.BoxSizer(wx.VERTICAL)
        qrDataSizer = wx.BoxSizer(wx.HORIZONTAL)
        locationSizer = wx.BoxSizer(wx.HORIZONTAL)
        qrBtnSizer = wx.BoxSizer(wx.VERTICAL)
 
        qrDataSizer.Add(qrDataLbl, 0, wx.ALL, 5)
        qrDataSizer.Add(self.qrDataTxt, 1, wx.ALL|wx.EXPAND, 5)
        self.mainSizer.Add(wx.StaticLine(self, wx.ID_ANY),
                           0, wx.ALL|wx.EXPAND, 5)
        self.mainSizer.Add(qrDataSizer, 0, wx.EXPAND)
        self.mainSizer.Add(self.imageCtrl, 0, wx.ALL, 5)
        locationSizer.Add(instructLbl, 0, wx.ALL, 5)
        locationSizer.Add(self.qrPhotoTxt, 0, wx.ALL, 5)
        locationSizer.Add(browseBtn, 0, wx.ALL, 5)
        self.mainSizer.Add(locationSizer, 0, wx.ALL, 5)
        self.mainSizer.Add(self.defaultLocationLbl, 0, wx.ALL, 5)
 
        qrBtnSizer.Add(qrcodeBtn, 0, wx.ALL, 5)
        qrBtnSizer.Add(pyQRNativeBtn, 0, wx.ALL, 5)
        self.mainSizer.Add(qrBtnSizer, 0, wx.ALL|wx.CENTER, 10)
 
        self.SetSizer(self.mainSizer)
        self.Layout()
 
    #----------------------------------------------------------------------
    def onBrowse(self, event):
        """"""
        dlg = wx.DirDialog(self, "Выберите папку:",
                           style=wx.DD_DEFAULT_STYLE)
        if dlg.ShowModal() == wx.ID_OK:
            path = dlg.GetPath()
            self.defaultLocation = path
            self.defaultLocationLbl.SetLabel("Сохраняем в: %s" % path)
        dlg.Destroy()
 
    #----------------------------------------------------------------------
    def onUseQrcode(self, event):
        """

https://github.com/lincolnloop/python-qrcode

        """
        qr = qrcode.QRCode(version=1, box_size=10, border=4)
        qr.add_data(self.qrDataTxt.GetValue())
        qr.make(fit=True)
        x = qr.make_image()
 
        qr_file = os.path.join(self.defaultLocation, self.qrPhotoTxt.GetValue() + ".jpg")
        img_file = open(qr_file, 'wb')
        x.save(img_file, 'JPEG')
        img_file.close()
        self.showQRCode(qr_file)
 
    #----------------------------------------------------------------------
    def onUsePyQR(self, event):
        """

http://code.google.com/p/pyqrnative/

        """
        qr = PyQRNative.QRCode(20, PyQRNative.QRErrorCorrectLevel.L)
        qr.addData(self.qrDataTxt.GetValue())
        qr.make()
        im = qr.makeImage()
 
        qr_file = os.path.join(self.defaultLocation, self.qrPhotoTxt.GetValue() + ".jpg")
        img_file = open(qr_file, 'wb')
        im.save(img_file, 'JPEG')
        img_file.close()
        self.showQRCode(qr_file)
 
    #----------------------------------------------------------------------
    def showQRCode(self, filepath):
        """"""
        img = wx.Image(filepath, wx.BITMAP_TYPE_ANY)
        # масштабируем изображение
        W = img.GetWidth()
        H = img.GetHeight()
        if W > H:
            NewW = self.photo_max_size
            NewH = self.photo_max_size * H / W
        else:
            NewH = self.photo_max_size
            NewW = self.photo_max_size * W / H
        img = img.Scale(NewW,NewH)
 
        self.imageCtrl.SetBitmap(wx.BitmapFromImage(img))
        self.Refresh()
 
 
########################################################################
class QRFrame(wx.Frame):
    """"""
 
    #----------------------------------------------------------------------
    def __init__(self):
        """Constructor"""
        wx.Frame.__init__(self, None, title="QR Code Viewer", size=(550,500))
        panel = QRPanel(self)
 
if __name__ == "__main__":
    app = wx.App(False)
    frame = QRFrame()
    frame.Show()
    app.MainLoop()
Код для изменения и отображения картинки разъяснён в одной из предыдущих статей (не переведена), так что единственное, что может вызвать у Вас непонимание - два метода создания кодов: onUseQrcode и onUsePyQR. Я всего лишь взял примеры с их сайтов и немного поменял их, чтобы сгенерировать изображение с кодом. Они достаточно самоочевидны, но почти не документированны, так что я не смогу сказать Вам, что же тут происходит. И, хотя оба проекта страдают на момент написания от отсутствия документации, код, тем не менее работает:
Как можно видеть - вполне стандартный код. А этот, созданный PyQRNative, выглядит гораздо более "плотным":
Оба кода были корректно распознаны моим телефоном на  Android. Так что если Вам надо создать для вашего проекта QR код - я надеюсь, эта статья Вам поможет!

Обновление от 21/05/2012

Один из моих читателей (Mike Farmer) сообщил мне недавно, что в PyQRNative “первый аргумент - размер контейнера, а второй - избыточность / коррекция ошибок”. У меня были такие подозрения по поводу второго аргумента, но я не знаю, что делают эти уровни коррекции ошибок. К счастью, Mr. Farmer объяснил это мне: Если уровень коррекции ошибок низкий, то смазывание кода или его намокание приведёт его в нечитабельное состоянии. Если же Вы будете использовать более высокий уровень коррекции, то за счёт добавления / дублирования данных внутри кода, он сможет пережить даже потоки слёз. Более того, при высоком уровне коррекции ошибок Вы можете даже наложить на код дополнительное изображение, и он  это переживёт. В любом случае, если Вы изменяете первый параметр, Вы можете изменить размер кода. Зачем? Ну, чем больше информации Вы хотите засунуть в код, тем больше он должен быть. Mr. Farmer предложил забавный тестовый код, который позволяет вычислить минимальный возможный для вашей информации размер кода:

import PyQRNative
 
def makeQR(data_string,path,level=1):
    quality={1: PyQRNative.QRErrorCorrectLevel.L,
             2: PyQRNative.QRErrorCorrectLevel.M,
             3: PyQRNative.QRErrorCorrectLevel.Q,
             4: PyQRNative.QRErrorCorrectLevel.H}
    size=3
    while 1:
        try:
            q = PyQRNative.QRCode(size,quality[level])
            q.addData(data_string)
            q.make()
            im=q.makeImage()
            im.save(path,format="png")
            break
        except TypeError:
            size+=1

Исходник

Комментариев нет:

Отправить комментарий