add qr reading from file to ScanQRTextEdit and SendTab
There was no ability to read qr codes contained in image files. This could lead to confusion in some contexts, as `on_file_input()` of ScanQRTextEdit will read the whole content of the file (instead of looking for qr codes). The revealer plugin for example generates png files containing qr codes and uses the `ScanQRTextEdit` to get user input, for the user it would seem logical to click on 'Read from file' to load the generated file, however this will result in the wrong data being loaded. Having the option to explicitly load a QR from file makes this clear. Also it seems useful, especially considering reading QR from screenshots doesn't work on wayland.
This commit is contained in:
@@ -161,6 +161,13 @@ class PayToEdit(QWidget, Logger, GenericInputHandler):
|
||||
show_error=self.send_tab.show_error,
|
||||
setText=self.try_payment_identifier,
|
||||
)
|
||||
self.on_qr_from_file_input_btn = partial(
|
||||
self.input_qr_from_file,
|
||||
allow_multi=False,
|
||||
config=self.config,
|
||||
show_error=self.send_tab.show_error,
|
||||
setText=self.try_payment_identifier,
|
||||
)
|
||||
self.on_input_file = partial(
|
||||
self.input_file,
|
||||
config=self.config,
|
||||
|
||||
@@ -25,7 +25,8 @@ import sys
|
||||
from typing import Callable, Optional, TYPE_CHECKING, Mapping, Sequence
|
||||
|
||||
from PyQt6.QtWidgets import QMessageBox, QWidget
|
||||
from PyQt6.QtGui import QImage
|
||||
from PyQt6.QtGui import QImage, QPainter, QColor
|
||||
from PyQt6.QtCore import QRect
|
||||
|
||||
from electrum.i18n import _
|
||||
from electrum.util import UserFacingException
|
||||
@@ -59,16 +60,34 @@ def scan_qrcode(
|
||||
def scan_qr_from_image(image: QImage) -> Sequence[QrCodeResult]:
|
||||
"""Might raise exception: MissingQrDetectionLib."""
|
||||
qr_reader = get_qr_reader()
|
||||
image_y800 = image.convertToFormat(QImage.Format.Format_Grayscale8)
|
||||
res = qr_reader.read_qr_code(
|
||||
image_y800.constBits().__int__(),
|
||||
image_y800.sizeInBytes(),
|
||||
image_y800.bytesPerLine(),
|
||||
image_y800.width(),
|
||||
image_y800.height(),
|
||||
)
|
||||
|
||||
for attempt in range(4):
|
||||
image_y800 = image.convertToFormat(QImage.Format.Format_Grayscale8)
|
||||
res = qr_reader.read_qr_code(
|
||||
image_y800.constBits().__int__(),
|
||||
image_y800.sizeInBytes(),
|
||||
image_y800.bytesPerLine(),
|
||||
image_y800.width(),
|
||||
image_y800.height(),
|
||||
)
|
||||
if res:
|
||||
break
|
||||
# zbar doesn't like qr codes that are too large in relation to the whole image
|
||||
image = _reduce_qr_code_density(image)
|
||||
return res
|
||||
|
||||
def _reduce_qr_code_density(image: QImage) -> QImage:
|
||||
""" Reduces the size of the qr code relative to the whole image. """
|
||||
new_image = QImage(image.width(), image.height(), QImage.Format.Format_RGB32)
|
||||
new_image.fill(QColor(255, 255, 255)) # Fill white
|
||||
|
||||
painter = QPainter(new_image)
|
||||
source_rect = QRect(0, 0, image.width(), image.height())
|
||||
target_rect = QRect(0, 0, int(image.width() * 0.75), int(image.height() * 0.75))
|
||||
painter.drawImage(target_rect, image, source_rect)
|
||||
painter.end()
|
||||
|
||||
return new_image
|
||||
|
||||
def find_system_cameras() -> Mapping[str, str]:
|
||||
"""Returns a camera_description -> camera_path map."""
|
||||
|
||||
@@ -47,6 +47,13 @@ class ScanQRTextEdit(ButtonsTextEdit, MessageBoxMixin):
|
||||
show_error=self.show_error,
|
||||
setText=setText,
|
||||
)
|
||||
self.on_qr_from_file_input_btn = partial(
|
||||
self.input_qr_from_file,
|
||||
allow_multi=allow_multi,
|
||||
config=config,
|
||||
show_error=self.show_error,
|
||||
setText=setText,
|
||||
)
|
||||
self.on_input_file = partial(
|
||||
self.input_file,
|
||||
config=config,
|
||||
@@ -62,7 +69,8 @@ class ScanQRTextEdit(ButtonsTextEdit, MessageBoxMixin):
|
||||
self.add_menu_button(
|
||||
options=[
|
||||
("picture_in_picture.png", _("Read QR code from screen"), self.on_qr_from_screenshot_input_btn),
|
||||
("file.png", _("Read file"), self.on_input_file),
|
||||
("qr_file.png", _("Read QR code from file"), self.on_qr_from_file_input_btn),
|
||||
("file.png", _("Read text from file"), self.on_input_file),
|
||||
],
|
||||
)
|
||||
self.add_qr_input_from_camera_button(config=config, show_error=self.show_error, allow_multi=allow_multi, setText=setText)
|
||||
@@ -72,7 +80,8 @@ class ScanQRTextEdit(ButtonsTextEdit, MessageBoxMixin):
|
||||
m.addSeparator()
|
||||
m.addAction(get_icon_camera(), _("Read QR code with camera"), self.on_qr_from_camera_input_btn)
|
||||
m.addAction(read_QIcon("picture_in_picture.png"), _("Read QR code from screen"), self.on_qr_from_screenshot_input_btn)
|
||||
m.addAction(read_QIcon("file.png"), _("Read file"), self.on_input_file)
|
||||
m.addAction(read_QIcon("qr_file.png"), _("Read QR code from file"), self.on_qr_from_file_input_btn)
|
||||
m.addAction(read_QIcon("file.png"), _("Read text from file"), self.on_input_file)
|
||||
m.exec(e.globalPos())
|
||||
|
||||
|
||||
|
||||
@@ -180,6 +180,7 @@ class SendTab(QWidget, MessageBoxMixin, Logger):
|
||||
|
||||
menu.addAction(get_icon_camera(), _("Read QR code with camera"), self.payto_e.on_qr_from_camera_input_btn)
|
||||
menu.addAction(read_QIcon("picture_in_picture.png"), _("Read QR code from screen"), self.payto_e.on_qr_from_screenshot_input_btn)
|
||||
menu.addAction(read_QIcon("qr_file.png"), _("Read QR code from file"), self.payto_e.on_qr_from_file_input_btn)
|
||||
menu.addAction(read_QIcon("file.png"), _("Read invoice from file"), self.payto_e.on_input_file)
|
||||
self.paytomany_menu = menu.addToggle(_("&Pay to many"), self.toggle_paytomany)
|
||||
menu.addSeparator()
|
||||
|
||||
@@ -832,6 +832,8 @@ class GenericInputHandler:
|
||||
fileName = getOpenFileName(
|
||||
parent=None,
|
||||
title='select file',
|
||||
# trying to open non-text things like pdfs makes electrum freeze
|
||||
filter="Text files (*.txt *.csv);;All files (*)",
|
||||
config=config,
|
||||
)
|
||||
if not fileName:
|
||||
@@ -852,6 +854,52 @@ class GenericInputHandler:
|
||||
except Exception as e:
|
||||
show_error(_('Invalid payment identifier in file') + ':\n' + repr(e))
|
||||
|
||||
def input_qr_from_file(
|
||||
self,
|
||||
*,
|
||||
allow_multi: bool = False,
|
||||
config: 'SimpleConfig',
|
||||
show_error: Callable[[str], None],
|
||||
setText: Callable[[str], None] = None,
|
||||
):
|
||||
from .qrreader import scan_qr_from_image
|
||||
if setText is None:
|
||||
setText = self.setText
|
||||
|
||||
file_name = getOpenFileName(
|
||||
parent=None,
|
||||
title=_("Select image file"),
|
||||
config=config,
|
||||
filter="Image files (*.png *.jpg *.jpeg *.bmp);;",
|
||||
)
|
||||
if not file_name:
|
||||
return
|
||||
image = QImage(file_name)
|
||||
if image.isNull():
|
||||
show_error(_("Failed to open image file."))
|
||||
return
|
||||
try:
|
||||
scan_result: Sequence[QrCodeResult] = scan_qr_from_image(image)
|
||||
except MissingQrDetectionLib as e:
|
||||
show_error(_("Unable to scan image.") + "\n" + repr(e))
|
||||
return
|
||||
if len(scan_result) < 1:
|
||||
show_error(_("No QR code was found in the image."))
|
||||
return
|
||||
if len(scan_result) > 1 and not allow_multi:
|
||||
show_error(_("More than one QR code was found in the image."))
|
||||
return
|
||||
|
||||
if len(scan_result) > 1:
|
||||
result_text = "\n".join([r.data for r in scan_result])
|
||||
else:
|
||||
result_text = scan_result[0].data
|
||||
|
||||
try:
|
||||
setText(result_text)
|
||||
except Exception as e:
|
||||
show_error(_("Couldn't set result") + ':\n' + repr(e))
|
||||
|
||||
def input_paste_from_clipboard(
|
||||
self,
|
||||
*,
|
||||
|
||||
Reference in New Issue
Block a user