1
0
Files
electrum/electrum/gui/qml/qeqr.py
Sander van Grieken d3e88064d0 Use screen size as upper bound for qr code size
also fix some typing issues
2022-07-07 18:29:01 +02:00

143 lines
4.5 KiB
Python

from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject, QUrl, QRect, QPoint
from PyQt5.QtGui import QImage,QColor
from PyQt5.QtQuick import QQuickImageProvider
import asyncio
import qrcode
import math
from PIL import Image, ImageQt
from electrum.logging import get_logger
from electrum.qrreader import get_qr_reader
from electrum.i18n import _
from electrum.util import profiler, get_asyncio_loop
class QEQRParser(QObject):
def __init__(self, text=None, parent=None):
super().__init__(parent)
self._text = text
self.qrreader = get_qr_reader()
if not self.qrreader:
raise Exception(_("The platform QR detection library is not available."))
_logger = get_logger(__name__)
busyChanged = pyqtSignal()
dataChanged = pyqtSignal()
imageChanged = pyqtSignal()
_busy = False
_image = None
@pyqtSlot('QImage')
def scanImage(self, image=None):
if self._busy:
self._logger.warning("Already processing an image. Check 'busy' property before calling scanImage")
return
if image == None:
self._logger.warning("No image to decode")
return
self._busy = True
self.busyChanged.emit()
self.logImageStats(image)
self._parseQR(image)
def logImageStats(self, image):
self._logger.info('width: ' + str(image.width()))
self._logger.info('height: ' + str(image.height()))
self._logger.info('depth: ' + str(image.depth()))
self._logger.info('format: ' + str(image.format()))
def _parseQR(self, image):
self.w = image.width()
self.h = image.height()
img_crop_rect = self._get_crop(image, 360)
frame_cropped = image.copy(img_crop_rect)
async def co_parse_qr(image):
# Convert to Y800 / GREY FourCC (single 8-bit channel)
# This creates a copy, so we don't need to keep the frame around anymore
frame_y800 = image.convertToFormat(QImage.Format_Grayscale8)
self.frame_id = 0
# Read the QR codes from the frame
self.qrreader_res = self.qrreader.read_qr_code(
frame_y800.constBits().__int__(),
frame_y800.byteCount(),
frame_y800.bytesPerLine(),
frame_y800.width(),
frame_y800.height(),
self.frame_id
)
if len(self.qrreader_res) > 0:
result = self.qrreader_res[0]
self._data = result
self.dataChanged.emit()
self._busy = False
self.busyChanged.emit()
asyncio.run_coroutine_threadsafe(co_parse_qr(frame_cropped), get_asyncio_loop())
def _get_crop(self, image: QImage, scan_size: int) -> QRect:
"""
Returns a QRect that is scan_size x scan_size in the middle of the resolution
"""
self.scan_pos_x = (image.width() - scan_size) // 2
self.scan_pos_y = (image.height() - scan_size) // 2
return QRect(self.scan_pos_x, self.scan_pos_y, scan_size, scan_size)
@pyqtProperty(bool, notify=busyChanged)
def busy(self):
return self._busy
@pyqtProperty('QImage', notify=imageChanged)
def image(self):
return self._image
@pyqtProperty(str, notify=dataChanged)
def data(self):
return self._data.data
@pyqtProperty('QPoint', notify=dataChanged)
def center(self):
(x,y) = self._data.center
return QPoint(x+self.scan_pos_x, y+self.scan_pos_y)
@pyqtProperty('QVariant', notify=dataChanged)
def points(self):
result = []
for item in self._data.points:
(x,y) = item
result.append(QPoint(x+self.scan_pos_x, y+self.scan_pos_y))
return result
class QEQRImageProvider(QQuickImageProvider):
def __init__(self, max_size, parent=None):
super().__init__(QQuickImageProvider.Image)
self._max_size = max_size
_logger = get_logger(__name__)
@profiler
def requestImage(self, qstr, size):
self._logger.debug('QR requested for %s' % qstr)
qr = qrcode.QRCode(version=1, border=2)
qr.add_data(qstr)
# calculate best box_size
pixelsize = min(self._max_size, 400)
modules = 17 + 4 * qr.best_fit()
qr.box_size = math.floor(pixelsize/(modules+2*2))
qr.make(fit=True)
pimg = qr.make_image(fill_color='black', back_color='white')
self.qimg = ImageQt.ImageQt(pimg)
return self.qimg, self.qimg.size()