Move to SDK EInkDisplay and enable anti-aliased 2-bit text (#5)

* First pass at moving to SDK EInkDisplay library

* Add 2-bit grayscale text and anti-aliased rendering of text

* Render status bar for empty chapters

* Refresh screen every 15 pages to avoid ghosting

* Simplify boot and sleep screens

* Give FileSelectionScreen task more stack memory

* Move text around slightly on Boot and Sleep screens

* Re-use existing buffer and write to whole screen for 'partial update'
This commit is contained in:
Dave Allie
2025-12-08 19:48:49 +11:00
committed by GitHub
parent de453fed1d
commit 2ed8017aa2
31 changed files with 11300 additions and 9243 deletions

View File

@@ -11,4 +11,9 @@ void BootLogoScreen::onEnter() {
renderer.clearScreen();
// Location for images is from top right in landscape orientation
renderer.drawImage(CrossLarge, (pageHeight - 128) / 2, (pageWidth - 128) / 2, 128, 128);
const int width = renderer.getUiTextWidth("CrossPoint", BOLD);
renderer.drawUiText((pageWidth - width)/ 2, pageHeight / 2 + 70, "CrossPoint", true, BOLD);
const int bootingWidth = renderer.getSmallTextWidth("BOOTING");
renderer.drawSmallText((pageWidth - bootingWidth) / 2, pageHeight / 2 + 95, "BOOTING");
renderer.flushDisplay();
}

View File

@@ -1,11 +1,12 @@
#include "EpubReaderScreen.h"
#include <EpdRenderer.h>
#include <Epub/Page.h>
#include <SD.h>
#include "Battery.h"
constexpr int PAGES_PER_REFRESH = 20;
constexpr int PAGES_PER_REFRESH = 15;
constexpr unsigned long SKIP_CHAPTER_MS = 700;
void EpubReaderScreen::taskTrampoline(void* param) {
@@ -128,7 +129,7 @@ void EpubReaderScreen::displayTaskLoop() {
if (updateRequired) {
updateRequired = false;
xSemaphoreTake(renderingMutex, portMAX_DELAY);
renderPage();
renderScreen();
xSemaphoreGive(renderingMutex);
}
vTaskDelay(10 / portTICK_PERIOD_MS);
@@ -136,7 +137,7 @@ void EpubReaderScreen::displayTaskLoop() {
}
// TODO: Failure handling
void EpubReaderScreen::renderPage() {
void EpubReaderScreen::renderScreen() {
if (!epub) {
return;
}
@@ -159,10 +160,12 @@ void EpubReaderScreen::renderPage() {
constexpr int y = 50;
const int w = textWidth + margin * 2;
const int h = renderer.getLineHeight() + margin * 2;
renderer.swapBuffers();
renderer.fillRect(x, y, w, h, 0);
renderer.drawText(x + margin, y + margin, "Indexing...");
renderer.drawRect(x + 5, y + 5, w - 10, h - 10);
renderer.flushArea(x, y, w, h);
renderer.flushDisplay(EInkDisplay::HALF_REFRESH);
pagesUntilFullRefresh = 0;
}
section->setupCacheDir();
@@ -184,16 +187,29 @@ void EpubReaderScreen::renderPage() {
}
renderer.clearScreen();
section->renderPage();
renderStatusBar();
if (pagesUntilFullRefresh <= 1) {
renderer.flushDisplay(false);
pagesUntilFullRefresh = PAGES_PER_REFRESH;
} else {
if (section->pageCount == 0) {
Serial.println("No pages to render");
const int width = renderer.getTextWidth("Empty chapter", BOLD);
renderer.drawText((renderer.getPageWidth() - width) / 2, 300, "Empty chapter", true, BOLD);
renderStatusBar();
renderer.flushDisplay();
pagesUntilFullRefresh--;
return;
}
if (section->currentPage < 0 || section->currentPage >= section->pageCount) {
Serial.printf("Page out of bounds: %d (max %d)\n", section->currentPage, section->pageCount);
const int width = renderer.getTextWidth("Out of bounds", BOLD);
renderer.drawText((renderer.getPageWidth() - width) / 2, 300, "Out of bounds", true, BOLD);
renderStatusBar();
renderer.flushDisplay();
return;
}
const Page* p = section->loadPageFromSD();
renderContents(p);
delete p;
File f = SD.open((epub->getCachePath() + "/progress.bin").c_str(), FILE_WRITE);
uint8_t data[4];
data[0] = currentSpineIndex & 0xFF;
@@ -204,6 +220,36 @@ void EpubReaderScreen::renderPage() {
f.close();
}
void EpubReaderScreen::renderContents(const Page* p) {
p->render(renderer);
renderStatusBar();
if (pagesUntilFullRefresh <= 1) {
renderer.flushDisplay(EInkDisplay::HALF_REFRESH);
pagesUntilFullRefresh = PAGES_PER_REFRESH;
} else {
renderer.flushDisplay();
pagesUntilFullRefresh--;
}
// grayscale rendering
{
renderer.clearScreen(0x00);
renderer.setFontRendererMode(GRAYSCALE_LSB);
p->render(renderer);
renderer.copyGrayscaleLsbBuffers();
// Render and copy to MSB buffer
renderer.clearScreen(0x00);
renderer.setFontRendererMode(GRAYSCALE_MSB);
p->render(renderer);
renderer.copyGrayscaleMsbBuffers();
// display grayscale part
renderer.displayGrayBuffer();
renderer.setFontRendererMode(BW);
}
}
void EpubReaderScreen::renderStatusBar() const {
const auto pageWidth = renderer.getPageWidth();

View File

@@ -20,7 +20,8 @@ class EpubReaderScreen final : public Screen {
static void taskTrampoline(void* param);
[[noreturn]] void displayTaskLoop();
void renderPage();
void renderScreen();
void renderContents(const Page *p);
void renderStatusBar() const;
public:

View File

@@ -51,7 +51,7 @@ void FileSelectionScreen::onEnter() {
updateRequired = true;
xTaskCreate(&FileSelectionScreen::taskTrampoline, "FileSelectionScreenTask",
1024, // Stack size
2048, // Stack size
this, // Parameters
1, // Priority
&displayTaskHandle // Task handle
@@ -120,7 +120,7 @@ void FileSelectionScreen::render() const {
const auto pageWidth = renderer.getPageWidth();
const auto titleWidth = renderer.getTextWidth("CrossPoint Reader", BOLD);
renderer.drawText((pageWidth - titleWidth) / 2, 0, "CrossPoint Reader", 1, BOLD);
renderer.drawText((pageWidth - titleWidth) / 2, 0, "CrossPoint Reader", true, BOLD);
if (files.empty()) {
renderer.drawUiText(10, 50, "No EPUBs found");
@@ -130,7 +130,7 @@ void FileSelectionScreen::render() const {
for (size_t i = 0; i < files.size(); i++) {
const auto file = files[i];
renderer.drawUiText(10, 50 + i * 30, file.c_str(), i == selectorIndex ? 0 : 1);
renderer.drawUiText(10, 50 + i * 30, file.c_str(), i != selectorIndex);
}
}

View File

@@ -8,7 +8,7 @@ void FullScreenMessageScreen::onEnter() {
const auto left = (renderer.getPageWidth() - width) / 2;
const auto top = (renderer.getPageHeight() - height) / 2;
renderer.clearScreen(invert);
renderer.drawUiText(left, top, text.c_str(), invert ? 0 : 1, style);
renderer.flushDisplay(partialUpdate);
renderer.clearScreen();
renderer.drawUiText(left, top, text.c_str(), true, style);
renderer.flushDisplay(refreshMode);
}

View File

@@ -2,23 +2,22 @@
#include <string>
#include <utility>
#include "EpdFontFamily.h"
#include <EInkDisplay.h>
#include <EpdFontFamily.h>
#include "Screen.h"
class FullScreenMessageScreen final : public Screen {
std::string text;
EpdFontStyle style;
bool invert;
bool partialUpdate;
EInkDisplay::RefreshMode refreshMode;
public:
explicit FullScreenMessageScreen(EpdRenderer& renderer, InputManager& inputManager, std::string text,
const EpdFontStyle style = REGULAR, const bool invert = false,
const bool partialUpdate = true)
const EpdFontStyle style = REGULAR,
const EInkDisplay::RefreshMode refreshMode = EInkDisplay::FAST_REFRESH)
: Screen(renderer, inputManager),
text(std::move(text)),
style(style),
invert(invert),
partialUpdate(partialUpdate) {}
refreshMode(refreshMode) {}
void onEnter() override;
};

View File

@@ -2,6 +2,18 @@
#include <EpdRenderer.h>
#include "images/SleepScreenImg.h"
#include "images/CrossLarge.h"
void SleepScreen::onEnter() { renderer.drawImageNoMargin(SleepScreenImg, 0, 0, 800, 480, false, true); }
void SleepScreen::onEnter() {
const auto pageWidth = renderer.getPageWidth();
const auto pageHeight = renderer.getPageHeight();
renderer.clearScreen();
renderer.drawImage(CrossLarge, (pageHeight - 128) / 2, (pageWidth - 128) / 2, 128, 128);
const int width = renderer.getUiTextWidth("CrossPoint", BOLD);
renderer.drawUiText((pageWidth - width)/ 2, pageHeight / 2 + 70, "CrossPoint", true, BOLD);
const int bootingWidth = renderer.getSmallTextWidth("SLEEPING");
renderer.drawSmallText((pageWidth - bootingWidth) / 2, pageHeight / 2 + 95, "SLEEPING");
renderer.invertScreen();
renderer.flushDisplay(EInkDisplay::FULL_REFRESH);
}