Files
crosspoint-reader-mod/chat-summaries/2026-02-13_letterbox-extend-edges.md

50 lines
2.9 KiB
Markdown
Raw Normal View History

# 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