[Kivy]Androidアプリで保存した画像の表示

プログラミング

Androidアプリで、作成した画像をピクチャフォルダに保存したところ、処理自体はエラーなく完了したのに、ギャラリー(アルバムとかライブラリとか)で確認すると出てこない。

Androidを再起動するとギャラリーにも出てくるのだが、普通に使っていて撮った写真とかをすぐに確認できないのは困ってしまう。

調べたところ、メディアスキャンというのをしてあげないとギャラリーに反映されないらしい。

メディアスキャンの実装サンプルです。

import os
from plyer import storagepath
picturesPath = storagepath.get_pictures_dir()

from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy import platform
from kivy.clock import Clock
from kivy.graphics.texture import Texture
from kivy.uix.camera import Camera
import time
import cv2
import numpy as np
from kivy.properties import StringProperty
from kivy.uix.popup import Popup

from kivy.core.window import Window

currentActivity = None
CLS_Activity = None
CLS_File = None
CLS_Uri = None
CLS_Intent = None
CLS_Context = None

class ImageFileSave(BoxLayout):

    def play(self, button):

        self.cam = Camera(index=0)
        self.cam.play = True
        Clock.schedule_interval(self.update, 1.0 / 30)
        button.disabled = True
        self.ids.capture_button.disabled = False

    def update(self, dt):
        height, width = self.cam.texture.height, self.cam.texture.width
        capture = np.frombuffer(self.cam.texture.pixels, np.uint8)
        capture = capture.reshape(height, width, 4)
        frame = capture[:,:,:3]
        if type(frame).__module__ == "numpy":
            ret = True
        if platform == "android":
            frame = cv2.flip(frame, 0)
            frame = cv2.rotate(frame, cv2.ROTATE_90_CLOCKWISE)
        self.image_data = frame
        if ret:
            buf = cv2.flip(frame, 0)
            image_texture = Texture.create(size=(frame.shape[1], frame.shape[0]), colorfmt='rgb')
            image_texture.blit_buffer(buf.tostring(), colorfmt='rgb', bufferfmt='ubyte')
            self.ids.camera_view.texture = image_texture

    def capture(self):
        
        if self.image_data is not None:

            timestr = time.strftime("%Y%m%d_%H%M%S")
            file_path = os.path.join(picturesPath , "output_{}.png".format(timestr))
            image_data = cv2.cvtColor(self.image_data, cv2.COLOR_RGB2BGR) # openCVの色の並びはBGR
            cv2.imwrite(file_path, image_data)
            
            # メディアスキャン
            self.android_rescan_MediaStore(file_path)

            # 完了メッセージ
            self.popup = MessagePopup(title='', message='seved' , separator_height=0)
            self.popup.open()

    def android_rescan_MediaStore(self, file_path):
        if platform == "android":
            uri = CLS_Uri.fromFile(CLS_File(file_path))
            mediaScanIntent = CLS_Intent(CLS_Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, uri)
            CLS_Context.sendBroadcast(mediaScanIntent)

class MessagePopup(Popup):
    message = StringProperty('')
    ok_text = StringProperty('OK')

    __events__ = ('on_ok',)

    def ok(self):
        self.dispatch('on_ok')
        self.dismiss()
    def on_ok(self):
        pass
    def __init__(self, **kwargs) -> None:
        super(MessagePopup, self).__init__(**kwargs)
        self.size_hint = (0.8, 0.3)
        self.pos_hint={'center_x':0.5, 'center_y':0.5}
        self.auto_dismiss = False

class TestImageFileSaveApp(App):

    def build(self):
        if platform == "android":
            Window.bind(on_keyboard=self.key_input)
            
            from jnius import autoclass, cast
            
            global currentActivity
            global CLS_Activity
            global CLS_File
            global CLS_Uri
            global CLS_Intent
            global CLS_Context

            PythonActivity = autoclass('org.kivy.android.PythonActivity')
            currentActivity = cast('android.app.Activity', PythonActivity.mActivity)
            CLS_Activity = autoclass('android.app.Activity')
            CLS_File = autoclass('java.io.File')
            CLS_Uri = autoclass('android.net.Uri')
            CLS_Intent = autoclass('android.content.Intent')
            CLS_Context = cast('android.content.Context', currentActivity.getApplicationContext())

            from android.permissions import request_permissions, Permission
            request_permissions([Permission.READ_EXTERNAL_STORAGE,
                                 Permission.CAMERA,
                                 Permission.WRITE_EXTERNAL_STORAGE])

        return ImageFileSave()
    
    def key_input(self, window, key, scancode, codepoint, modifier):
        if key == 27:
            return True
        else:
            return False

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

確認用のKVファイル

<ImageFileSave>:
    camera_view:camera_view
    BoxLayout:
        orientation: 'vertical'
        Image:
            id: camera_view
        Button:
            text: 'play'
            size_hint_y: 0.1
            on_press: root.play(self)
        Button:
            id:capture_button
            text: 'capture'
            size_hint_y: 0.1
            disabled: True
            on_press: root.capture()

<MessagePopup>:
    FloatLayout:
        Label:
            size_hint: 0.8, 0.6
            pos_hint: {'x': 0.1, 'y':0.4}
            text: root.message
            text_size: self.size
            halign: 'center'
            valign: 'middle'
        Button:
            size_hint: 0.4, 0.35
            pos_hint: {'x':0.5, 'y':0.05}
            text: root.ok_text
            on_release: root.ok()

サンプルソースがやたらと長くなってしまいましたが、メインとなる関数は「android_rescan_MediaStore」だけです。

使い方もシンプルで、画像を保存する処理(サンプルではimwrite)の後にファイルのパスを渡してあげるだけです。

Android API にアクセスするため Pyjnius を使っています。サンプルでは build内で分岐させていますが頭のグローバル変数宣言時に書いてもいいと思います。

from jnius import autoclass, cast

PythonActivity = autoclass('org.kivy.android.PythonActivity')
currentActivity = cast('android.app.Activity', PythonActivity.mActivity)
Activity = autoclass('android.app.Activity')
File = autoclass('java.io.File')
Uri = autoclass('android.net.Uri')
Intent = autoclass('android.content.Intent')
Context = cast('android.content.Context', currentActivity.getApplicationContext())

※サンプルで変数名についてる”CLS_”は、ラップしたJavaクラスだと分かりやすくするために適当につけただけで深い意味はないです。

他のサンプル部分については今回の主旨とは外れますが少し補足説明します。

動作確認のために、カメラからの画像取得メッセージ通知のためのクラスを使っています。これらについての使い方は別の記事で書いていますので気になる方はご覧ください。(宣伝)

保存先のピクチャパスの取得については、plyerを使っています。実際には、”/storage/emulated/0/Pictures”のようなパスになっていると思います。

以上です。

(参考)

https://developer.android.com/training/camera/photobasics?hl=ja#TaskGallery

コメント

タイトルとURLをコピーしました