50 lines
2.9 KiB
Markdown
50 lines
2.9 KiB
Markdown
|
|
# Letterbox Fill: Replace Dithering with Edge Replication ("Extend Edges")
|
||
|
|
|
||
|
|
**Date:** 2026-02-13
|
||
|
|
|
||
|
|
## Task Description
|
||
|
|
|
||
|
|
After implementing Bayer ordered dithering for the "Blended" letterbox fill mode (replacing earlier noise dithering), the user reported that the problematic cover (*The World in a Grain* by Vince Beiser) looked even worse. Investigation revealed that **any dithering technique** is fundamentally incompatible with the e-ink multi-pass rendering pipeline for large uniform fill areas:
|
||
|
|
|
||
|
|
- The BW HALF_REFRESH pass creates a visible dark/white pattern in the letterbox
|
||
|
|
- The subsequent greyscale correction can't cleanly handle mixed-level patterns in large uniform areas
|
||
|
|
- This causes crosstalk artifacts that can affect adjacent cover rendering
|
||
|
|
- Uniform fills (all same level, like Solid mode) work fine because all pixels go through identical correction
|
||
|
|
|
||
|
|
The user's key insight: the display already renders the cover's grayscale correctly (including Atkinson-dithered mixed levels). Instead of re-dithering an averaged color, replicate the cover's actual boundary pixels into the letterbox -- letting the display render them the same way it renders the cover itself.
|
||
|
|
|
||
|
|
## Changes Made
|
||
|
|
|
||
|
|
### `src/CrossPointSettings.h`
|
||
|
|
- Renamed `LETTERBOX_BLENDED` (value 2) → `LETTERBOX_EXTENDED`
|
||
|
|
- Updated comment to reflect "None / Solid / Extend Edges"
|
||
|
|
|
||
|
|
### `src/SettingsList.h`
|
||
|
|
- Changed UI label from `"Blended"` → `"Extend Edges"`
|
||
|
|
|
||
|
|
### `src/activities/boot_sleep/SleepActivity.cpp`
|
||
|
|
- **Removed**: `BAYER_4X4` matrix, `quantizeBayerDither()` function (all Bayer dithering code)
|
||
|
|
- **Added**: `getPackedPixel()`, `setPackedPixel()` helpers for packed 2-bit array access
|
||
|
|
- **Rewrote** `LetterboxFillData` struct:
|
||
|
|
- Added `edgeA`/`edgeB` (dynamically allocated packed 2-bit arrays) for per-pixel edge data
|
||
|
|
- Added `edgePixelCount`, `scale` for coordinate mapping
|
||
|
|
- Added `freeEdgeData()` cleanup method
|
||
|
|
- **Renamed** `computeEdgeAverages()` → `computeEdgeData()`:
|
||
|
|
- New `captureEdgePixels` parameter controls whether to allocate and fill edge arrays
|
||
|
|
- For horizontal letterboxing: captures first/last visible BMP rows
|
||
|
|
- For vertical letterboxing: captures leftmost/rightmost pixel per visible row
|
||
|
|
- Still computes averages for SOLID mode in the same pass
|
||
|
|
- **Rewrote** `drawLetterboxFill()`:
|
||
|
|
- SOLID mode: unchanged (uniform fill with snapped level)
|
||
|
|
- EXTENDED mode: maps screen coordinates → BMP coordinates via scale factor, looks up stored 2-bit pixel values from the cover's boundary row/column
|
||
|
|
- **Updated** `renderBitmapSleepScreen()`: new log messages, `freeEdgeData()` call at end
|
||
|
|
|
||
|
|
## Backward Compatibility
|
||
|
|
- Enum value 2 is unchanged (was `LETTERBOX_BLENDED`, now `LETTERBOX_EXTENDED`)
|
||
|
|
- Serialized settings files continue to work without migration
|
||
|
|
|
||
|
|
## Follow-up Items
|
||
|
|
- Test "Extend Edges" mode with both covers (Power Broker and World in a Grain)
|
||
|
|
- Test vertical letterboxing (left/right) if applicable covers are available
|
||
|
|
- Verify SOLID mode still works as expected
|