pyjniusを使って写真を撮る - Kivy Advent Calendar 2013

KivyにはCameraというウィジェットがあるのですが、これは写真撮影ではなくて動画を画面上に映すものでした。しかもAndroidでは未実装とのこと。仮にこれが動けばスクリーンショットを取る機能と組み合わせて写真撮影できなくもないですが...

できれば内蔵のカメラ機能を使いたい思っていろいろ探したところ、Kivyのソースの中にカメラを扱うサンプルがありました。
以下はそれの焼き直し版です。
このアプリを起動すると真っ暗ですが、タップするとカメラモードになります。
そして撮影すると縮小してアルバム風の画面にどんどん保存されていきます。
 

from kivy.app import App
from kivy.clock import Clock
from kivy.uix.stacklayout import StackLayout
from kivy.uix.image import Image
from jnius import autoclass, cast
from android import activity
from functools import partial
from datetime import datetime
from os.path import exists

class ImageCaptureApp(App):

    RESULT_CODE = 0x5963

    Intent = autoclass('android.content.Intent')
    PythonActivity = autoclass('org.renpy.android.PythonActivity')
    MediaStore = autoclass('android.provider.MediaStore')
    Uri = autoclass('android.net.Uri')
    parcel = partial(cast, 'android.os.Parcelable')

    def build(self):
        self.root = StackLayout()
        self.root.bind(on_touch_up=self.do_capture)
        activity.bind(on_activity_result=self.on_activity_result)
        return self.root

    def get_filename(self):
        i = 0
        while True:
            i += 1
            fn = '/sdcard/{:%Y%m%d_%H%M}_{:03d}.png'.format(datetime.now(), i)
            if not exists(fn):
                return fn

    def do_capture(self, instance, value):
        self.filename = self.get_filename()
        uri = self.parcel(self.Uri.parse('file://' + self.filename))
        intent = self.Intent(self.MediaStore.ACTION_IMAGE_CAPTURE)
        intent.putExtra(self.MediaStore.EXTRA_OUTPUT, uri)
        self.PythonActivity.mActivity.startActivityForResult(intent, self.RESULT_CODE)

    def on_activity_result(self, requestCode, resultCode, intent):
        if requestCode == self.RESULT_CODE:
            Clock.schedule_once(partial(self.add_picture, self.filename), 0)

    def add_picture(self, filename, *args):
        self.root.add_widget(Image(source=filename, size=(320,320), size_hint=(None,None)))

    def on_pause(self):
        return True

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

解説

MediaStore.ACTION_IMAGE_CAPTUREのインテントを使えばカメラが起動して、EXTRA_OUTPUTにURLをセットすればそこに画像を保存してくれます。
そこまではpyjniusを使って何とか出来てたのですが、問題はそこから「どうやってアプリに制御を戻すか」で、Kivyのサンプル(examples/android/tackpicture/) を見るまでよく分からなかったところです。
結局Python for Androidandroidモジュール(※SL4Aやpygameandroidモジュールとは別です)を使えばon_activity_resultイベントが発生という形で受け取れるので、そのタイミングで保存した画像ファイルを取り込んでいます。

既知の問題

カメラを撮影しないで終了するケースに対応していません(エラーにはならないですが真っ白けの画像が入ってしまいます)。

追記

https://github.com/kivy/plyer/
本当はこちらを使った方がいいのかな。ビルドが必要だけど...
後で試してみます。