Skip to content

Commit

Permalink
feat: render relevant parts of the screen only
Browse files Browse the repository at this point in the history
feat: keep a copy of the raw data transferred to the display in `config._display.raw_data`
  • Loading branch information
sassanh committed Mar 14, 2024
1 parent 411dfe9 commit c634975
Show file tree
Hide file tree
Showing 6 changed files with 36 additions and 23 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Changelog

## Version 0.7.0

- feat: render relevant parts of the screen only
- feat: keep a copy of the raw data transferred to the display in `config._display.raw_data`

## Version 0.6.1

- chore: GitHub workflow to publish pushes on `main` branch to PyPI
Expand Down
4 changes: 2 additions & 2 deletions headless_kivy_pi/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@
from kivy.uix.widget import Widget

from headless_kivy_pi import config
from headless_kivy_pi.constants import IS_TEST_ENVIRONMENT
from headless_kivy_pi.display import transfer_to_display
from headless_kivy_pi.logger import logger

Expand Down Expand Up @@ -61,7 +60,7 @@ class HeadlessWidget(Widget):

def __init__(self: HeadlessWidget, **kwargs: dict[str, object]) -> None:
"""Initialize a `HeadlessWidget`."""
if not IS_TEST_ENVIRONMENT:
if not config.is_test_environment():
config.check_initialized()

self.should_ignore_hash = False
Expand Down Expand Up @@ -260,6 +259,7 @@ def render_on_display(self: HeadlessWidget, *_: object) -> None:
thread = Thread(
target=transfer_to_display,
args=(
(self.x, self.y, self.width, self.height),
data,
data_hash,
last_thread,
Expand Down
27 changes: 22 additions & 5 deletions headless_kivy_pi/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from typing import TYPE_CHECKING, NoReturn, NotRequired, TypedDict

import kivy
import numpy as np
from kivy.config import Config

from headless_kivy_pi.constants import (
Expand All @@ -20,7 +21,6 @@
HEIGHT,
IS_DEBUG_MODE,
IS_RPI,
IS_TEST_ENVIRONMENT,
MAX_FPS,
MIN_FPS,
SYNCHRONOUS_CLOCK,
Expand Down Expand Up @@ -80,7 +80,7 @@ class SetupHeadlessConfig(TypedDict):


_config: SetupHeadlessConfig | None = None
_display: DisplaySPI | None = None
_display: DisplaySPI = None


def report_uninitialized() -> NoReturn:
Expand Down Expand Up @@ -134,16 +134,19 @@ def setup_headless_kivy(config: SetupHeadlessConfig) -> None:
{BITS_PER_BYTE}))"""
raise ValueError(msg)

if IS_TEST_ENVIRONMENT:
if is_test_environment():
Config.set('graphics', 'window_state', 'hidden')
from kivy.core.window import Window
elif IS_RPI:

from kivy.metrics import dp

if IS_RPI:
Config.set('graphics', 'window_state', 'hidden')
spi = board.SPI()
# Configuration for CS and DC pins (these are PiTFT defaults):
cs_pin = digitalio.DigitalInOut(board.CE0)
dc_pin = digitalio.DigitalInOut(board.D25)
reset_pin = digitalio.DigitalInOut(board.D24)
spi = board.SPI()
_display = display_class(
spi,
height=height(),
Expand Down Expand Up @@ -171,11 +174,17 @@ def setup_headless_kivy(config: SetupHeadlessConfig) -> None:
from screeninfo import get_monitors

monitor = get_monitors()[0]
_display = Fake()

Window._win.set_always_on_top(True) # noqa: SLF001
Window._set_top(200) # noqa: SLF001
Window._set_left(monitor.width - Window._size[0]) # noqa: SLF001

_display.raw_data = np.zeros(
(int(dp(width())), int(dp(height())), 3),
dtype=np.uint8,
)


def check_initialized() -> None:
"""Check if the module has been initialized."""
Expand Down Expand Up @@ -223,6 +232,14 @@ def is_debug_mode() -> bool:
report_uninitialized()


@cache
def is_test_environment() -> bool:
"""Return `True` if the application is running in test environment."""
import os

return 'PYTEST_CURRENT_TEST' in os.environ


@cache
def double_buffering() -> bool:
"""Generate the next frame while sending the last frame to the display."""
Expand Down
3 changes: 0 additions & 3 deletions headless_kivy_pi/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,6 @@

# Configure the headless mode for the Kivy application and initialize the display

IS_TEST_ENVIRONMENT = (
strtobool(os.environ.get('HEADLESS_KIVY_PI_TEST_ENVIRONMENT', 'False')) == 1
)
MIN_FPS = int(os.environ.get('HEADLESS_KIVY_PI_MIN_FPS', '1'))
MAX_FPS = int(os.environ.get('HEADLESS_KIVY_PI_MAX_FPS', '32'))
WIDTH = int(os.environ.get('HEADLESS_KIVY_PI_WIDTH', '240'))
Expand Down
18 changes: 6 additions & 12 deletions headless_kivy_pi/display.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@


def transfer_to_display(
rectangle: tuple[int, int, int, int],
data: NDArray[np.uint16],
data_hash: int,
last_render_thread: Thread,
Expand All @@ -23,11 +24,7 @@ def transfer_to_display(
logger.debug(f'Rendering frame with hash "{data_hash}"')

# Flip the image vertically
data = data.reshape(
config.width(),
config.height(),
-1,
)[::-1, :, :3].astype(np.uint16)
data = data.reshape(rectangle[2], rectangle[3], -1)[::-1, :, :3].astype(np.uint8)

color = (
((data[:, :, 0] & 0xF8) << 8)
Expand All @@ -45,10 +42,7 @@ def transfer_to_display(
# Only render when running on a Raspberry Pi
display = config._display # noqa: SLF001
if display:
display._block( # noqa: SLF001
0,
0,
config.width() - 1,
config.height() - 1,
data_bytes,
)
display.raw_data[rectangle[0] : rectangle[0] + rectangle[2]][
rectangle[1] : rectangle[1] + rectangle[3]
] = data
display._block(*rectangle, data_bytes) # noqa: SLF001
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "headless-kivy-pi"
version = "0.6.1"
version = "0.7.0"
description = "Headless renderer for Kivy framework on Raspberry Pi"
authors = ["Sassan Haradji <sassanh@gmail.com>"]
license = "Apache-2.0"
Expand Down

0 comments on commit c634975

Please sign in to comment.