qml: remove dependency "Pillow" (and its transitive deps)
closes https://github.com/spesmilo/electrum/issues/9572
This commit is contained in:
@@ -76,7 +76,6 @@ requirements =
|
||||
cryptography,
|
||||
pyqt6sip,
|
||||
pyqt6,
|
||||
pillow,
|
||||
libzbar
|
||||
|
||||
# (str) Presplash of the application
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
import os
|
||||
|
||||
from pythonforandroid.recipes.Pillow import PillowRecipe
|
||||
from pythonforandroid.util import load_source
|
||||
|
||||
util = load_source('util', os.path.join(os.path.dirname(os.path.dirname(__file__)), 'util.py'))
|
||||
|
||||
|
||||
assert PillowRecipe._version == "8.4.0"
|
||||
assert PillowRecipe.depends == ['png', 'jpeg', 'freetype', 'setuptools', 'python3']
|
||||
assert PillowRecipe.python_depends == []
|
||||
|
||||
|
||||
class PillowRecipePinned(util.InheritedRecipeMixin, PillowRecipe):
|
||||
sha512sum = "d395f69ccb37c52a3b6f45836700ffbc3173afae31848cc61d7b47db88ca1594541023beb9a14fd9067aca664e182c7d6e3300ab3e3095c31afe8dcbc6e08233"
|
||||
|
||||
|
||||
recipe = PillowRecipePinned()
|
||||
@@ -1,18 +0,0 @@
|
||||
import os
|
||||
|
||||
from pythonforandroid.recipes.freetype import FreetypeRecipe
|
||||
from pythonforandroid.util import load_source
|
||||
|
||||
util = load_source('util', os.path.join(os.path.dirname(os.path.dirname(__file__)), 'util.py'))
|
||||
|
||||
|
||||
assert FreetypeRecipe._version == "2.10.1"
|
||||
assert FreetypeRecipe.depends == []
|
||||
assert FreetypeRecipe.python_depends == []
|
||||
|
||||
|
||||
class FreetypeRecipePinned(util.InheritedRecipeMixin, FreetypeRecipe):
|
||||
sha512sum = "346c682744bcf06ca9d71265c108a242ad7d78443eff20142454b72eef47ba6d76671a6e931ed4c4c9091dd8f8515ebdd71202d94b073d77931345ff93cfeaa7"
|
||||
|
||||
|
||||
recipe = FreetypeRecipePinned()
|
||||
@@ -1,18 +0,0 @@
|
||||
import os
|
||||
|
||||
from pythonforandroid.recipes.jpeg import JpegRecipe
|
||||
from pythonforandroid.util import load_source
|
||||
|
||||
util = load_source('util', os.path.join(os.path.dirname(os.path.dirname(__file__)), 'util.py'))
|
||||
|
||||
|
||||
assert JpegRecipe._version == "2.0.1"
|
||||
assert JpegRecipe.depends == []
|
||||
assert JpegRecipe.python_depends == []
|
||||
|
||||
|
||||
class JpegRecipePinned(util.InheritedRecipeMixin, JpegRecipe):
|
||||
sha512sum = "d456515dcda7c5e2e257c9fd1441f3a5cff0d33281237fb9e3584bbec08a181c4b037947a6f87d805977ec7528df39b12a5d32f6e8db878a62bcc90482f86e0e"
|
||||
|
||||
|
||||
recipe = JpegRecipePinned()
|
||||
@@ -1,18 +0,0 @@
|
||||
import os
|
||||
|
||||
from pythonforandroid.recipes.png import PngRecipe
|
||||
from pythonforandroid.util import load_source
|
||||
|
||||
util = load_source('util', os.path.join(os.path.dirname(os.path.dirname(__file__)), 'util.py'))
|
||||
|
||||
|
||||
assert PngRecipe._version == "1.6.37"
|
||||
assert PngRecipe.depends == []
|
||||
assert PngRecipe.python_depends == []
|
||||
|
||||
|
||||
class PngRecipePinned(util.InheritedRecipeMixin, PngRecipe):
|
||||
sha512sum = "f304f8aaaee929dbeff4ee5260c1ab46d231dcb0261f40f5824b5922804b6b4ed64c91cbf6cc1e08554c26f50ac017899a5971190ca557bc3c11c123379a706f"
|
||||
|
||||
|
||||
recipe = PngRecipePinned()
|
||||
75
electrum/gui/common_qt/util.py
Normal file
75
electrum/gui/common_qt/util.py
Normal file
@@ -0,0 +1,75 @@
|
||||
from typing import Optional
|
||||
|
||||
from PyQt6 import QtGui
|
||||
from PyQt6.QtCore import Qt
|
||||
from PyQt6.QtGui import QColor, QPen, QPaintDevice
|
||||
import qrcode
|
||||
|
||||
from electrum.i18n import _
|
||||
|
||||
|
||||
def draw_qr(
|
||||
*,
|
||||
qr: Optional[qrcode.main.QRCode],
|
||||
paint_device: QPaintDevice, # target to paint on
|
||||
is_enabled: bool = True,
|
||||
min_boxsize: int = 2, # min size in pixels of single black/white unit box of the qr code
|
||||
) -> None:
|
||||
"""Draw 'qr' onto 'paint_device'.
|
||||
- qr.box_size is ignored. We will calculate our own boxsize to fill the whole size of paint_device.
|
||||
- qr.border is respected.
|
||||
"""
|
||||
black = QColor(0, 0, 0, 255)
|
||||
grey = QColor(196, 196, 196, 255)
|
||||
white = QColor(255, 255, 255, 255)
|
||||
black_pen = QPen(black) if is_enabled else QPen(grey)
|
||||
black_pen.setJoinStyle(Qt.PenJoinStyle.MiterJoin)
|
||||
|
||||
if not qr:
|
||||
qp = QtGui.QPainter()
|
||||
qp.begin(paint_device)
|
||||
qp.setBrush(white)
|
||||
qp.setPen(white)
|
||||
r = qp.viewport()
|
||||
qp.drawRect(0, 0, r.width(), r.height())
|
||||
qp.end()
|
||||
return
|
||||
|
||||
# note: next line can raise qrcode.exceptions.DataOverflowError (or ValueError)
|
||||
matrix = qr.get_matrix() # includes qr.border
|
||||
k = len(matrix)
|
||||
qp = QtGui.QPainter()
|
||||
qp.begin(paint_device)
|
||||
r = qp.viewport()
|
||||
framesize = min(r.width(), r.height())
|
||||
boxsize = int(framesize / k)
|
||||
if boxsize < min_boxsize:
|
||||
# The amount of data is still within what can fit into a QR code,
|
||||
# however we don't have enough pixels to draw it.
|
||||
qp.setBrush(white)
|
||||
qp.setPen(white)
|
||||
qp.drawRect(0, 0, r.width(), r.height())
|
||||
qp.setBrush(black)
|
||||
qp.setPen(black)
|
||||
qp.drawText(0, 20, _("Cannot draw QR code") + ":")
|
||||
qp.drawText(0, 40, _("Not enough space available."))
|
||||
qp.end()
|
||||
return
|
||||
size = k * boxsize
|
||||
left = (framesize - size) / 2
|
||||
top = (framesize - size) / 2
|
||||
# Draw white background with margin
|
||||
qp.setBrush(white)
|
||||
qp.setPen(white)
|
||||
qp.drawRect(0, 0, framesize, framesize)
|
||||
# Draw qr code
|
||||
qp.setBrush(black if is_enabled else grey)
|
||||
qp.setPen(black_pen)
|
||||
for r in range(k):
|
||||
for c in range(k):
|
||||
if matrix[r][c]:
|
||||
qp.drawRect(
|
||||
int(left + c * boxsize), int(top + r * boxsize),
|
||||
boxsize - 1, boxsize - 1)
|
||||
qp.end()
|
||||
|
||||
@@ -15,7 +15,7 @@ Item {
|
||||
|
||||
Rectangle {
|
||||
id: r
|
||||
width: _qrprops.modules * _qrprops.box_size
|
||||
width: _qrprops.qr_pixelsize
|
||||
height: width
|
||||
color: 'white'
|
||||
}
|
||||
@@ -29,8 +29,8 @@ Item {
|
||||
color: 'white'
|
||||
x: (parent.width - width) / 2
|
||||
y: (parent.height - height) / 2
|
||||
width: _qrprops.icon_modules * _qrprops.box_size
|
||||
height: _qrprops.icon_modules * _qrprops.box_size
|
||||
width: _qrprops.icon_pixelsize
|
||||
height: _qrprops.icon_pixelsize
|
||||
|
||||
Image {
|
||||
visible: _qrprops.valid
|
||||
|
||||
@@ -5,8 +5,6 @@ from qrcode.exceptions import DataOverflowError
|
||||
import math
|
||||
import urllib
|
||||
|
||||
from PIL import ImageQt
|
||||
|
||||
from PyQt6.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject, QRect
|
||||
from PyQt6.QtGui import QImage, QColor
|
||||
from PyQt6.QtQuick import QQuickImageProvider
|
||||
@@ -22,6 +20,7 @@ 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
|
||||
from electrum.gui.common_qt.util import draw_qr
|
||||
|
||||
|
||||
class QEQRParser(QObject):
|
||||
@@ -125,6 +124,11 @@ class QEQRParser(QObject):
|
||||
|
||||
|
||||
class QEQRImageProvider(QQuickImageProvider):
|
||||
MAX_QR_PIXELSIZE = 400
|
||||
ERROR_CORRECT_LEVEL = qrcode.constants.ERROR_CORRECT_M
|
||||
# ^ note: this is higher than for desktop. but on desktop we don't put a logo in the middle.
|
||||
QR_BORDER = 2
|
||||
|
||||
def __init__(self, max_size, parent=None):
|
||||
super().__init__(QQuickImageProvider.ImageType.Image)
|
||||
self._max_size = max_size
|
||||
@@ -147,20 +151,18 @@ class QEQRImageProvider(QQuickImageProvider):
|
||||
uri = uri._replace(query=query)
|
||||
qstr = urllib.parse.urlunparse(uri)
|
||||
|
||||
qr = qrcode.QRCode(version=1, border=2)
|
||||
qr.add_data(qstr)
|
||||
qr = qrcode.main.QRCode(border=self.QR_BORDER, error_correction=self.ERROR_CORRECT_LEVEL)
|
||||
|
||||
# calculate best box_size
|
||||
pixelsize = min(self._max_size, 400)
|
||||
pixelsize = min(self._max_size, self.MAX_QR_PIXELSIZE)
|
||||
try:
|
||||
modules = 17 + 4 * qr.best_fit() + qr.border * 2
|
||||
qr.add_data(qstr)
|
||||
modules = len(qr.get_matrix())
|
||||
qr.box_size = math.floor(pixelsize/modules)
|
||||
|
||||
qr.make(fit=True)
|
||||
|
||||
pimg = qr.make_image(fill_color='black', back_color='white')
|
||||
self.qimg = ImageQt.ImageQt(pimg)
|
||||
except DataOverflowError:
|
||||
self.qimg = QImage(modules * qr.box_size, modules * qr.box_size, QImage.Format.Format_RGB32)
|
||||
draw_qr(qr=qr, paint_device=self.qimg)
|
||||
except (ValueError, qrcode.exceptions.DataOverflowError):
|
||||
# fake it
|
||||
modules = 17 + qr.border * 2
|
||||
box_size = math.floor(pixelsize/modules)
|
||||
@@ -179,15 +181,18 @@ class QEQRImageProviderHelper(QObject):
|
||||
|
||||
@pyqtSlot(str, result='QVariantMap')
|
||||
def getDimensions(self, qstr):
|
||||
qr = qrcode.QRCode(version=1, border=2)
|
||||
qr.add_data(qstr)
|
||||
qr = qrcode.QRCode(
|
||||
border=QEQRImageProvider.QR_BORDER,
|
||||
error_correction=QEQRImageProvider.ERROR_CORRECT_LEVEL,
|
||||
)
|
||||
|
||||
# calculate best box_size
|
||||
pixelsize = min(self._max_size, 400)
|
||||
pixelsize = min(self._max_size, QEQRImageProvider.MAX_QR_PIXELSIZE)
|
||||
try:
|
||||
modules = 17 + 4 * qr.best_fit() + qr.border * 2
|
||||
qr.add_data(qstr)
|
||||
modules = len(qr.get_matrix())
|
||||
valid = True
|
||||
except DataOverflowError:
|
||||
except (ValueError, qrcode.exceptions.DataOverflowError):
|
||||
# fake it
|
||||
modules = 17 + qr.border * 2
|
||||
valid = False
|
||||
@@ -198,8 +203,7 @@ class QEQRImageProviderHelper(QObject):
|
||||
icon_modules += (icon_modules+1) % 2 # force odd
|
||||
|
||||
return {
|
||||
'modules': modules,
|
||||
'box_size': qr.box_size,
|
||||
'icon_modules': icon_modules,
|
||||
'qr_pixelsize': modules * qr.box_size,
|
||||
'icon_pixelsize': icon_modules * qr.box_size,
|
||||
'valid': valid
|
||||
}
|
||||
|
||||
@@ -3,13 +3,13 @@ from typing import Optional
|
||||
import qrcode
|
||||
import qrcode.exceptions
|
||||
|
||||
from PyQt6.QtGui import QColor, QPen
|
||||
import PyQt6.QtGui as QtGui
|
||||
from PyQt6.QtCore import Qt, QRect
|
||||
from PyQt6.QtCore import QRect
|
||||
from PyQt6.QtWidgets import QApplication, QVBoxLayout, QHBoxLayout, QPushButton, QWidget
|
||||
|
||||
from electrum.i18n import _
|
||||
from electrum.simple_config import SimpleConfig
|
||||
from electrum.gui.common_qt.util import draw_qr
|
||||
|
||||
from .util import WindowModalDialog, WWLabel, getSaveFileName
|
||||
|
||||
@@ -34,8 +34,7 @@ class QRCodeWidget(QWidget):
|
||||
if data:
|
||||
qr = qrcode.QRCode(
|
||||
error_correction=qrcode.constants.ERROR_CORRECT_L,
|
||||
box_size=10,
|
||||
border=0,
|
||||
border=1,
|
||||
)
|
||||
try:
|
||||
qr.add_data(data)
|
||||
@@ -57,53 +56,12 @@ class QRCodeWidget(QWidget):
|
||||
def paintEvent(self, e):
|
||||
if not self.data:
|
||||
return
|
||||
|
||||
black = QColor(0, 0, 0, 255)
|
||||
grey = QColor(196, 196, 196, 255)
|
||||
white = QColor(255, 255, 255, 255)
|
||||
black_pen = QPen(black) if self.isEnabled() else QPen(grey)
|
||||
black_pen.setJoinStyle(Qt.PenJoinStyle.MiterJoin)
|
||||
|
||||
if not self.qr:
|
||||
qp = QtGui.QPainter()
|
||||
qp.begin(self)
|
||||
qp.setBrush(white)
|
||||
qp.setPen(white)
|
||||
r = qp.viewport()
|
||||
qp.drawRect(0, 0, r.width(), r.height())
|
||||
qp.end()
|
||||
return
|
||||
|
||||
matrix = self.qr.get_matrix()
|
||||
k = len(matrix)
|
||||
qp = QtGui.QPainter()
|
||||
qp.begin(self)
|
||||
r = qp.viewport()
|
||||
framesize = min(r.width(), r.height())
|
||||
self._framesize = framesize
|
||||
boxsize = int(framesize/(k + 2))
|
||||
if boxsize < self.MIN_BOXSIZE:
|
||||
qp.drawText(0, 20, _("Cannot draw QR code")+":")
|
||||
qp.drawText(0, 40, _("Not enough space available. Try increasing the window size."))
|
||||
qp.end()
|
||||
return
|
||||
size = k*boxsize
|
||||
left = (framesize - size)/2
|
||||
top = (framesize - size)/2
|
||||
# Draw white background with margin
|
||||
qp.setBrush(white)
|
||||
qp.setPen(white)
|
||||
qp.drawRect(0, 0, framesize, framesize)
|
||||
# Draw qr code
|
||||
qp.setBrush(black if self.isEnabled() else grey)
|
||||
qp.setPen(black_pen)
|
||||
for r in range(k):
|
||||
for c in range(k):
|
||||
if matrix[r][c]:
|
||||
qp.drawRect(
|
||||
int(left+c*boxsize), int(top+r*boxsize),
|
||||
boxsize - 1, boxsize - 1)
|
||||
qp.end()
|
||||
draw_qr(
|
||||
qr=self.qr,
|
||||
paint_device=self,
|
||||
is_enabled=self.isEnabled(),
|
||||
min_boxsize=self.MIN_BOXSIZE,
|
||||
)
|
||||
|
||||
def grab(self) -> QtGui.QPixmap:
|
||||
"""Overrides QWidget.grab to only include the QR code itself,
|
||||
|
||||
2
setup.py
2
setup.py
@@ -45,7 +45,7 @@ extras_require = {
|
||||
'gui': ['pyqt6'],
|
||||
'crypto': ['cryptography>=2.6'],
|
||||
'tests': ['pycryptodomex>=3.7', 'cryptography>=2.6', 'pyaes>=0.1a1'],
|
||||
'qml_gui': ['pyqt6', 'Pillow>=8.4.0']
|
||||
'qml_gui': ['pyqt6<6.6', 'pyqt6-qt6<6.6']
|
||||
}
|
||||
# 'full' extra that tries to grab everything an enduser would need (except for libsecp256k1...)
|
||||
extras_require['full'] = [pkg for sublist in
|
||||
|
||||
Reference in New Issue
Block a user