# Sleep Screen Tweaks Implementation ## Task Description Implemented the two "Sleep Screen tweaks" from the plan: 1. **Gradient fill for letterboxed areas** - When a sleep screen image doesn't match the display's aspect ratio, the void (letterbox) areas are now filled with a dithered gradient sampled from the nearest ~20 pixels of the image's edge, fading toward white. 2. **Fix "Fit" mode for small images** - Images smaller than the 480x800 display are now scaled up (nearest-neighbor) to fit while preserving aspect ratio, instead of being displayed at native size with wasted screen space. ## Changes Made ### `lib/GfxRenderer/GfxRenderer.cpp` - Modified `drawBitmap()` scaling logic: when both `maxWidth` and `maxHeight` are provided, always computes an aspect-ratio-preserving scale factor (supports both upscaling and downscaling) - Modified `drawBitmap()` rendering loop: uses block-fill approach where each source pixel maps to a screen rectangle (handles both upscaling blocks and 1:1/downscaling single pixels via a unified loop) - Applied same changes to `drawBitmap1Bit()` for 1-bit bitmap consistency - Added `drawPixelGray()` method: draws a pixel using its 2-bit grayscale value, dispatching correctly based on the current render mode (BW, GRAYSCALE_LSB, GRAYSCALE_MSB) ### `lib/GfxRenderer/GfxRenderer.h` - Added `drawPixelGray(int x, int y, uint8_t val2bit)` declaration ### `lib/GfxRenderer/BitmapHelpers.cpp` - Added `quantizeNoiseDither()`: hash-based noise dithering that always uses noise (unlike `quantize()` which is controlled by a compile-time flag), used for smooth gradient rendering on the 4-level display ### `lib/GfxRenderer/BitmapHelpers.h` - Added `quantizeNoiseDither()` declaration ### `src/activities/boot_sleep/SleepActivity.cpp` - Removed the `if (bitmap.getWidth() > pageWidth || bitmap.getHeight() > pageHeight)` gate in `renderBitmapSleepScreen()` — position/scale is now always computed regardless of whether the image is larger or smaller than the screen - Added anonymous namespace with: - `LetterboxGradientData` struct for edge sample storage - `sampleBitmapEdges()`: reads bitmap row-by-row to sample per-column (or per-row) average gray values from the first/last 20 pixels of the image edges - `drawLetterboxGradients()`: draws dithered gradients in letterbox areas using sampled edge colors, interpolating toward white - Integrated gradient rendering into the sleep screen flow: edge sampling (first pass), then gradient + bitmap rendering in BW pass, and again in each grayscale pass (LSB, MSB) ## Follow-up: Letterbox Fill Settings Added three letterbox fill options and two new persisted settings: ### `src/CrossPointSettings.h` - Added `SLEEP_SCREEN_LETTERBOX_FILL` enum: `LETTERBOX_NONE` (plain white), `LETTERBOX_GRADIENT` (default), `LETTERBOX_SOLID` - Added `SLEEP_SCREEN_GRADIENT_DIR` enum: `GRADIENT_TO_WHITE` (default), `GRADIENT_TO_BLACK` - Added `sleepScreenLetterboxFill` and `sleepScreenGradientDir` member fields ### `src/CrossPointSettings.cpp` - Incremented `SETTINGS_COUNT` to 32 - Added serialization (read/write) for the two new fields at the end for backward compatibility ### `src/SettingsList.h` - Added "Letterbox Fill" menu entry (None / Gradient / Solid) in Display category - Added "Gradient Direction" menu entry (To White / To Black) in Display category ### `src/activities/boot_sleep/SleepActivity.cpp` - Renamed `drawLetterboxGradients` → `drawLetterboxFill` with added `solidFill` and `targetColor` parameters - Solid mode: uses edge color directly (no distance-based interpolation), quantized with noise dithering - Gradient direction: interpolates from edge color toward `targetColor` (255 for white, 0 for black) - `renderBitmapSleepScreen` reads the settings and skips edge sampling entirely when fill mode is "None" ## Follow-up Items - Test with various cover image sizes and aspect ratios on actual hardware - Test custom images from `/sleep/` directory (1-bit and multi-bit) - Monitor RAM usage via serial during gradient rendering