-
Notifications
You must be signed in to change notification settings - Fork 0
/
qr.py
137 lines (107 loc) · 3.68 KB
/
qr.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
import io
import math
import sys
from typing import Optional
import cv2
import pygame
import qrcode
class QRDisplay:
"""
A window that shows QR codes.
"""
def __init__(self, title: str = "IP over QR") -> None:
pygame.init()
monitor_height = pygame.display.get_desktop_sizes()[0][1]
surface_height = math.floor(monitor_height * 3/5)
self.surface = pygame.display.set_mode((surface_height, surface_height))
self.title = title
self.surface.fill(self.background_colour)
pygame.event.set_allowed(None)
pygame.event.set_allowed(pygame.QUIT)
self.qr_code = QRDisplay.make_qrcode(b"")
@property
def background_colour(self) -> (int, int, int):
return 255, 255, 255
@property
def title(self) -> str:
return pygame.display.get_caption()[0]
@title.setter
def title(self, title: str) -> None:
pygame.display.set_caption(title, title)
def set_data(self, data: bytes) -> None:
"""
Convert the specified data to a QR code and set it to be the next one displayed on the screen.
:param data: the data to show
"""
self.qr_code = QRDisplay.make_qrcode(data)
def show_image(self, image_surface: pygame.Surface) -> None:
"""
Display an image in the centre of the window.
:param image_surface: the image to display
"""
screen_width, screen_height = self.surface.get_size()
image_rect = image_surface.get_rect()
image_rect.update(
screen_width / 2 - image_rect.width / 2,
screen_height / 2 - image_rect.height / 2,
image_rect.width,
image_rect.height,
)
self.surface.blit(image_surface, image_rect)
def update_window(self) -> None:
"""
Update the window where the QR code is displayed.
This should be run at least as often as the monitor's refresh rate.
"""
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
self.surface.fill(self.background_colour)
self.show_image(self.qr_code)
pygame.display.flip()
@staticmethod
def make_qrcode(data: bytes) -> pygame.Surface:
"""
Make a QR code that can be displayed by pygame.
:param data: the data to put in the QR code
:return: a file-like BytesIO object containing a BMP of the QR code
"""
qr_image = qrcode.make(data, box_size=5, border=2)
qr_bytes = io.BytesIO()
qr_image.save(qr_bytes, "PNG")
qr_bytes.seek(0)
return pygame.image.load(qr_bytes)
class QRReader:
"""
A camera input that reads QR codes.
Uses the first camera it finds.
"""
def __init__(self) -> None:
self.capture_device = cv2.VideoCapture(0)
self.detector = cv2.QRCodeDetector()
def read(self) -> Optional[str]:
"""
Try to read a QR code from the camera.
"""
success, frame = self.capture_device.read()
if not success:
raise OSError("Couldn't read from camera")
try:
data, bounding_box, _ = self.detector.detectAndDecode(frame)
if bounding_box is None or len(data) == 0:
return None
return data
except cv2.error:
return None
def finish(self) -> None:
self.capture_device.release()
if __name__ == "__main__":
screen = QRDisplay()
reader = QRReader()
old_qr = None
while True:
qr = reader.read()
if qr != old_qr and qr is not None and qr != b"":
print(qr)
old_qr = qr
screen.update_window()