Greyscale UI methods

This commit is contained in:
CaptainFrito 2025-12-27 16:10:39 -08:00
parent 06ce25f0cd
commit 5ecc277824
5 changed files with 64 additions and 7 deletions

View File

@ -250,6 +250,55 @@ void GfxRenderer::fillRect(const int x, const int y, const int width, const int
} }
} }
// Use Bayer matrix 4x4 dithering to fill the rectangle with a grey level - 0 white to 15 black
void GfxRenderer::fillRectGrey(const int x, const int y, const int width, const int height, const int greyLevel) const {
static constexpr uint8_t bayer4x4[4][4] = {
{0, 8, 2, 10},
{12, 4, 14, 6},
{3, 11, 1, 9},
{15, 7, 13, 5},
};
static constexpr int matrixSize = 4;
static constexpr int matrixLevels = matrixSize * matrixSize;
const int normalizedGrey = (greyLevel * 255) / (matrixLevels - 1);
const int clampedGrey = std::max(0, std::min(normalizedGrey, 255));
const int threshold = (clampedGrey * (matrixLevels + 1)) / 256;
for (int dy = 0; dy < height; ++dy) {
const int screenY = y + dy;
const int matrixY = screenY & (matrixSize - 1);
for (int dx = 0; dx < width; ++dx) {
const int screenX = x + dx;
const int matrixX = screenX & (matrixSize - 1);
const uint8_t patternValue = bayer4x4[matrixY][matrixX];
const bool black = patternValue < threshold;
drawPixel(screenX, screenY, black);
}
}
}
// Color -1 white, 0 clear, 1 black
void GfxRenderer::fillArc(const int maxRadius, const int cx, const int cy, const int xDir, const int yDir, const int insideColor, const int outsideColor) const {
const int radiusSq = maxRadius * maxRadius;
for (int dy = 0; dy <= maxRadius; ++dy) {
for (int dx = 0; dx <= maxRadius; ++dx) {
const int distSq = dx * dx + dy * dy;
const int px = cx + xDir * dx;
const int py = cy + yDir * dy;
if (distSq > radiusSq) {
if (outsideColor != 0) {
drawPixel(px, py, outsideColor == 1);
}
} else {
if (insideColor != 0) {
drawPixel(px, py, insideColor == 1);
}
}
}
}
};
void GfxRenderer::drawImage(const uint8_t bitmap[], const int x, const int y, const int width, const int height) const { void GfxRenderer::drawImage(const uint8_t bitmap[], const int x, const int y, const int width, const int height) const {
// Flip X and Y for portrait mode // Flip X and Y for portrait mode
einkDisplay.drawImage(bitmap, y, x, height, width); einkDisplay.drawImage(bitmap, y, x, height, width);

View File

@ -51,6 +51,8 @@ class GfxRenderer {
void drawArc(int maxRadius, int cx, int cy, int xDir, int yDir, int lineWidth, bool state) const; void drawArc(int maxRadius, int cx, int cy, int xDir, int yDir, int lineWidth, bool state) const;
void drawRoundedRect(int x, int y, int width, int height, int lineWidth, int cornerRadius, bool state) const; void drawRoundedRect(int x, int y, int width, int height, int lineWidth, int cornerRadius, bool state) const;
void fillRect(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 fillRectGrey(int x, int y, int width, int height, int greyLevel) const;
void fillArc(int maxRadius, int cx, int cy, int xDir, int yDir, int insideColor, int outsideColor) const;
void drawImage(const uint8_t bitmap[], int x, int y, int width, int height) const; void drawImage(const uint8_t bitmap[], int x, int y, int width, int height) const;
void drawIcon(const uint8_t bitmap[], int x, int y, int width, int height) const; void drawIcon(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) const; void drawBitmap(const Bitmap& bitmap, int x, int y, int maxWidth, int maxHeight) const;

View File

@ -17,7 +17,7 @@ constexpr int TILE_PADDING = 5;
constexpr int THUMB_W = 90; constexpr int THUMB_W = 90;
constexpr int THUMB_H = 120; constexpr int THUMB_H = 120;
constexpr int TILE_TEXT_H = 60; constexpr int TILE_TEXT_H = 60;
constexpr int gridLeftOffset = 45; constexpr int gridLeftOffset = 37;
constexpr int gridTopOffset = 125; constexpr int gridTopOffset = 125;
} // namespace } // namespace
@ -61,7 +61,7 @@ void GridBrowserActivity::loadFiles() {
std::string ext = filename.substr(dot); std::string ext = filename.substr(dot);
basename = filename.substr(0, dot); basename = filename.substr(0, dot);
// lowercase ext for case-insensitive compare // lowercase ext for case-insensitive compare
for (char &c : ext) c = (char)tolower(c); std::transform(ext.begin(), ext.end(), ext.begin(), [](unsigned char c){ return std::tolower(c); });
if (ext == ".epub") { if (ext == ".epub") {
type = F_EPUB; type = F_EPUB;
} else if (ext == ".thumb.bmp") { } else if (ext == ".thumb.bmp") {
@ -136,7 +136,7 @@ void GridBrowserActivity::loop() {
} }
} else if (inputManager.wasPressed(InputManager::BTN_BACK)) { } else if (inputManager.wasPressed(InputManager::BTN_BACK)) {
if (basepath != "/") { if (basepath != "/") {
basepath = basepath.substr(0, basepath.rfind('/')); basepath.resize(basepath.rfind('/'));
if (basepath.empty()) basepath = "/"; if (basepath.empty()) basepath = "/";
loadFiles(); loadFiles();
renderRequired = true; renderRequired = true;
@ -197,9 +197,9 @@ void GridBrowserActivity::render(bool clear) const {
auto folderName = basepath == "/" ? "SD card" : basepath.substr(basepath.rfind('/') + 1).c_str(); auto folderName = basepath == "/" ? "SD card" : basepath.substr(basepath.rfind('/') + 1).c_str();
drawFullscreenWindowFrame(renderer, folderName); drawFullscreenWindowFrame(renderer, folderName);
} }
bool hasGeyscaleBitmaps = false;
if (!files.empty()) { if (!files.empty()) {
bool hasGeyscaleBitmaps = false;
for (int pass = 0; pass < 3; pass++) { for (int pass = 0; pass < 3; pass++) {
if (pass > 0) { if (pass > 0) {
renderer.clearScreen(0x00); renderer.clearScreen(0x00);
@ -255,7 +255,7 @@ void GridBrowserActivity::render(bool clear) const {
} }
} else if (pass == 1) { } else if (pass == 1) {
renderer.copyGrayscaleLsbBuffers(); renderer.copyGrayscaleLsbBuffers();
} else if (pass == 2) { } else {
renderer.copyGrayscaleMsbBuffers(); renderer.copyGrayscaleMsbBuffers();
renderer.displayGrayBuffer(); renderer.displayGrayBuffer();
renderer.setRenderMode(GfxRenderer::BW); renderer.setRenderMode(GfxRenderer::BW);

View File

@ -30,7 +30,7 @@ class GridBrowserActivity final : public Activity {
std::vector<FileInfo> files; std::vector<FileInfo> files;
int selectorIndex = 0; int selectorIndex = 0;
int previousSelectorIndex = -1; int previousSelectorIndex = -1;
int page; int page = 0;
bool updateRequired = false; bool updateRequired = false;
bool renderRequired = false; bool renderRequired = false;
const std::function<void(const std::string&)> onSelect; const std::function<void(const std::string&)> onSelect;

View File

@ -14,6 +14,13 @@ constexpr int batteryHeight = 10;
void drawWindowFrame(GfxRenderer& renderer, int xMargin, int y, int height, bool hasShadow, const char* title) { void drawWindowFrame(GfxRenderer& renderer, int xMargin, int y, int height, bool hasShadow, const char* title) {
const int windowWidth = GfxRenderer::getScreenWidth() - 2 * xMargin; const int windowWidth = GfxRenderer::getScreenWidth() - 2 * xMargin;
if (title) { // Header background
renderer.fillRectGrey(xMargin, y, windowWidth, windowHeaderHeight, 5);
renderer.fillArc(windowCornerRadius, xMargin + windowCornerRadius, y + windowCornerRadius, -1, -1, 0, -1); // TL
renderer.fillArc(windowCornerRadius, windowWidth + xMargin - windowCornerRadius, y + windowCornerRadius, 1, -1, 0, -1); // TR
}
renderer.drawRoundedRect(xMargin, y, windowWidth, height, windowBorderWidth, windowCornerRadius, true); renderer.drawRoundedRect(xMargin, y, windowWidth, height, windowBorderWidth, windowCornerRadius, true);
if (hasShadow) { if (hasShadow) {
@ -43,7 +50,6 @@ void drawStatusBar(GfxRenderer& renderer) {
// Left aligned battery icon and percentage // Left aligned battery icon and percentage
const uint16_t percentage = battery.readPercentage(); const uint16_t percentage = battery.readPercentage();
const auto percentageText = std::to_string(percentage) + "%"; const auto percentageText = std::to_string(percentage) + "%";
const auto percentageTextWidth = renderer.getTextWidth(SMALL_FONT_ID, percentageText.c_str());
renderer.drawText(SMALL_FONT_ID, fullscreenWindowMargin + batteryWidth + 5, textY, percentageText.c_str()); renderer.drawText(SMALL_FONT_ID, fullscreenWindowMargin + batteryWidth + 5, textY, percentageText.c_str());
// 1 column on left, 2 columns on right, 5 columns of battery body // 1 column on left, 2 columns on right, 5 columns of battery body