Skip to content

feat: add DBS refinement option; prepare images off the event loop - #170

Merged
g4bri3lDev merged 2 commits into
mainfrom
feat/dbs-refinement
Oct 7, 2026
Merged

g4bri3lDev merged 2 commits into
mainfrom
feat/dbs-refinement

Conversation

@g4bri3lDev

Copy link
Copy Markdown
Member

Summary

Adds the Direct Binary Search refinement from epaper-dithering 6.1.0 to the upload path, and moves image preparation off the event loop.

fix: prepare images off the event loop in upload_image

upload_image() called _prepare_image() (rotate → fit → dither → encode → compress) synchronously inside the coroutine, so all of that CPU work ran on the caller's event loop. In Home Assistant, that blocks every integration for the duration of the dither:

  • plain dithering takes ~30 ms;
  • with DBS it would be ~0.3 s on a fast multi-core machine and longer on a Pi.

It now runs via asyncio.to_thread. dither_image already releases the GIL, so the loop stays responsive. A new test asserts _prepare_image never runs on the event-loop thread; it failed before the change.

feat: add DBS refinement option to image preparation and upload

prepare_image() and upload_image() take dbs: DbsParams | bool | None = None:

  • None / False: off (the default, so existing behaviour is unchanged).
  • True: on, with the panel's real pixel density filled in from its physical size via a new DisplayConfig.ppi. It's measured along the diagonal, so it doesn't depend on orientation; e.g. 800×480 px on a 160×96 mm active area gives 127 ppi. Panels without a known size fall back to the library defaults.
  • DbsParams(...): passed through unchanged, e.g. for a custom viewing distance.

The epaper-dithering pin goes from 6.0.0 to 6.1.0; the uv.lock change is only that package.

Tests

  • 14 new tests: off-loop preparation; DisplayConfig.ppi with known and unknown sizes; dbs resolution (on, off, explicit params, missing size); forwarding through prepare_image and upload_image.
  • Two rotation tests replace dither_image with a stub that lists every argument, so dbs was added to that list.
  • 1068 passed; ruff, mypy (strict) and pylint are clean.

Downstream

  • Home Assistant core integration: after a release, bump py-opendisplay and add an opt-in service field that passes dbs=True. Its direct device.upload_image() call also gets the off-loop fix for free.
  • Custom integration: it already calls prepare_image() in an executor for its sleeping-device queue; it only needs to pass dbs= through.

upload_image() called _prepare_image() (rotate, fit, dither, encode,
compress) synchronously inside the coroutine, so the CPU work ran on the
caller's event loop. In Home Assistant that stalls every integration for
the duration of the dither. Run it via asyncio.to_thread instead; the
dither call already releases the GIL, so the loop stays responsive.
prepare_image() and upload_image() accept dbs=None | True | DbsParams,
passed through to epaper-dithering's Direct Binary Search refinement
(opt-in, slower). dbs=True fills in the panel's real pixel density from
its physical size via the new DisplayConfig.ppi (measured along the
diagonal, so it is orientation-independent), so the eye model matches the
hardware; panels without a known size fall back to the library defaults.

Requires epaper-dithering 6.1.0 (pin bumped).
@codecov

codecov Bot commented Oct 7, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@g4bri3lDev
g4bri3lDev merged commit 0579972 into main Oct 7, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant