Improved I/O Test screen

This commit is contained in:
kdmukai
2022-04-03 16:52:30 -05:00
parent 16a01ea4bd
commit 520cb61369
6 changed files with 156 additions and 69 deletions
+1 -1
View File
@@ -2,7 +2,7 @@ embit==0.4.10
numpy==1.21.1
picamera==1.13
Pillow==8.2.0
-e git+https://github.com/kdmukai/pyzbar.git@c3c237821c6a20b17953efe59b90df0b514a1c03#egg=pyzbar
-e git+https://github.com/seedsigner/pyzbar.git@c3c237821c6a20b17953efe59b90df0b514a1c03#egg=pyzbar
qrcode==7.3.1
RPi.GPIO==0.7.0
six==1.16.0
+2 -1
View File
@@ -257,7 +257,7 @@ class TextArea(BaseComponent):
screen_x: int = 0
screen_y: int = 0
min_text_x: int = None
background_color: str = "black"
background_color: str = GUIConstants.BACKGROUND_COLOR
font_name: str = GUIConstants.BODY_FONT_NAME
font_size: int = GUIConstants.BODY_FONT_SIZE
font_color: str = GUIConstants.BODY_FONT_COLOR
@@ -1168,6 +1168,7 @@ class IconButton(Button):
icon_size: int = GUIConstants.ICON_INLINE_FONT_SIZE
text: str = None
is_icon_inline: bool = False
is_text_centered: bool = True
+2 -2
View File
@@ -11,8 +11,8 @@ class Renderer(ConfigurableSingleton):
buttons = None
canvas_width = 0
canvas_height = 0
canvas: Image = None
draw: ImageDraw = None
canvas: Image.Image = None
draw: ImageDraw.ImageDraw = None
disp = None
lock = Lock()
+37 -31
View File
@@ -21,7 +21,8 @@ class ScanScreen(BaseScreen):
instructions_text: str = "Scan a QR code"
resolution: Tuple[int,int] = (480, 480)
framerate: int = 12
auto_deactivate_buttons: bool = False # Used by the I/O test screen
render_rect: Tuple[int,int,int,int] = None
def __post_init__(self):
from seedsigner.hardware.camera import Camera
@@ -36,25 +37,35 @@ class ScanScreen(BaseScreen):
decoder=self.decoder,
renderer=self.renderer,
instructions_text=self.instructions_text,
components=self.components,
auto_deactivate_buttons=self.auto_deactivate_buttons,
render_rect=self.render_rect,
))
class LivePreviewThread(BaseThread):
def __init__(self, camera: Camera, decoder: DecodeQR, renderer: renderer, instructions_text: str, components: List[BaseComponent], auto_deactivate_buttons: bool):
def __init__(self, camera: Camera, decoder: DecodeQR, renderer: renderer.Renderer, instructions_text: str, render_rect: Tuple[int,int,int,int]):
self.camera = camera
self.decoder = decoder
self.renderer = renderer
self.instructions_text = instructions_text
self.components = components
self.auto_deactivate_buttons = auto_deactivate_buttons
if render_rect:
self.render_rect = render_rect
else:
self.render_rect = (0, 0, self.renderer.canvas_width, self.renderer.canvas_height)
self.render_width = self.render_rect[2] - self.render_rect[0]
self.render_height = self.render_rect[3] - self.render_rect[1]
print(f"render_width: {self.render_width}")
print(f"render_height: {self.render_height}")
super().__init__()
def run(self):
from timeit import default_timer as timer
instructions_font = Fonts.get_font(GUIConstants.BODY_FONT_NAME, GUIConstants.BUTTON_FONT_SIZE)
while self.keep_running:
start = timer()
frame = self.camera.read_video_stream(as_image=True)
if frame is not None:
scan_text = self.instructions_text
@@ -62,40 +73,35 @@ class ScanScreen(BaseScreen):
scan_text = str(self.decoder.get_percent_complete()) + "% Complete"
with self.renderer.lock:
if frame.width > self.renderer.canvas_width or frame.height > self.renderer.canvas_height:
if frame.width > self.render_width or frame.height > self.render_height:
frame = frame.resize(
(self.renderer.canvas_width, self.renderer.canvas_height)
(self.render_width, self.render_height)
)
self.renderer.canvas.paste(
frame,
(0, 0)
(self.render_rect[0], self.render_rect[1])
)
self.renderer.draw.text(
xy=(
int(self.renderer.canvas_width/2),
self.renderer.canvas_height - GUIConstants.EDGE_PADDING
),
text=scan_text,
fill=GUIConstants.BODY_FONT_COLOR,
font=instructions_font,
stroke_width=4,
stroke_fill=GUIConstants.BACKGROUND_COLOR,
anchor="ms"
)
# Need to re-render all onscreen UI components since we just overwrote
# the screen.
for component in self.components:
component.render()
if self.auto_deactivate_buttons and type(component) in [Button, IconButton]:
if component.is_selected:
# deactivate for the next round
component.is_selected = False
if scan_text:
self.renderer.draw.text(
xy=(
int(self.renderer.canvas_width/2),
self.renderer.canvas_height - GUIConstants.EDGE_PADDING
),
text=scan_text,
fill=GUIConstants.BODY_FONT_COLOR,
font=instructions_font,
stroke_width=4,
stroke_fill=GUIConstants.BACKGROUND_COLOR,
anchor="ms"
)
self.renderer.show_image()
time.sleep(0.1) # turn this up or down to tune performance while decoding psbt
end = timer()
# print(f"{1.0/(end - start)} fps") # Time in seconds, e.g. 5.38091952400282
time.sleep(0.05) # turn this up or down to tune performance while decoding psbt
if self.camera._video_stream is None:
break
+113 -28
View File
@@ -1,12 +1,14 @@
import time
from dataclasses import dataclass
from PIL.ImageOps import autocontrast
from typing import List
from seedsigner.gui.components import Button, CheckboxButton, CheckedSelectionButton, FontAwesomeIconConstants, GUIConstants, IconButton, TextArea
from seedsigner.gui.components import Button, CheckboxButton, CheckedSelectionButton, FontAwesomeIconConstants, Fonts, GUIConstants, Icon, IconButton, IconTextLine, TextArea
from seedsigner.gui.screens.scan_screens import ScanScreen
from seedsigner.gui.screens.screen import BaseTopNavScreen, ButtonListScreen
from seedsigner.gui.screens.screen import BaseScreen, BaseTopNavScreen, ButtonListScreen
from seedsigner.hardware.buttons import HardwareButtonsConstants
from seedsigner.hardware.camera import Camera
from seedsigner.models.settings import SettingsConstants
@@ -48,14 +50,18 @@ class SettingsEntryUpdateSelectionScreen(ButtonListScreen):
@dataclass
class IOTestScreen(ScanScreen):
class IOTestScreen(BaseTopNavScreen):
def __post_init__(self):
self.title = "I/O Test"
self.show_back_button = False
self.resolution = (96, 96)
self.framerate = 10
self.instructions_text = None
super().__post_init__()
# D-pad pictogram
input_button_width = GUIConstants.BUTTON_HEIGHT
input_button_height = input_button_width
input_button_width = GUIConstants.BUTTON_HEIGHT + 2
input_button_height = input_button_width + 2
dpad_center_x = GUIConstants.EDGE_PADDING + input_button_width + GUIConstants.COMPONENT_PADDING
dpad_center_y = int((self.canvas_height - input_button_height)/2)
@@ -73,7 +79,6 @@ class IOTestScreen(ScanScreen):
self.joystick_up_button = IconButton(
icon_name=FontAwesomeIconConstants.ANGLE_UP,
icon_size=GUIConstants.ICON_INLINE_FONT_SIZE,
is_text_centered=True,
width=input_button_width,
height=input_button_height,
screen_x=dpad_center_x,
@@ -116,22 +121,30 @@ class IOTestScreen(ScanScreen):
)
self.components.append(self.joystick_right_button)
key_button_width = int(1.75*GUIConstants.BUTTON_HEIGHT)
key_button_height = int(0.85*GUIConstants.BUTTON_HEIGHT)
# Hardware keys UI
font = Fonts.get_font(GUIConstants.BUTTON_FONT_NAME, GUIConstants.BUTTON_FONT_SIZE)
(left, top, text_width, bottom) = font.getbbox(text="Clear", anchor="ls")
icon = Icon(
icon_name=FontAwesomeIconConstants.CAMERA,
icon_size=GUIConstants.ICON_INLINE_FONT_SIZE,
)
key_button_width = text_width + 2*GUIConstants.COMPONENT_PADDING + GUIConstants.EDGE_PADDING
key_button_height = icon.height + int(1.5*GUIConstants.COMPONENT_PADDING)
key2_y = int(self.canvas_height/2) - int(key_button_height/2)
self.key2_button = Button(
text=" ",
text="Clear", # Initialize with text to set vertical centering
width=key_button_width,
height=key_button_height,
screen_x=self.canvas_width - key_button_width + GUIConstants.EDGE_PADDING,
screen_y=key2_y,
outline_color=GUIConstants.ACCENT_COLOR,
)
self.key2_button.text = " " # but default state is empty
self.components.append(self.key2_button)
self.key1_button = Button(
text=" ",
self.key1_button = IconButton(
icon_name=FontAwesomeIconConstants.CAMERA,
width=key_button_width,
height=key_button_height,
screen_x=self.canvas_width - key_button_width + GUIConstants.EDGE_PADDING,
@@ -153,10 +166,90 @@ class IOTestScreen(ScanScreen):
def _run(self):
cur_selected_button = self.key1_button
msg_height = GUIConstants.ICON_LARGE_BUTTON_SIZE + 2*GUIConstants.COMPONENT_PADDING
camera_message = TextArea(
text="Capturing image...",
font_size=GUIConstants.TOP_NAV_TITLE_FONT_SIZE,
is_text_centered=True,
height=msg_height,
screen_y=int((self.canvas_height - msg_height)/ 2),
)
while True:
input = self.hw_inputs.wait_for(keys=HardwareButtonsConstants.ALL_KEYS, check_release=False)
if input == HardwareButtonsConstants.KEY_PRESS:
if input == HardwareButtonsConstants.KEY1:
cur_selected_button = self.key1_button
with self.renderer.lock:
cur_selected_button.is_selected = True
cur_selected_button.render()
camera_message.render()
# Render edges around message box
self.image_draw.rectangle(
(
-1, int((self.canvas_height - msg_height)/ 2) - 1,
self.canvas_width + 1, int((self.canvas_height + msg_height)/ 2) + 1
),
outline=GUIConstants.ACCENT_COLOR,
width=1,
)
self.renderer.show_image()
# Snap a pic, render it as the background, re-render all onscreen elements
camera = Camera.get_instance()
try:
camera.start_single_frame_mode(resolution=(self.canvas_width, self.canvas_height))
# Reset the button state
with self.renderer.lock:
cur_selected_button.is_selected = False
cur_selected_button.render()
self.renderer.show_image()
time.sleep(0.25)
background_frame = camera.capture_frame()
display_version = autocontrast(
background_frame,
cutoff=2
)
with self.renderer.lock:
self.canvas.paste(display_version, (0, self.top_nav.height))
self.key2_button.text = "Clear"
for component in self.components:
component.render()
self.renderer.show_image()
finally:
camera.stop_single_frame_mode()
continue
elif input == HardwareButtonsConstants.KEY2:
cur_selected_button = self.key2_button
# Clear the background
with self.renderer.lock:
cur_selected_button.is_selected = True
self._render()
self.renderer.show_image()
# And then re-render Key2 in its initial state
self.key2_button.text = " "
cur_selected_button.is_selected = False
cur_selected_button.render()
self.renderer.show_image()
continue
elif input == HardwareButtonsConstants.KEY3:
# Exit
cur_selected_button = self.key3_button
cur_selected_button.is_selected = True
with self.renderer.lock:
cur_selected_button.render()
self.renderer.show_image()
return
elif input == HardwareButtonsConstants.KEY_PRESS:
cur_selected_button = self.joystick_click_button
elif input == HardwareButtonsConstants.KEY_UP:
@@ -171,23 +264,15 @@ class IOTestScreen(ScanScreen):
elif input == HardwareButtonsConstants.KEY_RIGHT:
cur_selected_button = self.joystick_right_button
elif input == HardwareButtonsConstants.KEY1:
cur_selected_button = self.key1_button
elif input == HardwareButtonsConstants.KEY2:
cur_selected_button = self.key2_button
elif input == HardwareButtonsConstants.KEY3:
# Exit
self.camera.stop_video_stream_mode()
cur_selected_button = self.key3_button
with self.renderer.lock:
cur_selected_button.is_selected = True
with self.renderer.lock:
cur_selected_button.render()
self.renderer.show_image()
return
cur_selected_button.render()
self.renderer.show_image()
cur_selected_button.is_selected = True
with self.renderer.lock:
cur_selected_button.is_selected = False
cur_selected_button.render()
self.renderer.show_image()
time.sleep(0.1)
+1 -6
View File
@@ -178,12 +178,7 @@ class SettingsEntryUpdateSelectionView(View):
****************************************************************************"""
class IOTestView(View):
def run(self):
settings_screens.IOTestScreen(
instructions_text="Live camera feed",
resolution=(240,240),
framerate=24,
auto_deactivate_buttons=True,
).display()
settings_screens.IOTestScreen().display()
return Destination(SettingsMenuView)