fix(xtc): address PR review feedback for grayscale rendering

- Use FsHelpers::openFileForRead for consistency (XtcParser.cpp)
- Simplify file extension checking logic (FileSelectionActivity.cpp)
- Fix grayscale rendering bugs (XtcReaderActivity.cpp):
  - Use drawPixel(x, y, false) for LSB/MSB passes (LUT: 0=apply effect)
  - Add cleanupGrayscaleWithFrameBuffer() after re-rendering BW
  - Use conditional HALF_REFRESH based on pagesUntilFullRefresh
  - Decrement refresh counter instead of resetting
- Add cleanupGrayscaleWithFrameBuffer() helper (GfxRenderer)
This commit is contained in:
Eunchurn Park 2025-12-28 20:57:10 +09:00
parent 30c50ef45b
commit 76a1c30a87
No known key found for this signature in database
GPG Key ID: 29D94D9C697E3F92
5 changed files with 33 additions and 22 deletions

View File

@ -369,6 +369,17 @@ void GfxRenderer::restoreBwBuffer() {
Serial.printf("[%lu] [GFX] Restored and freed BW buffer chunks\n", millis()); Serial.printf("[%lu] [GFX] Restored and freed BW buffer chunks\n", millis());
} }
/**
* Cleanup grayscale buffers using the current frame buffer.
* Use this when BW buffer was re-rendered instead of stored/restored.
*/
void GfxRenderer::cleanupGrayscaleWithFrameBuffer() const {
uint8_t* frameBuffer = einkDisplay.getFrameBuffer();
if (frameBuffer) {
einkDisplay.cleanupGrayscaleBuffers(frameBuffer);
}
}
void GfxRenderer::renderChar(const EpdFontFamily& fontFamily, const uint32_t cp, int* x, const int* y, void GfxRenderer::renderChar(const EpdFontFamily& fontFamily, const uint32_t cp, int* x, const int* y,
const bool pixelState, const EpdFontStyle style) const { const bool pixelState, const EpdFontStyle style) const {
const EpdGlyph* glyph = fontFamily.getGlyph(cp, style); const EpdGlyph* glyph = fontFamily.getGlyph(cp, style);

View File

@ -67,6 +67,7 @@ class GfxRenderer {
void displayGrayBuffer() const; void displayGrayBuffer() const;
bool storeBwBuffer(); // Returns true if buffer was stored successfully bool storeBwBuffer(); // Returns true if buffer was stored successfully
void restoreBwBuffer(); void restoreBwBuffer();
void cleanupGrayscaleWithFrameBuffer() const;
// Low level functions // Low level functions
uint8_t* getFrameBuffer() const; uint8_t* getFrameBuffer() const;

View File

@ -7,6 +7,7 @@
#include "XtcParser.h" #include "XtcParser.h"
#include <FsHelpers.h>
#include <HardwareSerial.h> #include <HardwareSerial.h>
#include <cstring> #include <cstring>
@ -31,9 +32,7 @@ XtcError XtcParser::open(const char* filepath) {
} }
// Open file // Open file
m_file = SD.open(filepath, FILE_READ); if (!FsHelpers::openFileForRead("XTC", filepath, m_file)) {
if (!m_file) {
Serial.printf("[%lu] [XTC] Failed to open file: %s\n", millis(), filepath);
m_lastError = XtcError::FILE_NOT_FOUND; m_lastError = XtcError::FILE_NOT_FOUND;
return m_lastError; return m_lastError;
} }

View File

@ -40,18 +40,11 @@ void FileSelectionActivity::loadFiles() {
if (file.isDirectory()) { if (file.isDirectory()) {
files.emplace_back(filename + "/"); files.emplace_back(filename + "/");
} else if (filename.length() >= 5 && filename.substr(filename.length() - 5) == ".epub") { } else {
files.emplace_back(filename); std::string ext4 = filename.length() >= 4 ? filename.substr(filename.length() - 4) : "";
} else if (filename.length() >= 4) { std::string ext5 = filename.length() >= 5 ? filename.substr(filename.length() - 5) : "";
// Check for XTC format extensions (.xtc, .xtch) if (ext5 == ".epub" || ext5 == ".xtch" || ext4 == ".xtc") {
std::string ext4 = filename.substr(filename.length() - 4);
if (ext4 == ".xtc") {
files.emplace_back(filename); files.emplace_back(filename);
} else if (filename.length() >= 5) {
std::string ext5 = filename.substr(filename.length() - 5);
if (ext5 == ".xtch") {
files.emplace_back(filename);
}
} }
} }
file.close(); file.close();

View File

@ -236,29 +236,33 @@ void XtcReaderActivity::renderPage() {
} }
} }
// Display BW first with half refresh (clean base for grayscale overlay) // Display BW with conditional refresh based on pagesUntilFullRefresh
renderer.displayBuffer(EInkDisplay::HALF_REFRESH); if (pagesUntilFullRefresh <= 1) {
renderer.displayBuffer(EInkDisplay::HALF_REFRESH);
} else {
renderer.displayBuffer();
}
// Pass 2: LSB buffer - mark DARK gray only (XTH value 1) // Pass 2: LSB buffer - mark DARK gray only (XTH value 1)
// README: "mark the **dark** grays pixels with `1`" // In LUT: 0 bit = apply gray effect, 1 bit = untouched
renderer.clearScreen(0x00); renderer.clearScreen(0x00);
for (uint16_t y = 0; y < pageHeight; y++) { for (uint16_t y = 0; y < pageHeight; y++) {
for (uint16_t x = 0; x < pageWidth; x++) { for (uint16_t x = 0; x < pageWidth; x++) {
if (getPixelValue(x, y) == 1) { // Dark grey only if (getPixelValue(x, y) == 1) { // Dark grey only
renderer.drawPixel(x, y, true); renderer.drawPixel(x, y, false);
} }
} }
} }
renderer.copyGrayscaleLsbBuffers(); renderer.copyGrayscaleLsbBuffers();
// Pass 3: MSB buffer - mark LIGHT AND DARK gray (XTH value 1 or 2) // Pass 3: MSB buffer - mark LIGHT AND DARK gray (XTH value 1 or 2)
// README: "mark the **light and dark** grays pixels with `1`" // In LUT: 0 bit = apply gray effect, 1 bit = untouched
renderer.clearScreen(0x00); renderer.clearScreen(0x00);
for (uint16_t y = 0; y < pageHeight; y++) { for (uint16_t y = 0; y < pageHeight; y++) {
for (uint16_t x = 0; x < pageWidth; x++) { for (uint16_t x = 0; x < pageWidth; x++) {
const uint8_t pv = getPixelValue(x, y); const uint8_t pv = getPixelValue(x, y);
if (pv == 1 || pv == 2) { // Dark grey or Light grey if (pv == 1 || pv == 2) { // Dark grey or Light grey
renderer.drawPixel(x, y, true); renderer.drawPixel(x, y, false);
} }
} }
} }
@ -277,8 +281,11 @@ void XtcReaderActivity::renderPage() {
} }
} }
// Reset refresh counter (grayscale display is a full refresh) // Cleanup grayscale buffers with current frame buffer
pagesUntilFullRefresh = pagesPerRefresh; renderer.cleanupGrayscaleWithFrameBuffer();
// Decrement refresh counter
pagesUntilFullRefresh--;
free(pageBuffer); free(pageBuffer);