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:
@@ -6,22 +6,27 @@
|
||||
inline int min(const int a, const int b) { return a < b ? a : b; }
|
||||
inline int max(const int a, const int b) { return a > b ? a : b; }
|
||||
|
||||
enum EpdFontRendererMode { BW, GRAYSCALE_LSB, GRAYSCALE_MSB };
|
||||
|
||||
template <typename Renderable>
|
||||
class EpdFontRenderer {
|
||||
Renderable& renderer;
|
||||
void renderChar(uint32_t cp, int* x, const int* y, uint16_t color, EpdFontStyle style = REGULAR);
|
||||
void renderChar(uint32_t cp, int* x, const int* y, bool pixelState, EpdFontStyle style = REGULAR,
|
||||
EpdFontRendererMode mode = BW);
|
||||
|
||||
public:
|
||||
const EpdFontFamily* fontFamily;
|
||||
explicit EpdFontRenderer(const EpdFontFamily* fontFamily, Renderable& renderer)
|
||||
: fontFamily(fontFamily), renderer(renderer) {}
|
||||
~EpdFontRenderer() = default;
|
||||
void renderString(const char* string, int* x, int* y, uint16_t color, EpdFontStyle style = REGULAR);
|
||||
void renderString(const char* string, int* x, int* y, bool pixelState = true, EpdFontStyle style = REGULAR,
|
||||
EpdFontRendererMode mode = BW);
|
||||
void drawPixel(int x, int y, bool pixelState);
|
||||
};
|
||||
|
||||
template <typename Renderable>
|
||||
void EpdFontRenderer<Renderable>::renderString(const char* string, int* x, int* y, const uint16_t color,
|
||||
const EpdFontStyle style) {
|
||||
void EpdFontRenderer<Renderable>::renderString(const char* string, int* x, int* y, const bool pixelState,
|
||||
const EpdFontStyle style, const EpdFontRendererMode mode) {
|
||||
// cannot draw a NULL / empty string
|
||||
if (string == nullptr || *string == '\0') {
|
||||
return;
|
||||
@@ -34,15 +39,49 @@ void EpdFontRenderer<Renderable>::renderString(const char* string, int* x, int*
|
||||
|
||||
uint32_t cp;
|
||||
while ((cp = utf8NextCodepoint(reinterpret_cast<const uint8_t**>(&string)))) {
|
||||
renderChar(cp, x, y, color, style);
|
||||
renderChar(cp, x, y, pixelState, style, mode);
|
||||
}
|
||||
|
||||
*y += fontFamily->getData(style)->advanceY;
|
||||
}
|
||||
|
||||
// TODO: Consolidate this with EpdRenderer implementation
|
||||
template <typename Renderable>
|
||||
void EpdFontRenderer<Renderable>::renderChar(const uint32_t cp, int* x, const int* y, uint16_t color,
|
||||
const EpdFontStyle style) {
|
||||
void EpdFontRenderer<Renderable>::drawPixel(const int x, const int y, const bool pixelState) {
|
||||
uint8_t* frameBuffer = renderer.getFrameBuffer();
|
||||
|
||||
// Early return if no framebuffer is set
|
||||
if (!frameBuffer) {
|
||||
Serial.printf("!!No framebuffer\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// Bounds checking (portrait: 480x800)
|
||||
if (x < 0 || x >= EInkDisplay::DISPLAY_HEIGHT || y < 0 || y >= EInkDisplay::DISPLAY_WIDTH) {
|
||||
Serial.printf("!!Outside range (%d, %d)\n", x, y);
|
||||
return;
|
||||
}
|
||||
|
||||
// Rotate coordinates: portrait (480x800) -> landscape (800x480)
|
||||
// Rotation: 90 degrees clockwise
|
||||
const int16_t rotatedX = y;
|
||||
const int16_t rotatedY = EInkDisplay::DISPLAY_HEIGHT - 1 - x;
|
||||
|
||||
// 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
|
||||
|
||||
// Set or clear the bit
|
||||
if (pixelState) {
|
||||
frameBuffer[byteIndex] &= ~(1 << bitPosition); // Clear bit
|
||||
} else {
|
||||
frameBuffer[byteIndex] |= (1 << bitPosition); // Set bit
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Renderable>
|
||||
void EpdFontRenderer<Renderable>::renderChar(const uint32_t cp, int* x, const int* y, const bool pixelState,
|
||||
const EpdFontStyle style, const EpdFontRendererMode mode) {
|
||||
const EpdGlyph* glyph = fontFamily->getGlyph(cp, style);
|
||||
if (!glyph) {
|
||||
// TODO: Replace with fallback glyph property?
|
||||
@@ -55,6 +94,7 @@ void EpdFontRenderer<Renderable>::renderChar(const uint32_t cp, int* x, const in
|
||||
return;
|
||||
}
|
||||
|
||||
const int is2Bit = fontFamily->getData(style)->is2Bit;
|
||||
const uint32_t offset = glyph->dataOffset;
|
||||
const uint8_t width = glyph->width;
|
||||
const uint8_t height = glyph->height;
|
||||
@@ -70,11 +110,26 @@ void EpdFontRenderer<Renderable>::renderChar(const uint32_t cp, int* x, const in
|
||||
const int pixelPosition = glyphY * width + glyphX;
|
||||
int screenX = *x + left + glyphX;
|
||||
|
||||
const uint8_t byte = bitmap[pixelPosition / 8];
|
||||
const uint8_t bit_index = 7 - (pixelPosition % 8);
|
||||
if (is2Bit) {
|
||||
const uint8_t byte = bitmap[pixelPosition / 4];
|
||||
const uint8_t bit_index = (3 - pixelPosition % 4) * 2;
|
||||
|
||||
if ((byte >> bit_index) & 1) {
|
||||
renderer.drawPixel(screenX, screenY, color);
|
||||
const uint8_t val = (byte >> bit_index) & 0x3;
|
||||
if (mode == BW && val > 0) {
|
||||
drawPixel(screenX, screenY, pixelState);
|
||||
} else if (mode == GRAYSCALE_MSB && val == 1) {
|
||||
// TODO: Not sure how this anti-aliasing goes on black backgrounds
|
||||
drawPixel(screenX, screenY, false);
|
||||
} else if (mode == GRAYSCALE_LSB && val == 2) {
|
||||
drawPixel(screenX, screenY, false);
|
||||
}
|
||||
} else {
|
||||
const uint8_t byte = bitmap[pixelPosition / 8];
|
||||
const uint8_t bit_index = 7 - (pixelPosition % 8);
|
||||
|
||||
if ((byte >> bit_index) & 1) {
|
||||
drawPixel(screenX, screenY, pixelState);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user