E-Ink Rendering
Audience: Developer, Deployment Administrator, Support Related: Overview · Remote Pi Client · Menu Rendering · Overview
The supported Waveshare 13.3-inch K path is 960×680 with four grayscale levels. Blackcap prepares panel-ready PNG and, where appropriate, verified gray4 controller-plane transport artifacts for local and remote clients. Local and remote paths share render profiles and validation rules.
E-ink design must account for the physical panel: thin lines should be black rather than light gray, the Let’s Cook progress border is black, the progress fill may be light gray, meal-plan separators are 1 px black, and full refresh remains the established reliable path.
Four-gray rendering and transport
Blackcap uses one Type K rendering contract for attached local e-ink displays and paired remote Raspberry Pi clients:
- PNG, 960 × 680;
- Pillow mode
L; - gray values
0,128,192, and255only; - a paired
blackcap-gray4-planes-v1controller artifact containing the exact 0x24 and 0x26 RAM planes.
The PNG remains the Admin/UI preview. The controller artifact is the hardware input.
Content coverage
The controller artifact and bulk-write path apply to full four-gray updates for:
- Menu;
- Daily, weekly, today-and-tomorrow, and monthly Meal Plan views;
- Recipe display rendering;
- Let’s Cook;
- saved Menu restoration;
- deep-clean frame restoration.
Hosted receivers, kiosks, mock displays, and other non-Type-K profiles keep their existing output behavior and do not create a controller artifact.
Local and remote behavior
Remote Pi client
The server prepares both artifacts. A client-only Pi downloads only the controller artifact. A Pi that also hosts Blackcap downloads the controller artifact for hardware and the PNG for its local Admin UI.
Attached local e-ink
The local Blackcap server prepares the same controller artifact next to current_view.png and passes it to the hardware helper. This avoids reopening the PNG and packing the planes in the helper. If the transport is unavailable, the helper retains a panel-ready PNG fallback.
Atomic publication
The PNG and controller artifact are finalized before displays.last_preview_updated_at is advanced. That database field is now a microsecond UTC generation token and is authoritative for remote clients.
The state endpoint suppresses files whose modification time is newer than the committed generation token. This prevents both existing and newly restarted clients from seeing an intermediate PNG before its matching controller transport is ready.
Pixel mapping
The mapping matches Waveshare's official getbuffer_4Gray() and display_4Gray() implementation:
| PNG gray | Meaning | RAM 0x24 | RAM 0x26 |
|---|---|---|---|
| 255 | White | 0 | 0 |
| 192 | Light gray | 1 | 0 |
| 128 | Dark gray | 0 | 1 |
| 0 | Black | 1 | 1 |
Pixels are packed left-to-right, MSB first. Regression coverage packs and reconstructs an entire 960 × 680 frame, including text, a strikethrough rule, gray blocks, the final row, and byte boundaries, then requires exact pixel equality.
Hardware path
All optimized full four-gray paths use the shared eink_hardware_push_service:
- initialize the Type K four-gray controller;
- bulk-write the 81,600-byte 0x24 plane;
- bulk-write the 81,600-byte 0x26 plane;
- invoke the standard
0x22 / 0xC7 / 0x20four-gray update sequence; - wait for BUSY to clear;
- sleep and release the panel.
This replaces Waveshare's per-byte Python loops while preserving the controller mapping and waveform.
Let’s Cook strikethrough
For Type K output, completed-item strikethrough rules are rendered in solid black instead of medium gray. The line is positioned from the visible glyph bounding box and scaled with the font. This avoids a visually segmented strike where a gray rule crosses black glyphs, while hosted/color output retains its existing muted-gray styling.
Immutable preview generations
Display renderers atomically replace the display-scoped current_view PNG. For a supported Type K target, Blackcap then generates and validates the matching controller transport beside that PNG. Only after both steps finish does it advance the display row's UTC generation token. Pull clients version the artifact from that committed database token, so an in-progress render does not look new until the complete PNG/transport pair is ready.
A failed or concurrent later render can overwrite the working file without affecting the previously committed bundle. If controller-transport creation fails, Blackcap publishes the validated panel-ready PNG as a compatibility fallback so the physical display is not left indefinitely on stale content.
Remote artifact diagnostics
Remote clients validate paired Type K gray4 transport and PNG artifacts whenever both are downloaded. The service log reports transport_png_match, pixel mismatch count, and content hashes. An exact match proves the server preview and controller-plane artifact contain the same four gray states; a remaining visual difference is then in the client driver/LUT/panel path rather than the network artifact.
BLACKCAP_EINK_GRAY4_PUSH_MODE controls only content that is still published as native four-gray output. The default auto mode uses the verified bulk-plane path for structured content. An explicitly selected native_four_gray Menu uses the installed Waveshare PNG path because the one-write-per-plane experiment produced streaking on photographic frames. bulk and official remain diagnostic overrides. One-bit Menu modes do not use this setting.
Type K Menu strategy
Photographic Menu content now defaults to the organization-scoped crisp_bw strategy. The server publishes an exact one-bit PNG, does not publish a gray4 transport for that generation, and the client uses the normal fast black/white panel path. perceptual_four_tone provides a patterned black/white alternative, while native_four_gray remains experimental. See eink-rendering.md. Structured Meal Plan, Recipe, and Let’s Cook gray4 updates retain the fast bulk transport without a mandatory white pre-clear.
Visual style rules
This guide defines the display-language rules Blackcap should use for the Waveshare 13.3-inch Type K four-gray e-ink panel and any future grayscale e-ink targets with similar characteristics.
Core principles
- Thin lines must be black. Light gray does not read reliably when used as a 1px rule or outline.
- Light gray is for mass, not detail. Use it for filled regions, progress fills, and larger highlighted areas.
- Critical status marks should be black. Examples include strikethrough rules, separators, checkbox outlines, progress bar outlines, and table rules.
- Dark gray is optional secondary emphasis. It can work for larger shapes, but should not replace black for small, important lines.
- Prefer panel-native values.
Type K output should use only
0,128,192, and255.
Approved grayscale roles
| Role | Type K value | Usage |
|---|---|---|
| Black | 0 |
Text, thin lines, borders, separators, strikethroughs, icon details |
| Dark gray | 128 |
Secondary fills, subdued labels, larger secondary UI areas |
| Light gray | 192 |
Progress fills, large highlight fills, soft chips/cards where enough area exists |
| White | 255 |
Backgrounds and negative space |
Thin-line rules
These should render in black on e-ink:
- progress bar outlines
- strikethrough lines
- row separators
- divider rules
- checkbox outlines
- table borders
- underlines and other 1px emphasis strokes
Do not use light gray for any of the above.
Let's Cook guidance
- Progress bar outline: black
- Progress bar background: white
- Progress fill: light gray
- Completed-item strikethrough: black
- Primary text: black
- Secondary labels may use dark gray only when they are not thin rules
Meal Plan guidance
- Row separators: 1px black
- Table borders: black for e-ink
- Text: black
- Decorative faint separators used on color displays should promote to black on e-ink when they are thin rules
Implementation notes
- For e-ink-targeted renderers, choose colors based on the display profile before drawing.
- Do not rely on post-processing to rescue poor contrast decisions.
- When adding a new component, ask: is this a thin line or a filled area?
- thin line → black
- filled area → consider light gray or dark gray
Current applications
These rules are currently applied to:
- Let's Cook progress bar outline/fill
- Let's Cook completed-item strikethroughs
- Meal plan row separators
Future e-ink UI updates should follow this guide by default.
Monthly deep clean
inky_deep_clean.py performs the established full-panel ghosting cleanup sequence for an attached e-ink display:
white → black → white
The script then restores the prior image in a separate display session so the panel returns to useful content with a clean, high-contrast baseline. It uses the shared display lock and must not run while another local display operation owns the panel. Client mode and deployment choices may intentionally suppress local deep cleaning when a remote server owns the attached panel.
Pi deployment tooling can install the monthly deep-clean schedule for hosts that physically drive an e-ink panel. See Background Jobs and Schedules.
Hardware baseline
tools/run_waveshare_13in3k_baseline.py is an isolated hardware diagnostic.
It bypasses Blackcap rendering, remote-client downloads, controller-plane
transport, and tone compensation. It uses only the installed Waveshare
epd13in3k driver and mirrors the official demo's four-gray sequence.
Recommended baseline
Stop the remote client first so it cannot replace the test image:
sudo systemctl stop blackcap-client.service
Run the generated official four-gray stage and then Waveshare's official scale bitmap:
cd /home/pi/Blackcap-Pi
sudo /home/pi/inky_env/bin/python3 tools/run_waveshare_13in3k_baseline.py \
--download-official-scale-bmp
The generated stage remains visible for 15 seconds. The official
13in3k_Scale.bmp is then displayed and left on the panel while the controller
is put to sleep.
Restart the Blackcap client after photographing the result:
sudo systemctl start blackcap-client.service
sudo journalctl -u blackcap-client.service -f
Offline use
Download 13in3k_Scale.bmp separately and provide it without allowing the
script to modify or resize it:
sudo /home/pi/inky_env/bin/python3 tools/run_waveshare_13in3k_baseline.py \
--scale-bmp /path/to/13in3k_Scale.bmp
Omit both scale-BMP options to leave the generated official-style demo on the panel as the final image.
Render-only validation
/home/pi/inky_env/bin/python3 tools/run_waveshare_13in3k_baseline.py --render-only
This writes /tmp/waveshare_13in3k_generated_baseline.png without touching the
e-ink hardware.
Type K menu rendering
Blackcap treats photographic Menu captures differently from structured content on the Waveshare 13.3-inch Type K panel.
Organization setting
menu.type_k_render_mode controls Menu rendering only:
crisp_bw— default and recommended. Uses deterministic ordered black/white dithering and the normal fast one-bit panel refresh.perceptual_four_tone— uses four apparent tonal bands built from repeatable black/white halftone patterns. It still uses the fast one-bit panel refresh.native_four_gray— experimental. Uses Waveshare's four physical gray states, the official Menu driver path in clientautomode, and the full-white baseline sequence.
The setting is organization-scoped and is available on Menu Mode Settings. It does not affect Meal Plan, Recipe, or Let's Cook.
Remote client transport
For crisp_bw and perceptual_four_tone, the state API advertises:
pixel_format: bw1gray_levels: [0, 255]- no gray4 controller-plane transport
The client downloads the exact panel-ready PNG and invokes the normal one-bit Waveshare display path. BLACKCAP_EINK_GRAY4_PUSH_MODE is ignored because the artifact is not gray4.
For native_four_gray, the state API advertises pixel_format: gray4, publishes the paired controller-plane artifact, and retains the existing gray4 diagnostics.
Performance
Structured gray4 content no longer performs a mandatory white pre-clear. The pre-clear remains opt-in for native Menu diagnostics. This restores Meal Plan, Recipe, and Let's Cook to the original fast bulk initialization path.