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:
@@ -1,17 +1,17 @@
|
||||
#include "EpdRenderer.h"
|
||||
|
||||
#include "builtinFonts/babyblue.h"
|
||||
#include "builtinFonts/bookerly.h"
|
||||
#include "builtinFonts/bookerly_bold.h"
|
||||
#include "builtinFonts/bookerly_bold_italic.h"
|
||||
#include "builtinFonts/bookerly_italic.h"
|
||||
#include "builtinFonts/bookerly_2b.h"
|
||||
#include "builtinFonts/bookerly_bold_2b.h"
|
||||
#include "builtinFonts/bookerly_bold_italic_2b.h"
|
||||
#include "builtinFonts/bookerly_italic_2b.h"
|
||||
#include "builtinFonts/ubuntu_10.h"
|
||||
#include "builtinFonts/ubuntu_bold_10.h"
|
||||
|
||||
EpdFont bookerlyFont(&bookerly);
|
||||
EpdFont bookerlyBoldFont(&bookerly_bold);
|
||||
EpdFont bookerlyItalicFont(&bookerly_italic);
|
||||
EpdFont bookerlyBoldItalicFont(&bookerly_bold_italic);
|
||||
EpdFont bookerlyFont(&bookerly_2b);
|
||||
EpdFont bookerlyBoldFont(&bookerly_bold_2b);
|
||||
EpdFont bookerlyItalicFont(&bookerly_italic_2b);
|
||||
EpdFont bookerlyBoldItalicFont(&bookerly_bold_italic_2b);
|
||||
EpdFontFamily bookerlyFontFamily(&bookerlyFont, &bookerlyBoldFont, &bookerlyItalicFont, &bookerlyBoldItalicFont);
|
||||
|
||||
EpdFont smallFont(&babyblue);
|
||||
@@ -21,11 +21,17 @@ EpdFont ubuntu10Font(&ubuntu_10);
|
||||
EpdFont ununtuBold10Font(&ubuntu_bold_10);
|
||||
EpdFontFamily ubuntuFontFamily(&ubuntu10Font, &ununtuBold10Font);
|
||||
|
||||
EpdRenderer::EpdRenderer(XteinkDisplay& display)
|
||||
: display(display), marginTop(11), marginBottom(30), marginLeft(10), marginRight(10), lineCompression(0.95f) {
|
||||
this->regularFontRenderer = new EpdFontRenderer<XteinkDisplay>(&bookerlyFontFamily, display);
|
||||
this->smallFontRenderer = new EpdFontRenderer<XteinkDisplay>(&smallFontFamily, display);
|
||||
this->uiFontRenderer = new EpdFontRenderer<XteinkDisplay>(&ubuntuFontFamily, display);
|
||||
EpdRenderer::EpdRenderer(EInkDisplay& einkDisplay)
|
||||
: einkDisplay(einkDisplay),
|
||||
marginTop(11),
|
||||
marginBottom(30),
|
||||
marginLeft(10),
|
||||
marginRight(10),
|
||||
fontRendererMode(BW),
|
||||
lineCompression(0.95f) {
|
||||
this->regularFontRenderer = new EpdFontRenderer<EInkDisplay>(&bookerlyFontFamily, einkDisplay);
|
||||
this->smallFontRenderer = new EpdFontRenderer<EInkDisplay>(&smallFontFamily, einkDisplay);
|
||||
this->uiFontRenderer = new EpdFontRenderer<EInkDisplay>(&ubuntuFontFamily, einkDisplay);
|
||||
}
|
||||
|
||||
EpdRenderer::~EpdRenderer() {
|
||||
@@ -34,6 +40,40 @@ EpdRenderer::~EpdRenderer() {
|
||||
delete uiFontRenderer;
|
||||
}
|
||||
|
||||
void EpdRenderer::drawPixel(const int x, const int y, const bool state) const {
|
||||
uint8_t* frameBuffer = einkDisplay.getFrameBuffer();
|
||||
|
||||
// Early return if no framebuffer is set
|
||||
if (!frameBuffer) {
|
||||
Serial.printf("!!No framebuffer\n");
|
||||
return;
|
||||
}
|
||||
|
||||
const int adjX = x + marginLeft;
|
||||
const int adjY = y + marginTop;
|
||||
|
||||
// Bounds checking (portrait: 480x800)
|
||||
if (adjX < 0 || adjX >= EInkDisplay::DISPLAY_HEIGHT || adjY < 0 || adjY >= EInkDisplay::DISPLAY_WIDTH) {
|
||||
Serial.printf("!!Outside range (%d, %d)\n", adjX, adjY);
|
||||
return;
|
||||
}
|
||||
|
||||
// Rotate coordinates: portrait (480x800) -> landscape (800x480)
|
||||
// Rotation: 90 degrees clockwise
|
||||
const int rotatedX = adjY;
|
||||
const int rotatedY = EInkDisplay::DISPLAY_HEIGHT - 1 - adjX;
|
||||
|
||||
// Calculate byte position and bit position
|
||||
const uint16_t byteIndex = rotatedY * EInkDisplay::DISPLAY_WIDTH_BYTES + (rotatedX / 8);
|
||||
const uint8_t bitPosition = 7 - (rotatedX % 8); // MSB first
|
||||
|
||||
if (state) {
|
||||
frameBuffer[byteIndex] &= ~(1 << bitPosition); // Clear bit
|
||||
} else {
|
||||
frameBuffer[byteIndex] |= 1 << bitPosition; // Set bit
|
||||
}
|
||||
}
|
||||
|
||||
int EpdRenderer::getTextWidth(const char* text, const EpdFontStyle style) const {
|
||||
int w = 0, h = 0;
|
||||
|
||||
@@ -58,25 +98,25 @@ int EpdRenderer::getSmallTextWidth(const char* text, const EpdFontStyle style) c
|
||||
return w;
|
||||
}
|
||||
|
||||
void EpdRenderer::drawText(const int x, const int y, const char* text, const uint16_t color,
|
||||
void EpdRenderer::drawText(const int x, const int y, const char* text, const bool state,
|
||||
const EpdFontStyle style) const {
|
||||
int ypos = y + getLineHeight() + marginTop;
|
||||
int xpos = x + marginLeft;
|
||||
regularFontRenderer->renderString(text, &xpos, &ypos, color > 0 ? GxEPD_BLACK : GxEPD_WHITE, style);
|
||||
regularFontRenderer->renderString(text, &xpos, &ypos, state, style, fontRendererMode);
|
||||
}
|
||||
|
||||
void EpdRenderer::drawUiText(const int x, const int y, const char* text, const uint16_t color,
|
||||
void EpdRenderer::drawUiText(const int x, const int y, const char* text, const bool state,
|
||||
const EpdFontStyle style) const {
|
||||
int ypos = y + uiFontRenderer->fontFamily->getData(style)->advanceY + marginTop;
|
||||
int xpos = x + marginLeft;
|
||||
uiFontRenderer->renderString(text, &xpos, &ypos, color > 0 ? GxEPD_BLACK : GxEPD_WHITE, style);
|
||||
uiFontRenderer->renderString(text, &xpos, &ypos, state, style, fontRendererMode);
|
||||
}
|
||||
|
||||
void EpdRenderer::drawSmallText(const int x, const int y, const char* text, const uint16_t color,
|
||||
void EpdRenderer::drawSmallText(const int x, const int y, const char* text, const bool state,
|
||||
const EpdFontStyle style) const {
|
||||
int ypos = y + smallFontRenderer->fontFamily->getData(style)->advanceY + marginTop;
|
||||
int xpos = x + marginLeft;
|
||||
smallFontRenderer->renderString(text, &xpos, &ypos, color > 0 ? GxEPD_BLACK : GxEPD_WHITE, style);
|
||||
smallFontRenderer->renderString(text, &xpos, &ypos, state, style, fontRendererMode);
|
||||
}
|
||||
|
||||
void EpdRenderer::drawTextBox(const int x, const int y, const std::string& text, const int width, const int height,
|
||||
@@ -88,7 +128,7 @@ void EpdRenderer::drawTextBox(const int x, const int y, const std::string& text,
|
||||
int ypos = 0;
|
||||
while (true) {
|
||||
if (end >= length) {
|
||||
drawText(x, y + ypos, text.substr(start, length - start).c_str(), 1, style);
|
||||
drawText(x, y + ypos, text.substr(start, length - start).c_str(), true, style);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -97,7 +137,7 @@ void EpdRenderer::drawTextBox(const int x, const int y, const std::string& text,
|
||||
}
|
||||
|
||||
if (text[end - 1] == '\n') {
|
||||
drawText(x, y + ypos, text.substr(start, end - start).c_str(), 1, style);
|
||||
drawText(x, y + ypos, text.substr(start, end - start).c_str(), true, style);
|
||||
ypos += getLineHeight();
|
||||
start = end;
|
||||
end = start + 1;
|
||||
@@ -105,7 +145,7 @@ void EpdRenderer::drawTextBox(const int x, const int y, const std::string& text,
|
||||
}
|
||||
|
||||
if (getTextWidth(text.substr(start, end - start).c_str(), style) > width) {
|
||||
drawText(x, y + ypos, text.substr(start, end - start - 1).c_str(), 1, style);
|
||||
drawText(x, y + ypos, text.substr(start, end - start - 1).c_str(), true, style);
|
||||
ypos += getLineHeight();
|
||||
start = end - 1;
|
||||
continue;
|
||||
@@ -115,54 +155,88 @@ void EpdRenderer::drawTextBox(const int x, const int y, const std::string& text,
|
||||
}
|
||||
}
|
||||
|
||||
void EpdRenderer::drawLine(int x1, int y1, int x2, int y2, uint16_t color) const {
|
||||
display.drawLine(x1 + marginLeft, y1 + marginTop, x2 + marginLeft, y2 + marginTop,
|
||||
color > 0 ? GxEPD_BLACK : GxEPD_WHITE);
|
||||
void EpdRenderer::drawLine(int x1, int y1, int x2, int y2, const bool state) const {
|
||||
if (x1 == x2) {
|
||||
if (y2 < y1) {
|
||||
std::swap(y1, y2);
|
||||
}
|
||||
for (int y = y1; y <= y2; y++) {
|
||||
drawPixel(x1, y, state);
|
||||
}
|
||||
} else if (y1 == y2) {
|
||||
if (x2 < x1) {
|
||||
std::swap(x1, x2);
|
||||
}
|
||||
for (int x = x1; x <= x2; x++) {
|
||||
drawPixel(x, y1, state);
|
||||
}
|
||||
} else {
|
||||
// TODO: Implement
|
||||
Serial.println("Line drawing not supported");
|
||||
}
|
||||
}
|
||||
|
||||
void EpdRenderer::drawRect(const int x, const int y, const int width, const int height, const uint16_t color) const {
|
||||
display.drawRect(x + marginLeft, y + marginTop, width, height, color > 0 ? GxEPD_BLACK : GxEPD_WHITE);
|
||||
void EpdRenderer::drawRect(const int x, const int y, const int width, const int height, const bool state) const {
|
||||
drawLine(x, y, x + width - 1, y, state);
|
||||
drawLine(x + width - 1, y, x + width - 1, y + height - 1, state);
|
||||
drawLine(x + width - 1, y + height - 1, x, y + height - 1, state);
|
||||
drawLine(x, y, x, y + height - 1, state);
|
||||
}
|
||||
|
||||
void EpdRenderer::fillRect(const int x, const int y, const int width, const int height, const uint16_t color) const {
|
||||
display.fillRect(x + marginLeft, y + marginTop, width, height, color > 0 ? GxEPD_BLACK : GxEPD_WHITE);
|
||||
void EpdRenderer::fillRect(const int x, const int y, const int width, const int height, const bool state) const {
|
||||
for (int fillY = y; fillY < y + height; fillY++) {
|
||||
drawLine(x, fillY, x + width - 1, fillY, state);
|
||||
}
|
||||
}
|
||||
|
||||
void EpdRenderer::drawCircle(const int x, const int y, const int radius, const uint16_t color) const {
|
||||
display.drawCircle(x + marginLeft, y + marginTop, radius, color > 0 ? GxEPD_BLACK : GxEPD_WHITE);
|
||||
void EpdRenderer::drawImage(const uint8_t bitmap[], const int x, const int y, const int width, const int height) const {
|
||||
drawImageNoMargin(bitmap, x + marginLeft, y + marginTop, width, height);
|
||||
}
|
||||
|
||||
void EpdRenderer::fillCircle(const int x, const int y, const int radius, const uint16_t color) const {
|
||||
display.fillCircle(x + marginLeft, y + marginTop, radius, color > 0 ? GxEPD_BLACK : GxEPD_WHITE);
|
||||
// TODO: Support y-mirror?
|
||||
void EpdRenderer::drawImageNoMargin(const uint8_t bitmap[], const int x, const int y, const int width,
|
||||
const int height) const {
|
||||
einkDisplay.drawImage(bitmap, x, y, width, height);
|
||||
}
|
||||
|
||||
void EpdRenderer::drawImage(const uint8_t bitmap[], const int x, const int y, const int width, const int height,
|
||||
const bool invert, const bool mirrorY) const {
|
||||
drawImageNoMargin(bitmap, x + marginLeft, y + marginTop, width, height, invert, mirrorY);
|
||||
}
|
||||
|
||||
void EpdRenderer::drawImageNoMargin(const uint8_t bitmap[], const int x, const int y, const int width, const int height,
|
||||
const bool invert, const bool mirrorY) const {
|
||||
display.drawImage(bitmap, x, y, width, height, invert, mirrorY);
|
||||
}
|
||||
|
||||
void EpdRenderer::clearScreen(const bool black) const {
|
||||
void EpdRenderer::clearScreen(const uint8_t color) const {
|
||||
Serial.println("Clearing screen");
|
||||
display.fillScreen(black ? GxEPD_BLACK : GxEPD_WHITE);
|
||||
einkDisplay.clearScreen(color);
|
||||
}
|
||||
|
||||
void EpdRenderer::flushDisplay(const bool partialUpdate) const { display.display(partialUpdate); }
|
||||
|
||||
void EpdRenderer::flushArea(const int x, const int y, const int width, const int height) const {
|
||||
display.displayWindow(x + marginLeft, y + marginTop, width, height);
|
||||
void EpdRenderer::invertScreen() const {
|
||||
uint8_t *buffer = einkDisplay.getFrameBuffer();
|
||||
for (int i = 0; i < EInkDisplay::BUFFER_SIZE; i++) {
|
||||
buffer[i] = ~buffer[i];
|
||||
}
|
||||
}
|
||||
|
||||
int EpdRenderer::getPageWidth() const { return display.width() - marginLeft - marginRight; }
|
||||
void EpdRenderer::flushDisplay(const EInkDisplay::RefreshMode refreshMode) const {
|
||||
einkDisplay.displayBuffer(refreshMode);
|
||||
}
|
||||
|
||||
int EpdRenderer::getPageHeight() const { return display.height() - marginTop - marginBottom; }
|
||||
// TODO: Support partial window update
|
||||
// void EpdRenderer::flushArea(const int x, const int y, const int width, const int height) const {
|
||||
// const int rotatedX = y;
|
||||
// const int rotatedY = EInkDisplay::DISPLAY_HEIGHT - 1 - x;
|
||||
//
|
||||
// einkDisplay.displayBuffer(EInkDisplay::FAST_REFRESH, rotatedX, rotatedY, height, width);
|
||||
// }
|
||||
|
||||
int EpdRenderer::getPageWidth() const { return EInkDisplay::DISPLAY_HEIGHT - marginLeft - marginRight; }
|
||||
|
||||
int EpdRenderer::getPageHeight() const { return EInkDisplay::DISPLAY_WIDTH - marginTop - marginBottom; }
|
||||
|
||||
int EpdRenderer::getSpaceWidth() const { return regularFontRenderer->fontFamily->getGlyph(' ', REGULAR)->advanceX; }
|
||||
|
||||
int EpdRenderer::getLineHeight() const {
|
||||
return regularFontRenderer->fontFamily->getData(REGULAR)->advanceY * lineCompression;
|
||||
}
|
||||
|
||||
void EpdRenderer::swapBuffers() const { einkDisplay.swapBuffers(); }
|
||||
|
||||
void EpdRenderer::copyGrayscaleLsbBuffers() const { einkDisplay.copyGrayscaleLsbBuffers(einkDisplay.getFrameBuffer()); }
|
||||
|
||||
void EpdRenderer::copyGrayscaleMsbBuffers() const { einkDisplay.copyGrayscaleMsbBuffers(einkDisplay.getFrameBuffer()); }
|
||||
|
||||
void EpdRenderer::displayGrayBuffer() const { einkDisplay.displayGrayBuffer(); }
|
||||
|
||||
@@ -1,52 +1,58 @@
|
||||
#pragma once
|
||||
|
||||
#include <GxEPD2_BW.h>
|
||||
#include <EInkDisplay.h>
|
||||
|
||||
#include <EpdFontRenderer.hpp>
|
||||
|
||||
#define XteinkDisplay GxEPD2_BW<GxEPD2_426_GDEQ0426T82, GxEPD2_426_GDEQ0426T82::HEIGHT>
|
||||
|
||||
class EpdRenderer {
|
||||
XteinkDisplay& display;
|
||||
EpdFontRenderer<XteinkDisplay>* regularFontRenderer;
|
||||
EpdFontRenderer<XteinkDisplay>* smallFontRenderer;
|
||||
EpdFontRenderer<XteinkDisplay>* uiFontRenderer;
|
||||
EInkDisplay& einkDisplay;
|
||||
EpdFontRenderer<EInkDisplay>* regularFontRenderer;
|
||||
EpdFontRenderer<EInkDisplay>* smallFontRenderer;
|
||||
EpdFontRenderer<EInkDisplay>* uiFontRenderer;
|
||||
int marginTop;
|
||||
int marginBottom;
|
||||
int marginLeft;
|
||||
int marginRight;
|
||||
EpdFontRendererMode fontRendererMode;
|
||||
float lineCompression;
|
||||
|
||||
public:
|
||||
explicit EpdRenderer(XteinkDisplay& display);
|
||||
explicit EpdRenderer(EInkDisplay& einkDisplay);
|
||||
~EpdRenderer();
|
||||
void drawPixel(int x, int y, bool state = true) const;
|
||||
int getTextWidth(const char* text, EpdFontStyle style = REGULAR) const;
|
||||
int getUiTextWidth(const char* text, EpdFontStyle style = REGULAR) const;
|
||||
int getSmallTextWidth(const char* text, EpdFontStyle style = REGULAR) const;
|
||||
void drawText(int x, int y, const char* text, uint16_t color = 1, EpdFontStyle style = REGULAR) const;
|
||||
void drawUiText(int x, int y, const char* text, uint16_t color = 1, EpdFontStyle style = REGULAR) const;
|
||||
void drawSmallText(int x, int y, const char* text, uint16_t color = 1, EpdFontStyle style = REGULAR) const;
|
||||
void drawText(int x, int y, const char* text, bool state = true, EpdFontStyle style = REGULAR) const;
|
||||
void drawUiText(int x, int y, const char* text, bool state = true, EpdFontStyle style = REGULAR) const;
|
||||
void drawSmallText(int x, int y, const char* text, bool state = true, EpdFontStyle style = REGULAR) const;
|
||||
void drawTextBox(int x, int y, const std::string& text, int width, int height, EpdFontStyle style = REGULAR) const;
|
||||
void drawLine(int x1, int y1, int x2, int y2, uint16_t color = 1) const;
|
||||
void drawRect(int x, int y, int width, int height, uint16_t color = 1) const;
|
||||
void fillRect(int x, int y, int width, int height, uint16_t color = 1) const;
|
||||
void drawCircle(int x, int y, int radius, uint16_t color = 1) const;
|
||||
void fillCircle(int x, int y, int radius, uint16_t color = 1) const;
|
||||
void drawImage(const uint8_t bitmap[], int x, int y, int width, int height, bool invert = false,
|
||||
bool mirrorY = false) const;
|
||||
void drawImageNoMargin(const uint8_t bitmap[], int x, int y, int width, int height, bool invert = false,
|
||||
bool mirrorY = false) const;
|
||||
void clearScreen(bool black = false) const;
|
||||
void flushDisplay(bool partialUpdate = true) const;
|
||||
void flushArea(int x, int y, int width, int height) 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 drawImageNoMargin(const uint8_t bitmap[], int x, int y, int width, int height) const;
|
||||
|
||||
void swapBuffers() const;
|
||||
void copyGrayscaleLsbBuffers() const;
|
||||
void copyGrayscaleMsbBuffers() const;
|
||||
void displayGrayBuffer() const;
|
||||
void clearScreen(uint8_t color = 0xFF) const;
|
||||
|
||||
void invertScreen() const;
|
||||
|
||||
void flushDisplay(EInkDisplay::RefreshMode refreshMode = EInkDisplay::FAST_REFRESH) const;
|
||||
// void flushArea(int x, int y, int width, int height) const;
|
||||
|
||||
int getPageWidth() const;
|
||||
int getPageHeight() const;
|
||||
int getSpaceWidth() const;
|
||||
int getLineHeight() const;
|
||||
|
||||
// set margins
|
||||
void setMarginTop(const int newMarginTop) { this->marginTop = newMarginTop; }
|
||||
void setMarginBottom(const int newMarginBottom) { this->marginBottom = newMarginBottom; }
|
||||
void setMarginLeft(const int newMarginLeft) { this->marginLeft = newMarginLeft; }
|
||||
void setMarginRight(const int newMarginRight) { this->marginRight = newMarginRight; }
|
||||
void setFontRendererMode(const EpdFontRendererMode mode) { this->fontRendererMode = mode; }
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user