1
0

Merge pull request #9782 from f321x/fix_qr_input_from_file

qt: add qr reading from file to ScanQRTextEdit and SendTab
This commit is contained in:
accumulator
2025-05-09 11:50:04 +02:00
committed by GitHub
6 changed files with 95 additions and 11 deletions

View File

@@ -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,

View File

@@ -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."""

View File

@@ -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())

View File

@@ -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()

View File

@@ -839,6 +839,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:
@@ -859,6 +861,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,
*,