Skip to content

feat: add 4-level grayscale display calibration - #35

Open
markomitranic wants to merge 1 commit into
protolux-electronics:mainfrom
markomitranic:marko/display-calibration-upstream
Open

feat: add 4-level grayscale display calibration#35
markomitranic wants to merge 1 commit into
protolux-electronics:mainfrom
markomitranic:marko/display-calibration-upstream

Conversation

@markomitranic

@markomitranic markomitranic commented Jul 15, 2026

Copy link
Copy Markdown

Why

The UC8276 panel can render 4 gray levels, but only the two middle grays are tunable — black and white are fixed. Getting those two mids right is a per-panel, by-eye judgement, and there was no way to do it on the device. This adds a Settings → Calibrate Display screen to dial them in and remember the result.

Depends on protolux-electronics/eink#12. The screen needs both the 4-gray drawing pipeline and the runtime EInk.set_waveform/2 override API, which live in that PR. eink is pinned to its head commit here — this can't merge until #12 merges and a release is cut, after which the dep flips back to the packaged/hex version. Every hardware call is guarded, so it compiles and the simulator runs even before then.

Screenshot 2026-07-15 at 15 32 09 * The test photo is Kodak "kodim17" from the [Kodak Lossless True Color Image Suite](https://r0k.us/graphics/kodak/)

What

A two-step wizard, because two buttons can only tune one gray at a time:

Step 1 — Dark gray    short A/B nudge · hold A = next
Step 2 — Light gray   short A/B nudge · hold A = save+exit · hold B = exit (no save)

Both steps show the same photo dithered to the 4 real levels, so you judge each gray against a real image rather than a swatch. Values persist to /data/display_calibration.json and re-apply at boot (EInk forgets waveform overrides on restart).

The screen leans on a grayscale rendering pipeline the badge didn't have yet, so this PR brings it along:

  • Display switches from the old EInk.new/… handle + manual 1-bit bit-packing to the eink singleton draw API (EInk.draw(dither, opts)), which does its own dithering and understands mode: :grayscale.
  • Screen now forwards a :render_opts assign into the draw call, so any screen can opt into mode: :grayscale the same way it sets :button_hints.
  • DisplayMock gained a quantize4 path so the host simulator shows the 4 real levels instead of hard black/white.
  • The EInk singleton is supervised (before Display) and configured via config :eink in config/target.exs.
  • New: NameBadge.Calibration (the persistence + LUT logic) and NameBadge.Screen.Settings.Calibrate (the wizard UI), plus a settings-menu entry.

Notes for reviewers

  • dither bumped ~> 0.1.1~> 0.2.4 (override: true) — the new eink pipeline requires it.
  • eink is pinned to a git commit, not a branch or hex version. That's deliberate and temporary (see the blocker above); the comment in mix.exs says what to swap it to.
  • On host, eink isn't compiled (targets: [:trellis]), so EInk xref warnings during a host compile are expected — the simulator uses DisplayMock and never calls into EInk.

Settings → Calibrate Display: a two-step wizard to tune the two middle
grays of the 4-level UC8276 panel (black/white are fixed anchors). Both
steps preview a photo dithered to the 4 real levels so you judge each
gray against a real image; values persist in /data and re-apply at boot.

Brings the grayscale rendering pipeline the screen depends on: Display
switches to the eink singleton draw API (mode: :grayscale, self-dithering),
Screen forwards a :render_opts assign, DisplayMock simulates the 4 levels,
and the EInk singleton is supervised + configured.

Depends on protolux-electronics/eink#12 (EInk.set_waveform/2 + the 4-gray
pipeline); eink is pinned to that PR's head until it merges and releases.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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