From 9dc839c24468b0e78615e0b26eca14082aaffccd Mon Sep 17 00:00:00 2001 From: Jonas Diemer Date: Mon, 12 Jan 2026 18:36:07 +0100 Subject: [PATCH 1/6] WIP: extend bitmap to y direction --- lib/GfxRenderer/GfxRenderer.cpp | 40 +++++++++++++++++---- lib/GfxRenderer/GfxRenderer.h | 5 +-- src/activities/boot_sleep/SleepActivity.cpp | 6 ++-- 3 files changed, 39 insertions(+), 12 deletions(-) diff --git a/lib/GfxRenderer/GfxRenderer.cpp b/lib/GfxRenderer/GfxRenderer.cpp index cc1288a..d4b9346 100644 --- a/lib/GfxRenderer/GfxRenderer.cpp +++ b/lib/GfxRenderer/GfxRenderer.cpp @@ -152,8 +152,17 @@ void GfxRenderer::drawImage(const uint8_t bitmap[], const int x, const int y, co einkDisplay.drawImage(bitmap, rotatedX, rotatedY, width, height); } +void GfxRenderer::drawVal(const int x, const int y, const uint8_t val) const { + if (renderMode == BW && val < 3) { + drawPixel(x, y); + } else if (renderMode == GRAYSCALE_MSB && (val == 1 || val == 2)) { + drawPixel(x, y, false); + } else if (renderMode == GRAYSCALE_LSB && val == 1) { + drawPixel(x, y, false); + } +} void GfxRenderer::drawBitmap(const Bitmap& bitmap, const int x, const int y, const int maxWidth, const int maxHeight, - const float cropX, const float cropY) const { + const float cropX, const float cropY, bool extend) const { float scale = 1.0f; bool isScaled = false; int cropPixX = std::floor(bitmap.getWidth() * cropX / 2.0f); @@ -220,12 +229,29 @@ void GfxRenderer::drawBitmap(const Bitmap& bitmap, const int x, const int y, con const uint8_t val = outputRow[bmpX / 4] >> (6 - ((bmpX * 2) % 8)) & 0x3; - if (renderMode == BW && val < 3) { - drawPixel(screenX, screenY); - } else if (renderMode == GRAYSCALE_MSB && (val == 1 || val == 2)) { - drawPixel(screenX, screenY, false); - } else if (renderMode == GRAYSCALE_LSB && val == 1) { - drawPixel(screenX, screenY, false); + drawVal(screenX, screenY, val); + + // draw extended pixels + /// amount of pixels taken from bitmap and repeated to extend + int extendY = 10; // TODO: fix rounding errors if this is not a divisor of height? + // don't draw MSB for darker extended area + // if (extend && renderMode != GRAYSCALE_MSB) { + if (extend) { + if (bmpY < extendY) { + for (int ny = 0; ny < extendY; ny++) { + // TODO: handle when extendY > y + const uint8_t rval = val + random(2); + drawVal(screenX, y - (bmpY + (extendY - 3) * (ny)), renderMode == GRAYSCALE_MSB ? rval : val); + } + // drawVal(screenX, 2*y - screenY, val); + } + int endY = y + bitmap.getHeight(); + // Serial.printf("[%lu] [GFX] Drawing bottom extension: screenY=%d, endY=%d\n", millis(), screenY, endY); + if (bmpY >= bitmap.getHeight() - extendY) { + for (int ny = 0; ny < extendY; ny++) { + drawVal(screenX, screenY + (ny + 1) * (extendY - 2), val); + } + } } } } diff --git a/lib/GfxRenderer/GfxRenderer.h b/lib/GfxRenderer/GfxRenderer.h index e3e9558..8193e26 100644 --- a/lib/GfxRenderer/GfxRenderer.h +++ b/lib/GfxRenderer/GfxRenderer.h @@ -62,12 +62,13 @@ class GfxRenderer { // Drawing void drawPixel(int x, int y, bool state = true) const; + void drawVal(const int x, const int y, const uint8_t val) const; void drawLine(int x1, int y1, int x2, int y2, bool state = true) const; void drawRect(int x, int y, int width, int height, bool state = true) const; void fillRect(int x, int y, int width, int height, bool state = true) const; void drawImage(const uint8_t bitmap[], int x, int y, int width, int height) const; - void drawBitmap(const Bitmap& bitmap, int x, int y, int maxWidth, int maxHeight, float cropX = 0, - float cropY = 0) const; + void drawBitmap(const Bitmap& bitmap, int x, int y, int maxWidth, int maxHeight, float cropX = 0, float cropY = 0, + bool extend = false) const; // Text int getTextWidth(int fontId, const char* text, EpdFontFamily::Style style = EpdFontFamily::REGULAR) const; diff --git a/src/activities/boot_sleep/SleepActivity.cpp b/src/activities/boot_sleep/SleepActivity.cpp index 3305a16..a9de529 100644 --- a/src/activities/boot_sleep/SleepActivity.cpp +++ b/src/activities/boot_sleep/SleepActivity.cpp @@ -172,20 +172,20 @@ void SleepActivity::renderBitmapSleepScreen(const Bitmap& bitmap) const { Serial.printf("[%lu] [SLP] drawing to %d x %d\n", millis(), x, y); renderer.clearScreen(); - renderer.drawBitmap(bitmap, x, y, pageWidth, pageHeight, cropX, cropY); + renderer.drawBitmap(bitmap, x, y, pageWidth, pageHeight, cropX, cropY, true); renderer.displayBuffer(EInkDisplay::HALF_REFRESH); if (bitmap.hasGreyscale()) { bitmap.rewindToData(); renderer.clearScreen(0x00); renderer.setRenderMode(GfxRenderer::GRAYSCALE_LSB); - renderer.drawBitmap(bitmap, x, y, pageWidth, pageHeight, cropX, cropY); + renderer.drawBitmap(bitmap, x, y, pageWidth, pageHeight, cropX, cropY, true); renderer.copyGrayscaleLsbBuffers(); bitmap.rewindToData(); renderer.clearScreen(0x00); renderer.setRenderMode(GfxRenderer::GRAYSCALE_MSB); - renderer.drawBitmap(bitmap, x, y, pageWidth, pageHeight, cropX, cropY); + renderer.drawBitmap(bitmap, x, y, pageWidth, pageHeight, cropX, cropY, true); renderer.copyGrayscaleMsbBuffers(); renderer.displayGrayBuffer(); From 075324948ee7277923dfda44cddc38603f5c42bb Mon Sep 17 00:00:00 2001 From: Jonas Diemer Date: Mon, 12 Jan 2026 21:47:55 +0100 Subject: [PATCH 2/6] Implemented extending the bmp in Y direction --- lib/GfxRenderer/GfxRenderer.cpp | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/lib/GfxRenderer/GfxRenderer.cpp b/lib/GfxRenderer/GfxRenderer.cpp index d4b9346..e1b96f9 100644 --- a/lib/GfxRenderer/GfxRenderer.cpp +++ b/lib/GfxRenderer/GfxRenderer.cpp @@ -233,23 +233,31 @@ void GfxRenderer::drawBitmap(const Bitmap& bitmap, const int x, const int y, con // draw extended pixels /// amount of pixels taken from bitmap and repeated to extend - int extendY = 10; // TODO: fix rounding errors if this is not a divisor of height? + int extendY = 20; + int drawExtY = 0; // don't draw MSB for darker extended area - // if (extend && renderMode != GRAYSCALE_MSB) { - if (extend) { - if (bmpY < extendY) { - for (int ny = 0; ny < extendY; ny++) { + if (extend && renderMode != GRAYSCALE_MSB) { + // if (extend) { + if (screenY - y < extendY) { + for (int ny = 0; ny < y / extendY / 2; ny++) { // TODO: handle when extendY > y - const uint8_t rval = val + random(2); - drawVal(screenX, y - (bmpY + (extendY - 3) * (ny)), renderMode == GRAYSCALE_MSB ? rval : val); + // const uint8_t rval = val + random(3); + const uint8_t rval = val; + -(std::rand() < (RAND_MAX + 1.0) * 0.25); + drawExtY = y - (screenY - y + 2 * ny * (extendY)); + if (drawExtY > 0) drawVal(screenX, drawExtY, renderMode == BW ? rval : val); + drawExtY = screenY + 1 - (ny + 1) * extendY * 2; + if (drawExtY > 0) drawVal(screenX, drawExtY, renderMode == BW ? rval : val); } // drawVal(screenX, 2*y - screenY, val); } - int endY = y + bitmap.getHeight(); - // Serial.printf("[%lu] [GFX] Drawing bottom extension: screenY=%d, endY=%d\n", millis(), screenY, endY); - if (bmpY >= bitmap.getHeight() - extendY) { - for (int ny = 0; ny < extendY; ny++) { - drawVal(screenX, screenY + (ny + 1) * (extendY - 2), val); + if (screenY >= getScreenHeight() - y - extendY) { + for (int ny = 0; ny < y / extendY / 2; ny++) { + // int drawExtY = screenY + extendY + 1 + (ny) * (extendY); + drawExtY = getScreenHeight() - y - 1 + (getScreenHeight() - y - screenY) + (ny) * (extendY) * 2; + if (drawExtY < getScreenHeight()) drawVal(screenX, drawExtY, val); + drawExtY = screenY + (ny + 1) * (extendY) * 2; + if (drawExtY < getScreenHeight()) drawVal(screenX, drawExtY, val); } } } From 1f2d5c64b1c36960e9bd9c31877aeca0b6a944cc Mon Sep 17 00:00:00 2001 From: Jonas Diemer Date: Mon, 12 Jan 2026 21:49:17 +0100 Subject: [PATCH 3/6] Render normal, not darker. --- lib/GfxRenderer/GfxRenderer.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/GfxRenderer/GfxRenderer.cpp b/lib/GfxRenderer/GfxRenderer.cpp index e1b96f9..b47938e 100644 --- a/lib/GfxRenderer/GfxRenderer.cpp +++ b/lib/GfxRenderer/GfxRenderer.cpp @@ -236,8 +236,8 @@ void GfxRenderer::drawBitmap(const Bitmap& bitmap, const int x, const int y, con int extendY = 20; int drawExtY = 0; // don't draw MSB for darker extended area - if (extend && renderMode != GRAYSCALE_MSB) { - // if (extend) { + // if (extend && renderMode != GRAYSCALE_MSB) { + if (extend) { if (screenY - y < extendY) { for (int ny = 0; ny < y / extendY / 2; ny++) { // TODO: handle when extendY > y From 3ae73098d86898f92cb03d4f99be18888f7910e8 Mon Sep 17 00:00:00 2001 From: Jonas Diemer Date: Tue, 13 Jan 2026 09:04:53 +0100 Subject: [PATCH 4/6] Cleanup and additional clarity. --- lib/GfxRenderer/GfxRenderer.cpp | 53 ++++++++++++++++++++------------- 1 file changed, 33 insertions(+), 20 deletions(-) diff --git a/lib/GfxRenderer/GfxRenderer.cpp b/lib/GfxRenderer/GfxRenderer.cpp index b47938e..1989658 100644 --- a/lib/GfxRenderer/GfxRenderer.cpp +++ b/lib/GfxRenderer/GfxRenderer.cpp @@ -235,29 +235,42 @@ void GfxRenderer::drawBitmap(const Bitmap& bitmap, const int x, const int y, con /// amount of pixels taken from bitmap and repeated to extend int extendY = 20; int drawExtY = 0; - // don't draw MSB for darker extended area - // if (extend && renderMode != GRAYSCALE_MSB) { if (extend) { - if (screenY - y < extendY) { - for (int ny = 0; ny < y / extendY / 2; ny++) { - // TODO: handle when extendY > y - // const uint8_t rval = val + random(3); - const uint8_t rval = val; - -(std::rand() < (RAND_MAX + 1.0) * 0.25); - drawExtY = y - (screenY - y + 2 * ny * (extendY)); - if (drawExtY > 0) drawVal(screenX, drawExtY, renderMode == BW ? rval : val); - drawExtY = screenY + 1 - (ny + 1) * extendY * 2; - if (drawExtY > 0) drawVal(screenX, drawExtY, renderMode == BW ? rval : val); + // 1. TOP EXTENSION + // Check if the current pixel is within the strip to be mirrored + if (screenY >= y && screenY < y + extendY) { + // How many times do we need to mirror to fill the gap 'y'? + // Using +1 to ensure we cover fractional blocks at the screen edge + int numIterations = (y / extendY) + 1; + + for (int ny = 0; ny < numIterations; ny++) { + // Compute 2 target rows t1, t2 for "accordeon" effect. + // Mirror Fold (e.g., pixel 0 goes to y-1, pixel 1 to y-2) + int t1 = y - 1 - (2 * ny * extendY + (screenY - y)); + // Reverse Fold (creates the 'accordion' continuity) + int t2 = y - 1 - (2 * ny * extendY + (2 * extendY - 1 - (screenY - y))); + + if (t1 >= 0 && t1 < y) drawVal(screenX, t1, val); + if (t2 >= 0 && t2 < y) drawVal(screenX, t2, val); } - // drawVal(screenX, 2*y - screenY, val); } - if (screenY >= getScreenHeight() - y - extendY) { - for (int ny = 0; ny < y / extendY / 2; ny++) { - // int drawExtY = screenY + extendY + 1 + (ny) * (extendY); - drawExtY = getScreenHeight() - y - 1 + (getScreenHeight() - y - screenY) + (ny) * (extendY) * 2; - if (drawExtY < getScreenHeight()) drawVal(screenX, drawExtY, val); - drawExtY = screenY + (ny + 1) * (extendY) * 2; - if (drawExtY < getScreenHeight()) drawVal(screenX, drawExtY, val); + + // 2. BOTTOM EXTENSION + int imgHeight = std::floor(scale * (bitmap.getHeight() - cropPixY)); + int imgBottom = y + imgHeight; + int gapBottom = getScreenHeight() - imgBottom; + + if (screenY >= imgBottom - extendY && screenY < imgBottom) { + int numIterations = (gapBottom / extendY) + 1; + + for (int ny = 0; ny < numIterations; ny++) { + // Mirror Fold (pixel at imgBottom-1 goes to imgBottom) + int t1 = imgBottom + (2 * ny * extendY + (imgBottom - 1 - screenY)); + // Reverse Fold + int t2 = imgBottom + (2 * ny * extendY + (2 * extendY - 1 - (imgBottom - 1 - screenY))); + + if (t1 >= imgBottom && t1 < getScreenHeight()) drawVal(screenX, t1, val); + if (t2 >= imgBottom && t2 < getScreenHeight()) drawVal(screenX, t2, val); } } } From 757753df727ada7ccdbe3a5504d10bbab00d20ec Mon Sep 17 00:00:00 2001 From: Jonas Diemer Date: Tue, 13 Jan 2026 10:51:56 +0100 Subject: [PATCH 5/6] Cover mode setting "extend", extension in x. --- lib/GfxRenderer/GfxRenderer.cpp | 36 +++++++++++++++++++- src/CrossPointSettings.h | 2 +- src/activities/boot_sleep/SleepActivity.cpp | 7 ++-- src/activities/settings/SettingsActivity.cpp | 2 +- 4 files changed, 41 insertions(+), 6 deletions(-) diff --git a/lib/GfxRenderer/GfxRenderer.cpp b/lib/GfxRenderer/GfxRenderer.cpp index 1989658..e94ea46 100644 --- a/lib/GfxRenderer/GfxRenderer.cpp +++ b/lib/GfxRenderer/GfxRenderer.cpp @@ -234,8 +234,11 @@ void GfxRenderer::drawBitmap(const Bitmap& bitmap, const int x, const int y, con // draw extended pixels /// amount of pixels taken from bitmap and repeated to extend int extendY = 20; + int extendX = 20; // The width of the strip to be mirrored int drawExtY = 0; if (extend) { + int imgHeight = std::floor(scale * (bitmap.getHeight() - cropPixY)); + int imgWidth = std::floor(scale * (bitmap.getWidth() - cropPixX)); // 1. TOP EXTENSION // Check if the current pixel is within the strip to be mirrored if (screenY >= y && screenY < y + extendY) { @@ -256,7 +259,6 @@ void GfxRenderer::drawBitmap(const Bitmap& bitmap, const int x, const int y, con } // 2. BOTTOM EXTENSION - int imgHeight = std::floor(scale * (bitmap.getHeight() - cropPixY)); int imgBottom = y + imgHeight; int gapBottom = getScreenHeight() - imgBottom; @@ -273,6 +275,38 @@ void GfxRenderer::drawBitmap(const Bitmap& bitmap, const int x, const int y, con if (t2 >= imgBottom && t2 < getScreenHeight()) drawVal(screenX, t2, val); } } + + // --- 2. LEFT EXTENSION --- + int imgRight = x + imgWidth; // x is the left margin/offset + // If the current pixel is within the leftmost 'extendX' pixels of the image + if (screenX >= x && screenX < x + extendX) { + int numIterations = (x / extendX) + 1; + for (int nx = 0; nx < numIterations; nx++) { + // Mirror Fold (pixel at 'x' maps to 'x-1') + int t1 = x - 1 - (2 * nx * extendX + (screenX - x)); + // Reverse Fold + int t2 = x - 1 - (2 * nx * extendX + (2 * extendX - 1 - (screenX - x))); + + if (t1 >= 0 && t1 < x) drawVal(t1, screenY, val); + if (t2 >= 0 && t2 < x) drawVal(t2, screenY, val); + } + } + + // --- 3. RIGHT EXTENSION --- + int gapRight = getScreenWidth() - imgRight; + // If the current pixel is within the rightmost 'extendX' pixels of the image + if (screenX >= imgRight - extendX && screenX < imgRight) { + int numIterations = (gapRight / extendX) + 1; + for (int nx = 0; nx < numIterations; nx++) { + // Mirror Fold (pixel at 'imgRight-1' maps to 'imgRight') + int t1 = imgRight + (2 * nx * extendX + (imgRight - 1 - screenX)); + // Reverse Fold + int t2 = imgRight + (2 * nx * extendX + (2 * extendX - 1 - (imgRight - 1 - screenX))); + + if (t1 >= imgRight && t1 < getScreenWidth()) drawVal(t1, screenY, val); + if (t2 >= imgRight && t2 < getScreenWidth()) drawVal(t2, screenY, val); + } + } } } } diff --git a/src/CrossPointSettings.h b/src/CrossPointSettings.h index d5f9103..4a03786 100644 --- a/src/CrossPointSettings.h +++ b/src/CrossPointSettings.h @@ -17,7 +17,7 @@ class CrossPointSettings { // Should match with SettingsActivity text enum SLEEP_SCREEN_MODE { DARK = 0, LIGHT = 1, CUSTOM = 2, COVER = 3, BLANK = 4 }; - enum SLEEP_SCREEN_COVER_MODE { FIT = 0, CROP = 1 }; + enum SLEEP_SCREEN_COVER_MODE { FIT = 0, CROP = 1, EXTEND = 2 }; // Status bar display type enum enum STATUS_BAR_MODE { NONE = 0, NO_PROGRESS = 1, FULL = 2 }; diff --git a/src/activities/boot_sleep/SleepActivity.cpp b/src/activities/boot_sleep/SleepActivity.cpp index a9de529..6003d47 100644 --- a/src/activities/boot_sleep/SleepActivity.cpp +++ b/src/activities/boot_sleep/SleepActivity.cpp @@ -170,22 +170,23 @@ void SleepActivity::renderBitmapSleepScreen(const Bitmap& bitmap) const { y = (pageHeight - bitmap.getHeight()) / 2; } + bool extended = SETTINGS.sleepScreenCoverMode == CrossPointSettings::SLEEP_SCREEN_COVER_MODE::EXTEND; Serial.printf("[%lu] [SLP] drawing to %d x %d\n", millis(), x, y); renderer.clearScreen(); - renderer.drawBitmap(bitmap, x, y, pageWidth, pageHeight, cropX, cropY, true); + renderer.drawBitmap(bitmap, x, y, pageWidth, pageHeight, cropX, cropY, extended); renderer.displayBuffer(EInkDisplay::HALF_REFRESH); if (bitmap.hasGreyscale()) { bitmap.rewindToData(); renderer.clearScreen(0x00); renderer.setRenderMode(GfxRenderer::GRAYSCALE_LSB); - renderer.drawBitmap(bitmap, x, y, pageWidth, pageHeight, cropX, cropY, true); + renderer.drawBitmap(bitmap, x, y, pageWidth, pageHeight, cropX, cropY, extended); renderer.copyGrayscaleLsbBuffers(); bitmap.rewindToData(); renderer.clearScreen(0x00); renderer.setRenderMode(GfxRenderer::GRAYSCALE_MSB); - renderer.drawBitmap(bitmap, x, y, pageWidth, pageHeight, cropX, cropY, true); + renderer.drawBitmap(bitmap, x, y, pageWidth, pageHeight, cropX, cropY, extended); renderer.copyGrayscaleMsbBuffers(); renderer.displayGrayBuffer(); diff --git a/src/activities/settings/SettingsActivity.cpp b/src/activities/settings/SettingsActivity.cpp index f22850a..9a9e459 100644 --- a/src/activities/settings/SettingsActivity.cpp +++ b/src/activities/settings/SettingsActivity.cpp @@ -17,7 +17,7 @@ constexpr int settingsCount = 19; const SettingInfo settingsList[settingsCount] = { // Should match with SLEEP_SCREEN_MODE SettingInfo::Enum("Sleep Screen", &CrossPointSettings::sleepScreen, {"Dark", "Light", "Custom", "Cover", "None"}), - SettingInfo::Enum("Sleep Screen Cover Mode", &CrossPointSettings::sleepScreenCoverMode, {"Fit", "Crop"}), + SettingInfo::Enum("Sleep Screen Cover Mode", &CrossPointSettings::sleepScreenCoverMode, {"Fit", "Crop", "Extend"}), SettingInfo::Enum("Status Bar", &CrossPointSettings::statusBar, {"None", "No Progress", "Full"}), SettingInfo::Enum("Hide Battery %", &CrossPointSettings::hideBatteryPercentage, {"Never", "In Reader", "Always"}), SettingInfo::Toggle("Extra Paragraph Spacing", &CrossPointSettings::extraParagraphSpacing), From b9cb412c1ce8d412419eec337339c68a2ce5e1b5 Mon Sep 17 00:00:00 2001 From: Jonas Diemer Date: Tue, 13 Jan 2026 11:38:22 +0100 Subject: [PATCH 6/6] Fixed name of cropped cover bmp, clarified comment. --- lib/Epub/Epub.cpp | 2 +- lib/GfxRenderer/GfxRenderer.cpp | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/Epub/Epub.cpp b/lib/Epub/Epub.cpp index 64727bc..65cfd4b 100644 --- a/lib/Epub/Epub.cpp +++ b/lib/Epub/Epub.cpp @@ -349,7 +349,7 @@ const std::string& Epub::getAuthor() const { } std::string Epub::getCoverBmpPath(bool cropped) const { - const auto coverFileName = "cover" + cropped ? "_crop" : ""; + const auto coverFileName = std::string("cover") + (cropped ? "_crop" : ""); return cachePath + "/" + coverFileName + ".bmp"; } diff --git a/lib/GfxRenderer/GfxRenderer.cpp b/lib/GfxRenderer/GfxRenderer.cpp index e94ea46..ebcbfe2 100644 --- a/lib/GfxRenderer/GfxRenderer.cpp +++ b/lib/GfxRenderer/GfxRenderer.cpp @@ -232,9 +232,10 @@ void GfxRenderer::drawBitmap(const Bitmap& bitmap, const int x, const int y, con drawVal(screenX, screenY, val); // draw extended pixels - /// amount of pixels taken from bitmap and repeated to extend + // amount of pixels taken from bitmap and repeated to extend. + // Trade-off between risk of repeating "edgy" content like text and more quality int extendY = 20; - int extendX = 20; // The width of the strip to be mirrored + int extendX = 20; int drawExtY = 0; if (extend) { int imgHeight = std::floor(scale * (bitmap.getHeight() - cropPixY));