QRコード作成 - Kivy Advent Calendar 2013

※「QRコード」はデンソーウェーブの登録商標です

Python for Androidの中の「recipes」フォルダには、ビルドして組み込むためのモジュールが多数収められていますが、なぜかこの中にPythonで書かれているのでビルドの必要がないモジュールも含まれています。

pyqrcodeはQRコードを作るためのモジュールで、Pythonで書かれているので特にビルドする必要はなく、pyqrcode.pyをコピーするだけでQRコードを作成できるようになります。

これはPILに依存するので、プラットフォームによってはPILをインストールする必要があります。ただAndroidであればKivyLauncherにPILが内蔵されているので動作します。

(/sdcard/kivy/qrcode/)

android.txt Androidの場合必要
main.py 下記参照
pyqrcode.py 次のファイルをダウンロードして置いてください https://github.com/kivy/python-for-android/raw/master/recipes/pyqrcode/src/pyqrcode.pyPyPI公開しているもの (https://pypi.python.org/pypi/PyQRCode) とは別物です

(android.txt)

title=qrcode
author=cheeseshop
orientation=portrait

(main.py)

from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.textinput import TextInput
from kivy.graphics.texture import Texture
from pyqrcode import MakeQRImage

from kivy.lang import Builder
Builder.load_string('''
:
    qr: _qr
    url: _url
    msg: _msg
    canvas:
        Color:
            rgb: 0.8, 0.8, 0.7
        Rectangle:
            size: self.size
            pos: self.pos
    BoxLayout:
        orientation: 'vertical'
        Image:
            id: _qr
            size_hint: 1, 1
            opacity: 0
        TextInput:
            id: _url
            multiline: False
            size_hint: 0.9, 0.1
            pos_hint: {'center_x':0.5,'center_y':0.2}
            on_text_validate: root.makeimage()
        Label:
            id: _msg
            text: ''
            size_hint: 1, 1

''')

class QRCode(BoxLayout):

    def makeimage(self):
        try:
            im = MakeQRImage(self.url.text)
            self.msg.text = ''
        except:
            self.msg.text = 'cannot make QR Image'
            self.qr.opacity = 0
            return
        im = im.convert('RGB')
        tx = Texture.create(
            size=im.size,
            colorfmt='rgb',
            bufferfmt='ubyte')
        tx.blit_buffer(im.tostring())
        tx.flip_vertical()
        self.qr.texture = tx
        self.qr.opacity = 1

class QRCodeApp(App):

    def build(self):
        return QRCode()

if __name__ == '__main__':
    QRCodeApp().run()


テキスト欄にURLを入力してEnterキーを押すと、そのURLのQRコードを表示します。携帯どうしでURLを送りたいとき便利かもしれません。



解説

今回のポイントは「pyqrcodeが返すイメージオブジェクトをどのようにしてKivyで扱うか」です。QRコードはPILのImageオブジェクトとして作成されますが、KivyはOpenGLを元にした画像オブジェクト型で処理をしているからです。

可能であれば一時的な画像ファイルに書き出さずメモリ上ですませたいですのですが、KivyのTextureオブジェクトはソースを見る限りファイルからの取り込みしかできないようです。でも調べてみるとblit_buffer()というバイト列から取り込む方法がありました。

PILはtostring()を使えば画像情報をバイト列として吐き出せるので、フォーマットを合わせればメモリ上で画像のやり取りができそうです。いろいろ試してみたところ、PILからは「RGB」で吐き出し、Textureオブジェクトは「colorfmt='rgb', bufferfmt='ubyte'」で受け取ったら、表示はできるようになりました。

しかし携帯のQRコードリーダでテストしてみると、なぜか読み込んでくれない...

よくよく見ると、QRコードでおなじみ「回」みたいな形をしたマーカの位置が違っています。実はPILのバイト列は、Kivyが使っているOpenGLテクスチャとスキャン方向が逆になっているので、上下逆の画像として表示されてしまうのでした(ついでにQRコードの鏡像は等価ではないというのも初めて知りました)。

結局テクスチャの方でflip_vertical()とすることで対応しました。PILで上下反転するとビットマップ書き換えが発生してしまいますが、flip_vertical()は単にtex_coords (テクスチャ座標系) を反転するだけで効率がよいためです。