diff --git a/src/kivy_garden/zbarcam/zbarcam.py b/src/kivy_garden/zbarcam/zbarcam.py index 1d85e7e..741f0cc 100644 --- a/src/kivy_garden/zbarcam/zbarcam.py +++ b/src/kivy_garden/zbarcam/zbarcam.py @@ -1,5 +1,7 @@ +import collections import os -from collections import namedtuple +import queue +import threading import PIL from kivy.clock import Clock @@ -21,7 +23,7 @@ class ZBarCam(AnchorLayout): resolution = ListProperty([640, 480]) symbols = ListProperty([]) - Symbol = namedtuple('Symbol', ['type', 'data']) + Symbol = collections.namedtuple('Symbol', ['type', 'data']) # checking all possible types by default code_types = ListProperty(set(pyzbar.ZBarSymbol)) @@ -30,6 +32,10 @@ def __init__(self, **kwargs): # that way the `XCamera` import doesn't happen too early Builder.load_file(os.path.join(MODULE_DIRECTORY, "zbarcam.kv")) super().__init__(**kwargs) + + self._decoding_frame = threading.Event() + self._symbols_queue = queue.Queue() + Clock.schedule_once(lambda dt: self._setup()) def _setup(self): @@ -49,6 +55,7 @@ def _on_camera_ready(self, xcamera): Starts binding when the `xcamera._camera` instance is ready. """ xcamera._camera.bind(on_texture=self._on_texture) + Clock.schedule_interval(self._update_symbols, 0) def _remove_shoot_button(self): """ @@ -59,13 +66,32 @@ def _remove_shoot_button(self): shoot_button = xcamera.children[0] xcamera.remove_widget(shoot_button) - def _on_texture(self, instance): - self.symbols = self._detect_qrcode_frame( - texture=instance.texture, code_types=self.code_types) + def _on_texture(self, xcamera): + if not self._decoding_frame.is_set(): + self._decoding_frame.set() + threading.Thread( + target=self._threaded_detect_qrcode_frame, + args=( + xcamera.texture, + xcamera.texture.pixels, + self.code_types, + ), + ).start() + + def _threaded_detect_qrcode_frame(self, texture, pixels, code_types): + self._symbols_queue.put( + self._detect_qrcode_frame(texture, code_types, pixels) + ) + self._decoding_frame.clear() - @classmethod - def _detect_qrcode_frame(cls, texture, code_types): - image_data = texture.pixels + def _update_symbols(self, *args): + try: + self.symbols = self._symbols_queue.get_nowait() + except queue.Empty: + return + + def _detect_qrcode_frame(self, texture, code_types, pixels=None): + image_data = pixels or texture.pixels # Use pixels kwarg for threading size = texture.size # Fix for mode mismatch between texture.colorfmt and data returned by # texture.pixels. texture.pixels always returns RGBA, so that should @@ -76,9 +102,11 @@ def _detect_qrcode_frame(cls, texture, code_types): pil_image = fix_android_image(pil_image) symbols = [] codes = pyzbar.decode(pil_image, symbols=code_types) + for code in codes: symbol = ZBarCam.Symbol(type=code.type, data=code.data) symbols.append(symbol) + return symbols @property diff --git a/tests/kivy_garden/zbarcam/test_zbarcam.py b/tests/kivy_garden/zbarcam/test_zbarcam.py index 2c7370b..414894b 100644 --- a/tests/kivy_garden/zbarcam/test_zbarcam.py +++ b/tests/kivy_garden/zbarcam/test_zbarcam.py @@ -4,6 +4,7 @@ from kivy.base import EventLoop from kivy.core.image import Image + from kivy_garden.zbarcam import ZBarCam FIXTURE_DIR = os.path.join(