ThomasV
9 years ago
4 changed files with 0 additions and 552 deletions
@ -1,60 +0,0 @@ |
|||
'''QrScanner Base Abstract implementation |
|||
''' |
|||
|
|||
__all__ = ('ScannerBase', 'QRScanner') |
|||
|
|||
from collections import namedtuple |
|||
|
|||
from kivy.uix.anchorlayout import AnchorLayout |
|||
from kivy.core import core_select_lib |
|||
from kivy.metrics import dp |
|||
from kivy.properties import ListProperty, BooleanProperty |
|||
from kivy.factory import Factory |
|||
|
|||
|
|||
class ScannerBase(AnchorLayout): |
|||
''' Base implementation for camera based scanner |
|||
''' |
|||
camera_size = ListProperty([320, 240] if dp(1) < 2 else [640, 480]) |
|||
|
|||
symbols = ListProperty([]) |
|||
|
|||
# XXX can't work now, due to overlay. |
|||
show_bounds = BooleanProperty(False) |
|||
|
|||
running = BooleanProperty(False) |
|||
|
|||
Qrcode = namedtuple('Qrcode', |
|||
['type', 'data', 'bounds', 'quality', 'count']) |
|||
|
|||
def start(self): |
|||
pass |
|||
|
|||
def stop(self): |
|||
pass |
|||
|
|||
def on_symbols(self, instance, value): |
|||
#if self.show_bounds: |
|||
# self.update_bounds() |
|||
pass |
|||
|
|||
def update_bounds(self): |
|||
self.canvas.after.remove_group('bounds') |
|||
if not self.symbols: |
|||
return |
|||
with self.canvas.after: |
|||
Color(1, 0, 0, group='bounds') |
|||
for symbol in self.symbols: |
|||
x, y, w, h = symbol.bounds |
|||
x = self._camera.right - x - w |
|||
y = self._camera.top - y - h |
|||
Line(rectangle=[x, y, w, h], group='bounds') |
|||
|
|||
|
|||
# load QRCodeDetector implementation |
|||
|
|||
QRScanner = core_select_lib('qr_scanner', ( |
|||
('android', 'scanner_android', 'ScannerAndroid'), |
|||
('camera', 'scanner_camera', 'ScannerCamera')), False, 'electrum_gui.kivy') |
|||
|
|||
Factory.register('QRScanner', cls=QRScanner) |
@ -1,390 +0,0 @@ |
|||
''' |
|||
Qrcode example application |
|||
========================== |
|||
|
|||
Author: Mathieu Virbel <mat@meltingrocks.com> |
|||
|
|||
License: |
|||
Copyright (c) 2013 Mathieu Virbel <mat@meltingrocks.com> |
|||
|
|||
Permission is hereby granted, free of charge, to any person obtaining a copy |
|||
of this software and associated documentation files (the "Software"), to deal |
|||
in the Software without restriction, including without limitation the rights |
|||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
|||
copies of the Software, and to permit persons to whom the Software is |
|||
furnished to do so, subject to the following conditions: |
|||
|
|||
The above copyright notice and this permission notice shall be included in |
|||
all copies or substantial portions of the Software. |
|||
|
|||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
|||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
|||
THE SOFTWARE. |
|||
|
|||
Featuring: |
|||
|
|||
- Android camera initialization |
|||
- Show the android camera into a Android surface that act as an overlay |
|||
- New AndroidWidgetHolder that control any android view as an overlay |
|||
- New ZbarQrcodeDetector that use AndroidCamera / PreviewFrame + zbar to |
|||
detect Qrcode. |
|||
|
|||
''' |
|||
|
|||
__all__ = ('ScannerAndroid', ) |
|||
|
|||
from kivy.utils import platform |
|||
if platform != 'android': |
|||
raise ImportError |
|||
|
|||
from electrum_gui.kivy.qr_scanner import ScannerBase |
|||
from kivy.properties import ObjectProperty, NumericProperty |
|||
from kivy.uix.widget import Widget |
|||
from kivy.uix.anchorlayout import AnchorLayout |
|||
from kivy.graphics import Color, Line |
|||
from jnius import autoclass, PythonJavaClass, java_method, cast |
|||
from android.runnable import run_on_ui_thread |
|||
|
|||
# preload java classes |
|||
System = autoclass('java.lang.System') |
|||
System.loadLibrary('iconv') |
|||
PythonActivity = autoclass('org.renpy.android.PythonActivity') |
|||
Camera = autoclass('android.hardware.Camera') |
|||
ImageScanner = autoclass('net.sourceforge.zbar.ImageScanner') |
|||
Image = autoclass('net.sourceforge.zbar.Image') |
|||
Symbol = autoclass('net.sourceforge.zbar.Symbol') |
|||
Config = autoclass('net.sourceforge.zbar.Config') |
|||
SurfaceView = autoclass('android.view.SurfaceView') |
|||
LayoutParams = autoclass('android.view.ViewGroup$LayoutParams') |
|||
ImageFormat = autoclass('android.graphics.ImageFormat') |
|||
LinearLayout = autoclass('android.widget.LinearLayout') |
|||
|
|||
|
|||
class PreviewCallback(PythonJavaClass): |
|||
'''Interface used to get back the preview frame of the Android Camera |
|||
''' |
|||
__javainterfaces__ = ('android.hardware.Camera$PreviewCallback', ) |
|||
|
|||
def __init__(self, callback): |
|||
super(PreviewCallback, self).__init__() |
|||
self.callback = callback |
|||
|
|||
@java_method('([BLandroid/hardware/Camera;)V') |
|||
def onPreviewFrame(self, data, camera): |
|||
self.callback(camera, data) |
|||
|
|||
|
|||
class SurfaceHolderCallback(PythonJavaClass): |
|||
'''Interface used to know exactly when the Surface used for the Android |
|||
Camera will be created and changed. |
|||
''' |
|||
|
|||
__javainterfaces__ = ('android.view.SurfaceHolder$Callback', ) |
|||
|
|||
def __init__(self, callback): |
|||
super(SurfaceHolderCallback, self).__init__() |
|||
self.callback = callback |
|||
|
|||
@java_method('(Landroid/view/SurfaceHolder;III)V') |
|||
def surfaceChanged(self, surface, fmt, width, height): |
|||
self.callback(fmt, width, height) |
|||
|
|||
@java_method('(Landroid/view/SurfaceHolder;)V') |
|||
def surfaceCreated(self, surface): |
|||
pass |
|||
|
|||
@java_method('(Landroid/view/SurfaceHolder;)V') |
|||
def surfaceDestroyed(self, surface): |
|||
pass |
|||
|
|||
|
|||
class AndroidWidgetHolder(Widget): |
|||
'''Act as a placeholder for an Android widget. |
|||
It will automatically add / remove the android view depending if the widget |
|||
view is set or not. The android view will act as an overlay, so any graphics |
|||
instruction in this area will be covered by the overlay. |
|||
''' |
|||
|
|||
view = ObjectProperty(allownone=True) |
|||
'''Must be an Android View |
|||
''' |
|||
|
|||
def __init__(self, **kwargs): |
|||
self._old_view = None |
|||
from kivy.core.window import Window |
|||
self._window = Window |
|||
kwargs['size_hint'] = (None, None) |
|||
super(AndroidWidgetHolder, self).__init__(**kwargs) |
|||
|
|||
def on_view(self, instance, view): |
|||
if self._old_view is not None: |
|||
layout = cast(LinearLayout, self._old_view.getParent()) |
|||
layout.removeView(self._old_view) |
|||
self._old_view = None |
|||
|
|||
if view is None: |
|||
return |
|||
|
|||
activity = PythonActivity.mActivity |
|||
activity.addContentView(view, LayoutParams(*self.size)) |
|||
view.setZOrderOnTop(True) |
|||
view.setX(self.x) |
|||
view.setY(self._window.height - self.y - self.height) |
|||
self._old_view = view |
|||
|
|||
def on_size(self, instance, size): |
|||
if self.view: |
|||
params = self.view.getLayoutParams() |
|||
params.width = self.width |
|||
params.height = self.height |
|||
self.view.setLayoutParams(params) |
|||
self.view.setY(self._window.height - self.y - self.height) |
|||
|
|||
def on_x(self, instance, x): |
|||
if self.view: |
|||
self.view.setX(x) |
|||
|
|||
def on_y(self, instance, y): |
|||
if self.view: |
|||
self.view.setY(self._window.height - self.y - self.height) |
|||
|
|||
|
|||
class AndroidCamera(Widget): |
|||
'''Widget for controling an Android Camera. |
|||
''' |
|||
|
|||
index = NumericProperty(0) |
|||
|
|||
__events__ = ('on_preview_frame', ) |
|||
|
|||
def __init__(self, **kwargs): |
|||
self._holder = None |
|||
self._android_camera = None |
|||
super(AndroidCamera, self).__init__(**kwargs) |
|||
self._holder = AndroidWidgetHolder(size=self.size, pos=self.pos) |
|||
self.add_widget(self._holder) |
|||
|
|||
@run_on_ui_thread |
|||
def stop(self): |
|||
self.running = False |
|||
if self._android_camera is None: |
|||
return |
|||
self._android_camera.setPreviewCallback(None) |
|||
self._android_camera.release() |
|||
self._android_camera = None |
|||
self._holder.view = None |
|||
|
|||
@run_on_ui_thread |
|||
def start(self): |
|||
self.running = True |
|||
if self._android_camera is not None: |
|||
return |
|||
|
|||
self._android_camera = Camera.open(self.index) |
|||
|
|||
# create a fake surfaceview to get the previewCallback working. |
|||
self._android_surface = SurfaceView(PythonActivity.mActivity) |
|||
surface_holder = self._android_surface.getHolder() |
|||
|
|||
# create our own surface holder to correctly call the next method when |
|||
# the surface is ready |
|||
self._android_surface_cb = SurfaceHolderCallback(self._on_surface_changed) |
|||
surface_holder.addCallback(self._android_surface_cb) |
|||
|
|||
# attach the android surfaceview to our android widget holder |
|||
self._holder.view = self._android_surface |
|||
|
|||
# set orientation |
|||
self._android_camera.setDisplayOrientation(90) |
|||
|
|||
def _on_surface_changed(self, fmt, width, height): |
|||
# internal, called when the android SurfaceView is ready |
|||
# FIXME if the size is not handled by the camera, it will failed. |
|||
params = self._android_camera.getParameters() |
|||
params.setPreviewSize(width, height) |
|||
self._android_camera.setParameters(params) |
|||
|
|||
# now that we know the camera size, we'll create 2 buffers for faster |
|||
# result (using Callback buffer approach, as described in Camera android |
|||
# documentation) |
|||
# it also reduce the GC collection |
|||
bpp = ImageFormat.getBitsPerPixel(params.getPreviewFormat()) / 8. |
|||
buf = '\x00' * int(width * height * bpp) |
|||
self._android_camera.addCallbackBuffer(buf) |
|||
self._android_camera.addCallbackBuffer(buf) |
|||
|
|||
# create a PreviewCallback to get back the onPreviewFrame into python |
|||
self._previewCallback = PreviewCallback(self._on_preview_frame) |
|||
|
|||
# connect everything and start the preview |
|||
self._android_camera.setPreviewCallbackWithBuffer(self._previewCallback); |
|||
self._android_camera.setPreviewDisplay(self._android_surface.getHolder()) |
|||
self._android_camera.startPreview(); |
|||
|
|||
def _on_preview_frame(self, camera, data): |
|||
# internal, called by the PreviewCallback when onPreviewFrame is |
|||
# received |
|||
self.dispatch('on_preview_frame', camera, data) |
|||
# reintroduce the data buffer into the queue |
|||
self._android_camera.addCallbackBuffer(data) |
|||
|
|||
def on_preview_frame(self, camera, data): |
|||
pass |
|||
|
|||
def on_size(self, instance, size): |
|||
if self._holder: |
|||
self._holder.size = size |
|||
|
|||
def on_pos(self, instance, pos): |
|||
if self._holder: |
|||
self._holder.pos = pos |
|||
|
|||
|
|||
from electrum.util import profiler |
|||
|
|||
use_camera = True |
|||
if use_camera: |
|||
from kivy.uix.camera import Camera |
|||
from kivy.clock import Clock |
|||
from PIL import Image as PILImage |
|||
class MyCamera(Camera): |
|||
def start(self): |
|||
self.play = True |
|||
def stop(self): |
|||
self.play = False |
|||
|
|||
class ScannerAndroid(ScannerBase): |
|||
'''Widget that use the AndroidCamera and zbar to detect qrcode. |
|||
When found, the `symbols` will be updated |
|||
''' |
|||
|
|||
def __init__(self, **kwargs): |
|||
super(ScannerAndroid, self).__init__(**kwargs) |
|||
if use_camera: |
|||
self._camera = MyCamera(resolution=self.camera_size) |
|||
Clock.schedule_interval(self._detect_qrcode_frame2, 1) |
|||
else: |
|||
self._camera = AndroidCamera( |
|||
size=self.camera_size, |
|||
size_hint=(None, None)) |
|||
self._camera.bind(on_preview_frame=self._detect_qrcode_frame) |
|||
|
|||
self.add_widget(self._camera) |
|||
|
|||
# create a scanner used for detecting qrcode |
|||
self._scanner = ImageScanner() |
|||
self._scanner.setConfig(0, Config.ENABLE, 0) |
|||
self._scanner.setConfig(Symbol.QRCODE, Config.ENABLE, 1) |
|||
self._scanner.setConfig(0, Config.X_DENSITY, 3) |
|||
self._scanner.setConfig(0, Config.Y_DENSITY, 3) |
|||
|
|||
|
|||
def start(self): |
|||
self._camera.start() |
|||
|
|||
def stop(self): |
|||
self._camera.stop() |
|||
|
|||
def _detect_qrcode_frame(self, instance, camera, data): |
|||
if not self.get_root_window(): |
|||
self.stop() |
|||
return |
|||
parameters = camera.getParameters() |
|||
size = parameters.getPreviewSize() |
|||
self.check_image(size.width, size.height, data) |
|||
|
|||
def _detect_qrcode_frame2(self, *args): |
|||
if not self._camera.play: |
|||
return |
|||
tex = self._camera.texture |
|||
if not tex: |
|||
return |
|||
im = PILImage.fromstring('RGBA', tex.size, tex.pixels) |
|||
im = im.convert('L') |
|||
self.check_image(tex.size[0], tex.size[1], im.tostring()) |
|||
|
|||
@profiler |
|||
def check_image(self, width, height, data): |
|||
print "zzz", width, height, len(data) |
|||
# the image we got by default from a camera is using the rgba format |
|||
# zbar only allow Y800/GREY image, so we first need to convert, |
|||
# then start the detection on the image |
|||
barcode = Image(width, height, 'NV21') |
|||
barcode.setData(data) |
|||
barcode = barcode.convert('Y800') |
|||
result = self._scanner.scanImage(barcode) |
|||
if result == 0: |
|||
self.symbols = [] |
|||
return |
|||
# we detected qrcode! extract and dispatch them |
|||
symbols = [] |
|||
it = barcode.getSymbols().iterator() |
|||
while it.hasNext(): |
|||
symbol = it.next() |
|||
qrcode = ScannerAndroid.Qrcode( |
|||
type=symbol.getType(), |
|||
data=symbol.getData(), |
|||
quality=symbol.getQuality(), |
|||
count=symbol.getCount(), |
|||
bounds=symbol.getBounds()) |
|||
symbols.append(qrcode) |
|||
self.symbols = symbols |
|||
|
|||
|
|||
''' |
|||
# can't work, due to the overlay. |
|||
def on_symbols(self, instance, value): |
|||
if self.show_bounds: |
|||
self.update_bounds() |
|||
|
|||
def update_bounds(self): |
|||
self.canvas.after.remove_group('bounds') |
|||
if not self.symbols: |
|||
return |
|||
with self.canvas.after: |
|||
Color(1, 0, 0, group='bounds') |
|||
for symbol in self.symbols: |
|||
x, y, w, h = symbol.bounds |
|||
x = self._camera.right - x - w |
|||
y = self._camera.top - y - h |
|||
Line(rectangle=[x, y, w, h], group='bounds') |
|||
''' |
|||
|
|||
|
|||
if __name__ == '__main__': |
|||
from kivy.lang import Builder |
|||
from kivy.app import App |
|||
|
|||
qrcode_kv = ''' |
|||
BoxLayout: |
|||
orientation: 'vertical' |
|||
|
|||
ZbarQrcodeDetector: |
|||
id: detector |
|||
|
|||
Label: |
|||
text: '\\n'.join(map(repr, detector.symbols)) |
|||
size_hint_y: None |
|||
height: '100dp' |
|||
|
|||
BoxLayout: |
|||
size_hint_y: None |
|||
height: '48dp' |
|||
|
|||
Button: |
|||
text: 'Scan a qrcode' |
|||
on_release: detector.start() |
|||
Button: |
|||
text: 'Stop detection' |
|||
on_release: detector.stop() |
|||
''' |
|||
|
|||
class QrcodeExample(App): |
|||
def build(self): |
|||
return Builder.load_string(qrcode_kv) |
|||
|
|||
QrcodeExample().run() |
@ -1,96 +0,0 @@ |
|||
from kivy.uix.camera import Camera |
|||
from kivy.clock import Clock |
|||
from kivy.utils import platform |
|||
|
|||
from electrum_gui.kivy.qr_scanner import ScannerBase |
|||
|
|||
import iconv |
|||
|
|||
try: |
|||
from zbar import ImageScanner, Config, Image, Symbol |
|||
except ImportError: |
|||
raise SystemError('unable to import zbar please make sure you have' |
|||
' it installed.\nFor mac osx: `brew install zbar then\n`' |
|||
'`pip install https://github.com/npinchot/zbar/archive/d3c1611ad2411fbdc3e79eb96ca704a63d30ae69.zip`') |
|||
try: |
|||
from PIL import Image as PILImage |
|||
except ImportError: |
|||
raise SystemError('unable to import Pil/pillow' |
|||
' please install one of the two.') |
|||
|
|||
__all__ = ('ScannerCamera', ) |
|||
|
|||
class ScannerCamera(ScannerBase): |
|||
'''Widget that use the kivy.uix.camera.Camera and zbar to detect |
|||
qrcode. When found, the `symbols` will be updated |
|||
''' |
|||
|
|||
def __init__(self, **kwargs): |
|||
super(ScannerCamera, self).__init__(**kwargs) |
|||
self._camera = None |
|||
# create a scanner used for detecting qrcode |
|||
self._scanner = ImageScanner() |
|||
self._scanner.parse_config('enable') |
|||
#self._scanner.setConfig(Symbol.QRCODE, Config.ENABLE, 1) |
|||
#self._scanner.setConfig(0, Config.X_DENSITY, 3) |
|||
#self._scanner.setConfig(0, Config.Y_DENSITY, 3) |
|||
|
|||
def start(self): |
|||
if not self._camera: |
|||
self._camera = Camera( |
|||
resolution=self.camera_size, |
|||
size_hint=(None, None)) |
|||
self.add_widget(self._camera) |
|||
self.bind(size=self._camera.setter('size')) |
|||
self.bind(pos=self._camera.setter('pos')) |
|||
else: |
|||
self._camera._camera.init_camera() |
|||
self._camera.play = True |
|||
Clock.schedule_interval(self._detect_qrcode_frame, 1/15) |
|||
|
|||
def stop(self): |
|||
if not self._camera: |
|||
return |
|||
self._camera.play = False |
|||
Clock.unschedule(self._detect_qrcode_frame) |
|||
# TODO: testing for various platforms(windows, mac) |
|||
if platform == 'linux': |
|||
self._camera._camera._pipeline.set_state(1) |
|||
#self._camera = None |
|||
|
|||
def _detect_qrcode_frame(self, *args): |
|||
# the image we got by default from a camera is using the rgba format |
|||
# zbar only allow Y800/GREY image, so we first need to convert, |
|||
# then start the detection on the image |
|||
if not self.get_root_window(): |
|||
self.stop() |
|||
return |
|||
cam = self._camera |
|||
tex = cam.texture |
|||
if not tex: |
|||
return |
|||
im = PILImage.fromstring('RGBA', tex.size, tex.pixels) |
|||
im = im.convert('L') |
|||
barcode = Image(tex.size[0], |
|||
tex.size[1], 'Y800', im.tostring()) |
|||
|
|||
result = self._scanner.scan(barcode) |
|||
|
|||
if result == 0: |
|||
self.symbols = [] |
|||
del(barcode) |
|||
return |
|||
|
|||
# we detected qrcode! extract and dispatch them |
|||
symbols = [] |
|||
for symbol in barcode.symbols: |
|||
qrcode = ScannerCamera.Qrcode( |
|||
type=symbol.type, |
|||
data=symbol.data, |
|||
quality=symbol.quality, |
|||
count=symbol.count, |
|||
bounds=symbol.location) |
|||
symbols.append(qrcode) |
|||
|
|||
self.symbols = symbols |
|||
del(barcode) |
Loading…
Reference in new issue