qml: refactor qr scan to qt6
As the method of capturing frames is totally different, the animation when a QR is found has been removed.
This commit is contained in:
@@ -7,11 +7,13 @@ from pythonforandroid.util import load_source
|
||||
util = load_source('util', os.path.join(os.path.dirname(os.path.dirname(__file__)), 'util.py'))
|
||||
|
||||
assert Qt6Recipe._version == "6.4.3"
|
||||
# assert Qt6Recipe._version == "6.5.3"
|
||||
assert Qt6Recipe.depends == ['python3', 'hostqt6']
|
||||
assert Qt6Recipe.python_depends == []
|
||||
|
||||
class Qt6RecipePinned(util.InheritedRecipeMixin, Qt6Recipe):
|
||||
sha512sum = "0bdbe8b9a43390c98cf19e851ec5394bc78438d227cf9d0d7a3748aee9a32a7f14fc46f52d4fa283819f21413567080aee7225c566af5278557f5e1992674da3"
|
||||
# sha512sum = "ca8ea3b81c121886636988275f7fa8ae6d19f7be02669e63ab19b4285b611057a41279db9532c25ae87baa3904b010e1db68b899cd0eda17a5a8d3d87098b4d5"
|
||||
|
||||
|
||||
recipe = Qt6RecipePinned()
|
||||
|
||||
@@ -7,12 +7,12 @@ from typing import TYPE_CHECKING
|
||||
try:
|
||||
import PyQt6
|
||||
except Exception:
|
||||
sys.exit("Error: Could not import PyQt6 on Linux systems, you may try 'sudo apt-get install python3-pyqt6'")
|
||||
sys.exit("Error: Could not import PyQt6. On Linux systems, you may try 'sudo apt-get install python3-pyqt6'")
|
||||
|
||||
try:
|
||||
import PyQt6.QtQml
|
||||
except Exception:
|
||||
sys.exit("Error: Could not import PyQt6.QtQml on Linux systems, you may try 'sudo apt-get install python3-pyqt6.qtquick'")
|
||||
sys.exit("Error: Could not import PyQt6.QtQml. On Linux systems, you may try 'sudo apt-get install python3-pyqt6.qtquick'")
|
||||
|
||||
from PyQt6.QtCore import (Qt, QCoreApplication, QLocale, QTranslator, QTimer, QT_VERSION_STR, PYQT_VERSION_STR)
|
||||
from PyQt6.QtGui import QGuiApplication
|
||||
|
||||
@@ -20,11 +20,17 @@ ElDialog {
|
||||
header: null
|
||||
topPadding: 0 // dialog needs topPadding override
|
||||
|
||||
function doClose() {
|
||||
qrscan.stop()
|
||||
Qt.callLater(doReject)
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
spacing: 0
|
||||
|
||||
QRScan {
|
||||
id: qrscan
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
hint: scanDialog.hint
|
||||
|
||||
@@ -19,6 +19,11 @@ ElDialog {
|
||||
padding: 0
|
||||
topPadding: 0
|
||||
|
||||
onAboutToHide: {
|
||||
console.log('about to hide')
|
||||
qrscan.stop()
|
||||
}
|
||||
|
||||
function restart() {
|
||||
qrscan.restart()
|
||||
}
|
||||
@@ -34,6 +39,13 @@ ElDialog {
|
||||
}
|
||||
}
|
||||
|
||||
// override
|
||||
function doClose() {
|
||||
console.log('SendDialog doClose override') // doesn't trigger when going back??
|
||||
qrscan.stop()
|
||||
Qt.callLater(doReject)
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
spacing: 0
|
||||
@@ -55,7 +67,10 @@ ElDialog {
|
||||
Layout.preferredWidth: 1
|
||||
icon.source: '../../icons/copy_bw.png'
|
||||
text: qsTr('Paste')
|
||||
onClicked: dialog.dispatch(AppController.clipboardToText())
|
||||
onClicked: {
|
||||
qrscan.stop()
|
||||
dialog.dispatch(AppController.clipboardToText())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ Item {
|
||||
|
||||
function closeSendDialog() {
|
||||
if (_sendDialog) {
|
||||
_sendDialog.close()
|
||||
_sendDialog.doClose()
|
||||
_sendDialog = null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtMultimedia
|
||||
import QtQml
|
||||
|
||||
import org.electrum 1.0
|
||||
|
||||
@@ -12,73 +13,125 @@ Item {
|
||||
property string scanData
|
||||
property string hint
|
||||
|
||||
property bool _pointsVisible
|
||||
|
||||
signal found
|
||||
|
||||
function restart() {
|
||||
still.source = ''
|
||||
_pointsVisible = false
|
||||
active = true
|
||||
console.log('qrscan.restart')
|
||||
scanData = ''
|
||||
qr.reset()
|
||||
start()
|
||||
}
|
||||
|
||||
VideoOutput {
|
||||
id: vo
|
||||
function start() {
|
||||
console.log('qrscan.start')
|
||||
loader.item.startTimer.start()
|
||||
}
|
||||
|
||||
function stop() {
|
||||
console.log('qrscan.stop')
|
||||
scanner.active = false
|
||||
}
|
||||
|
||||
Item {
|
||||
id: points
|
||||
z: 100
|
||||
anchors.fill: parent
|
||||
// source: camera
|
||||
fillMode: VideoOutput.PreserveAspectCrop
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: (parent.height - parent.width) / 2
|
||||
visible: camera.cameraStatus == Camera.ActiveStatus
|
||||
anchors.top: parent.top
|
||||
color: Qt.rgba(0,0,0,0.5)
|
||||
}
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: (parent.height - parent.width) / 2
|
||||
visible: camera.cameraStatus == Camera.ActiveStatus
|
||||
anchors.bottom: parent.bottom
|
||||
color: Qt.rgba(0,0,0,0.5)
|
||||
}
|
||||
InfoTextArea {
|
||||
visible: scanner.hint
|
||||
background.opacity: 0.5
|
||||
iconStyle: InfoTextArea.IconStyle.None
|
||||
anchors {
|
||||
top: parent.top
|
||||
topMargin: constants.paddingXLarge
|
||||
left: parent.left
|
||||
leftMargin: constants.paddingXXLarge
|
||||
right: parent.right
|
||||
rightMargin: constants.paddingXXLarge
|
||||
Loader {
|
||||
id: loader
|
||||
anchors.fill: parent
|
||||
sourceComponent: scancomp
|
||||
onStatusChanged: {
|
||||
if (loader.status == Loader.Ready) {
|
||||
console.log('camera loaded')
|
||||
} else if (loader.status == Loader.Error) {
|
||||
console.log('camera load error')
|
||||
}
|
||||
text: scanner.hint
|
||||
}
|
||||
}
|
||||
|
||||
Image {
|
||||
id: still
|
||||
anchors.fill: vo
|
||||
}
|
||||
Component {
|
||||
id: scancomp
|
||||
|
||||
SequentialAnimation {
|
||||
id: foundAnimation
|
||||
PropertyAction { target: scanner; property: '_pointsVisible'; value: true}
|
||||
PauseAnimation { duration: 80 }
|
||||
PropertyAction { target: scanner; property: '_pointsVisible'; value: false}
|
||||
PauseAnimation { duration: 80 }
|
||||
PropertyAction { target: scanner; property: '_pointsVisible'; value: true}
|
||||
PauseAnimation { duration: 80 }
|
||||
PropertyAction { target: scanner; property: '_pointsVisible'; value: false}
|
||||
PauseAnimation { duration: 80 }
|
||||
PropertyAction { target: scanner; property: '_pointsVisible'; value: true}
|
||||
PauseAnimation { duration: 80 }
|
||||
PropertyAction { target: scanner; property: '_pointsVisible'; value: false}
|
||||
PauseAnimation { duration: 80 }
|
||||
PropertyAction { target: scanner; property: '_pointsVisible'; value: true}
|
||||
onFinished: found()
|
||||
Item {
|
||||
property alias vo: _vo
|
||||
property alias ic: _ic
|
||||
property alias startTimer: _startTimer
|
||||
|
||||
VideoOutput {
|
||||
id: _vo
|
||||
anchors.fill: parent
|
||||
|
||||
fillMode: VideoOutput.PreserveAspectCrop
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: (parent.height - parent.width) / 2
|
||||
anchors.top: parent.top
|
||||
color: Qt.rgba(0,0,0,0.5)
|
||||
}
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: (parent.height - parent.width) / 2
|
||||
anchors.bottom: parent.bottom
|
||||
color: Qt.rgba(0,0,0,0.5)
|
||||
}
|
||||
InfoTextArea {
|
||||
visible: scanner.hint
|
||||
background.opacity: 0.5
|
||||
iconStyle: InfoTextArea.IconStyle.None
|
||||
anchors {
|
||||
top: parent.top
|
||||
topMargin: constants.paddingXLarge
|
||||
left: parent.left
|
||||
leftMargin: constants.paddingXXLarge
|
||||
right: parent.right
|
||||
rightMargin: constants.paddingXXLarge
|
||||
}
|
||||
text: scanner.hint
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
startTimer.start()
|
||||
}
|
||||
}
|
||||
|
||||
ImageCapture {
|
||||
id: _ic
|
||||
|
||||
}
|
||||
|
||||
MediaDevices {
|
||||
id: mediaDevices
|
||||
}
|
||||
|
||||
Camera {
|
||||
id: camera
|
||||
cameraDevice: mediaDevices.defaultVideoInput
|
||||
active: scanner.active
|
||||
focusMode: Camera.FocusModeAutoNear
|
||||
customFocusPoint: Qt.point(0.5, 0.5)
|
||||
|
||||
onErrorOccurred: {
|
||||
console.log('camera error: ' + errorString)
|
||||
}
|
||||
}
|
||||
|
||||
CaptureSession {
|
||||
videoOutput: _vo
|
||||
imageCapture: _ic
|
||||
camera: camera
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: _startTimer
|
||||
interval: 500
|
||||
repeat: false
|
||||
onTriggered: scanner.active = true
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
@@ -98,95 +151,16 @@ Item {
|
||||
Connections {
|
||||
target: qr
|
||||
function onDataChanged() {
|
||||
console.log(qr.data)
|
||||
console.log('QR DATA: ' + qr.data)
|
||||
scanner.active = false
|
||||
scanner.scanData = qr.data
|
||||
still.source = scanner.url
|
||||
|
||||
var sx = still.width/still.sourceSize.width
|
||||
var sy = still.height/still.sourceSize.height
|
||||
r.createObject(scanner, {cx: qr.points[0].x * sx, cy: qr.points[0].y * sy, color: 'yellow'})
|
||||
r.createObject(scanner, {cx: qr.points[1].x * sx, cy: qr.points[1].y * sy, color: 'yellow'})
|
||||
r.createObject(scanner, {cx: qr.points[2].x * sx, cy: qr.points[2].y * sy, color: 'yellow'})
|
||||
r.createObject(scanner, {cx: qr.points[3].x * sx, cy: qr.points[3].y * sy, color: 'yellow'})
|
||||
|
||||
foundAnimation.start()
|
||||
}
|
||||
}
|
||||
|
||||
MediaDevices {
|
||||
id: mediaDevices
|
||||
}
|
||||
|
||||
CaptureSession {
|
||||
videoOutput: VideoOutput
|
||||
|
||||
camera: Camera {
|
||||
id: camera
|
||||
// deviceId: QtMultimedia.defaultCamera.deviceId
|
||||
cameraDevice: mediaDevices.defaultVideoInput
|
||||
// TODO QT6
|
||||
// viewfinder.resolution: "640x480"
|
||||
|
||||
// focus {
|
||||
// focusMode: Camera.FocusContinuous
|
||||
// focusPointMode: Camera.FocusPointCustom
|
||||
// customFocusPoint: Qt.point(0.5, 0.5)
|
||||
// }
|
||||
|
||||
function dumpstats() {
|
||||
console.log(camera.viewfinder.resolution)
|
||||
console.log(camera.viewfinder.minimumFrameRate)
|
||||
console.log(camera.viewfinder.maximumFrameRate)
|
||||
var resolutions = camera.supportedViewfinderResolutions()
|
||||
resolutions.forEach(function(item, i) {
|
||||
console.log('' + item.width + 'x' + item.height)
|
||||
})
|
||||
// TODO
|
||||
// pick a suitable resolution from the available resolutions
|
||||
// problem: some cameras have no supportedViewfinderResolutions
|
||||
// but still error out when an invalid resolution is set.
|
||||
// 640x480 seems to be universally available, but this needs to
|
||||
// be checked across a range of phone models.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: scanTimer
|
||||
interval: 200
|
||||
repeat: true
|
||||
running: scanner.active
|
||||
onTriggered: {
|
||||
if (qr.busy)
|
||||
return
|
||||
vo.grabToImage(function(result) {
|
||||
if (result.image !== undefined) {
|
||||
scanner.url = result.url
|
||||
qr.scanImage(result.image)
|
||||
} else {
|
||||
console.log('image grab returned null')
|
||||
}
|
||||
})
|
||||
scanner.found()
|
||||
}
|
||||
}
|
||||
|
||||
QRParser {
|
||||
id: qr
|
||||
videoSink: loader.item.vo.videoSink
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
console.log('enumerating cameras')
|
||||
QtMultimedia.availableCameras.forEach(function(item) {
|
||||
console.log('cam found, id=' + item.deviceId + ' name=' + item.displayName)
|
||||
console.log('pos=' + item.position + ' orientation=' + item.orientation)
|
||||
if (QtMultimedia.defaultCamera.deviceId == item.deviceId) {
|
||||
vo.orientation = item.orientation
|
||||
}
|
||||
|
||||
camera.dumpstats()
|
||||
})
|
||||
|
||||
active = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -478,6 +478,7 @@ ApplicationWindow
|
||||
if (activeDialogs.length > 0) {
|
||||
var activeDialog = activeDialogs[activeDialogs.length - 1]
|
||||
if (activeDialog.allowClose) {
|
||||
console.log('main: dialog.doClose')
|
||||
activeDialog.doClose()
|
||||
} else {
|
||||
console.log('dialog disallowed close')
|
||||
|
||||
@@ -9,7 +9,7 @@ from typing import TYPE_CHECKING, Set
|
||||
|
||||
from PyQt6.QtCore import (pyqtSlot, pyqtSignal, pyqtProperty, QObject,
|
||||
qInstallMessageHandler, QTimer, QSortFilterProxyModel)
|
||||
from PyQt6.QtGui import QGuiApplication, QFontDatabase
|
||||
from PyQt6.QtGui import QGuiApplication, QFontDatabase, QScreen
|
||||
from PyQt6.QtQml import qmlRegisterType, qmlRegisterUncreatableType, QQmlApplicationEngine
|
||||
|
||||
from electrum import version, constants
|
||||
@@ -369,7 +369,7 @@ class ElectrumQmlApplication(QGuiApplication):
|
||||
qmlRegisterType(QETxCanceller, 'org.electrum', 1, 0, 'TxCanceller')
|
||||
qmlRegisterType(QEBip39RecoveryListModel, 'org.electrum', 1, 0, 'Bip39RecoveryListModel')
|
||||
|
||||
# TODO QT6
|
||||
# TODO QT6: these were declared as uncreatable, but that doesn't seem to work for pyqt6
|
||||
# qmlRegisterUncreatableType(QEAmount, 'org.electrum', 1, 0, 'Amount', 'Amount can only be used as property')
|
||||
# qmlRegisterUncreatableType(QENewWalletWizard, 'org.electrum', 1, 0, 'QNewWalletWizard', 'QNewWalletWizard can only be used as property')
|
||||
# qmlRegisterUncreatableType(QEServerConnectWizard, 'org.electrum', 1, 0, 'QServerConnectWizard', 'QServerConnectWizard can only be used as property')
|
||||
@@ -380,9 +380,10 @@ class ElectrumQmlApplication(QGuiApplication):
|
||||
|
||||
screensize = self.primaryScreen().size()
|
||||
|
||||
self.qr_ip = QEQRImageProvider((7/8)*min(screensize.width(), screensize.height()))
|
||||
qr_size = min(screensize.width(), screensize.height()) * 7/8
|
||||
self.qr_ip = QEQRImageProvider(qr_size)
|
||||
self.engine.addImageProvider('qrgen', self.qr_ip)
|
||||
self.qr_ip_h = QEQRImageProviderHelper((7/8)*min(screensize.width(), screensize.height()))
|
||||
self.qr_ip_h = QEQRImageProviderHelper(qr_size)
|
||||
|
||||
# add a monospace font as we can't rely on device having one
|
||||
self.fixedFont = 'PT Mono'
|
||||
|
||||
@@ -5,11 +5,12 @@ from qrcode.exceptions import DataOverflowError
|
||||
import math
|
||||
import urllib
|
||||
|
||||
from PIL import Image, ImageQt
|
||||
from PIL import ImageQt
|
||||
|
||||
from PyQt6.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject, QRect, QPoint
|
||||
from PyQt6.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject, QRect
|
||||
from PyQt6.QtGui import QImage, QColor
|
||||
from PyQt6.QtQuick import QQuickImageProvider
|
||||
from PyQt6.QtMultimedia import QVideoSink
|
||||
|
||||
from electrum.logging import get_logger
|
||||
from electrum.qrreader import get_qr_reader
|
||||
@@ -22,103 +23,99 @@ class QEQRParser(QObject):
|
||||
|
||||
busyChanged = pyqtSignal()
|
||||
dataChanged = pyqtSignal()
|
||||
imageChanged = pyqtSignal()
|
||||
sizeChanged = pyqtSignal()
|
||||
videoSinkChanged = pyqtSignal()
|
||||
|
||||
def __init__(self, text=None, parent=None):
|
||||
super().__init__(parent)
|
||||
|
||||
self._busy = False
|
||||
self._image = None
|
||||
self._data = None
|
||||
self._video_sink = None
|
||||
|
||||
self._text = text
|
||||
self.qrreader = get_qr_reader()
|
||||
if not self.qrreader:
|
||||
raise Exception(_("The platform QR detection library is not available."))
|
||||
|
||||
@pyqtSlot('QImage')
|
||||
def scanImage(self, image=None):
|
||||
if self._busy:
|
||||
self._logger.warning("Already processing an image. Check 'busy' property before calling scanImage")
|
||||
return
|
||||
@pyqtProperty(QVideoSink, notify=videoSinkChanged)
|
||||
def videoSink(self):
|
||||
return self._video_sink
|
||||
|
||||
if image is None:
|
||||
self._logger.warning("No image to decode")
|
||||
@videoSink.setter
|
||||
def videoSink(self, sink: QVideoSink):
|
||||
if self._video_sink != sink:
|
||||
self._video_sink = sink
|
||||
self._video_sink.videoFrameChanged.connect(self.onVideoFrame)
|
||||
|
||||
def onVideoFrame(self, videoframe):
|
||||
if self._busy or self._data:
|
||||
return
|
||||
|
||||
self._busy = True
|
||||
self.busyChanged.emit()
|
||||
|
||||
# self.logImageStats(image)
|
||||
self._parseQR(image)
|
||||
if not videoframe.isValid():
|
||||
self._logger.debug('invalid frame')
|
||||
return
|
||||
|
||||
def logImageStats(self, image):
|
||||
self._logger.info(f'width: {image.width()} height: {image.height()} depth: {image.depth()} format: {image.format()}')
|
||||
async def co_parse_qr(frame):
|
||||
image = frame.toImage()
|
||||
self._parseQR(image)
|
||||
|
||||
def _parseQR(self, image):
|
||||
self.w = image.width()
|
||||
self.h = image.height()
|
||||
img_crop_rect = self._get_crop(image, 360)
|
||||
asyncio.run_coroutine_threadsafe(co_parse_qr(videoframe), get_asyncio_loop())
|
||||
|
||||
def _parseQR(self, image: QImage):
|
||||
self._size = min(image.width(), image.height())
|
||||
self.sizeChanged.emit()
|
||||
img_crop_rect = self._get_crop(image, self._size)
|
||||
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)
|
||||
# Convert to Y800 / GREY FourCC (single 8-bit channel)
|
||||
frame_y800 = frame_cropped.convertToFormat(QImage.Format.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.sizeInBytes(),
|
||||
frame_y800.bytesPerLine(),
|
||||
frame_y800.width(),
|
||||
frame_y800.height(),
|
||||
self.frame_id
|
||||
)
|
||||
|
||||
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()
|
||||
|
||||
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())
|
||||
self._busy = False
|
||||
self.busyChanged.emit()
|
||||
|
||||
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)
|
||||
"""Returns a QRect that is scan_size x scan_size in the middle of the resolution"""
|
||||
scan_pos_x = (image.width() - scan_size) // 2
|
||||
scan_pos_y = (image.height() - scan_size) // 2
|
||||
return QRect(scan_pos_x, 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(int, notify=sizeChanged)
|
||||
def size(self):
|
||||
return self._size
|
||||
|
||||
@pyqtProperty(str, notify=dataChanged)
|
||||
def data(self):
|
||||
if not self._data:
|
||||
return ''
|
||||
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
|
||||
@pyqtSlot()
|
||||
def reset(self):
|
||||
self._data = None
|
||||
self.dataChanged.emit()
|
||||
|
||||
|
||||
class QEQRImageProvider(QQuickImageProvider):
|
||||
@@ -161,7 +158,7 @@ class QEQRImageProvider(QQuickImageProvider):
|
||||
# fake it
|
||||
modules = 17 + qr.border * 2
|
||||
box_size = math.floor(pixelsize/modules)
|
||||
self.qimg = QImage(box_size * modules, box_size * modules, QImage.Format_RGB32)
|
||||
self.qimg = QImage(box_size * modules, box_size * modules, QImage.Format.Format_RGB32)
|
||||
self.qimg.fill(QColor('gray'))
|
||||
return self.qimg, self.qimg.size()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user