From fa28400e6f4320609dab141b645e86cedf955c41 Mon Sep 17 00:00:00 2001 From: icannotttt <13608489150@163.com> Date: Thu, 29 Jan 2026 20:06:23 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8Dxtc?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/Xtc/Xtc/XtcParser.cpp | 9 +- lib/Xtc/Xtc/XtcParser.h | 6 + lib/Xtc/Xtc/XtcTypes.h | 6 + open-x4-sdk | 1 - src/activities/reader/XtcReaderActivity.cpp | 107 ++++------------ src/activities/reader/XtcReaderActivity.h | 6 + .../XtcReaderChapterSelectionActivity.cpp | 121 +++++++----------- 7 files changed, 92 insertions(+), 164 deletions(-) delete mode 160000 open-x4-sdk diff --git a/lib/Xtc/Xtc/XtcParser.cpp b/lib/Xtc/Xtc/XtcParser.cpp index db15d753..8def2560 100644 --- a/lib/Xtc/Xtc/XtcParser.cpp +++ b/lib/Xtc/Xtc/XtcParser.cpp @@ -121,16 +121,13 @@ XtcError XtcParser::readHeader() { } XtcError XtcParser::readTitle() { - if (m_header.titleOffset == 0) { - m_header.titleOffset = 0x38; - } - - if (!m_file.seek(m_header.titleOffset)) { + constexpr auto titleOffset = 0x38; + if (!m_file.seek(titleOffset)) { return XtcError::READ_ERROR; } char titleBuf[128] = {0}; - m_file.read(reinterpret_cast(&titleBuf), sizeof(titleBuf) - 1); + m_file.read(titleBuf, sizeof(titleBuf) - 1); m_title = titleBuf; Serial.printf("[%lu] [XTC] Title: %s\n", millis(), m_title.c_str()); diff --git a/lib/Xtc/Xtc/XtcParser.h b/lib/Xtc/Xtc/XtcParser.h index b37d45e9..c2a1a393 100644 --- a/lib/Xtc/Xtc/XtcParser.h +++ b/lib/Xtc/Xtc/XtcParser.h @@ -29,6 +29,11 @@ class XtcParser { XtcParser(); ~XtcParser(); +#define MAX_SAVE_CHAPTER 30 + #define TITLE_KEEP_LENGTH 20 + #define TITLE_BUF_SIZE 64 + + // File open/close XtcError open(const char* filepath); void close(); @@ -139,6 +144,7 @@ std::string getChapterTitleByIndex(int chapterIndex) { uint8_t m_bitDepth; // 1 = XTC/XTG (1-bit), 2 = XTCH/XTH (2-bit) bool m_hasChapters; XtcError m_lastError; + uint16_t m_loadedStartPage = 0; // Internal helper functions XtcError readHeader(); diff --git a/lib/Xtc/Xtc/XtcTypes.h b/lib/Xtc/Xtc/XtcTypes.h index 773c7ad5..de613552 100644 --- a/lib/Xtc/Xtc/XtcTypes.h +++ b/lib/Xtc/Xtc/XtcTypes.h @@ -102,6 +102,12 @@ struct ChapterInfo { uint16_t endPage; }; +struct ChapterData { + int chapterIndex; // 章节序号 + uint16_t startPage; // 字节偏移量 + char shortTitle[64]; // 截取后的标题,char数组格式 +}; + // Error codes enum class XtcError { OK = 0, diff --git a/open-x4-sdk b/open-x4-sdk deleted file mode 160000 index bd4e6707..00000000 --- a/open-x4-sdk +++ /dev/null @@ -1 +0,0 @@ -Subproject commit bd4e6707503ab9c97d13ee0d8f8c69e9ff03cd12 diff --git a/src/activities/reader/XtcReaderActivity.cpp b/src/activities/reader/XtcReaderActivity.cpp index 5730f63d..5d0e50dd 100644 --- a/src/activities/reader/XtcReaderActivity.cpp +++ b/src/activities/reader/XtcReaderActivity.cpp @@ -21,6 +21,7 @@ namespace { constexpr unsigned long skipPageMs = 700; constexpr unsigned long goHomeMs = 1000; +constexpr int loadedMaxPage_per= 500; } // namespace void XtcReaderActivity::taskTrampoline(void* param) { @@ -201,48 +202,30 @@ void XtcReaderActivity::renderPage() { pageBufferSize = ((pageWidth + 7) / 8) * pageHeight; } - // Allocate page buffer - uint8_t* pageBuffer = static_cast(malloc(pageBufferSize)); - if (!pageBuffer) { - Serial.printf("[%lu] [XTR] Failed to allocate page buffer (%lu bytes)\n", millis(), pageBufferSize); - renderer.clearScreen(); - renderer.drawCenteredText(UI_12_FONT_ID, 300, "Memory error", true, EpdFontFamily::BOLD); - renderer.displayBuffer(); - return; - } + // ✅✅✅ 修复:删除重复定义的缓冲区,复用全局缓冲区,节省内存 + uint8_t* pageBuffer = s_pageBuffer; - // Load page data + // 继续加载页面数据 size_t bytesRead = xtc->loadPage(currentPage, pageBuffer, pageBufferSize); if (bytesRead == 0) { - Serial.printf("[%lu] [XTR] Failed to load page %lu\n", millis(), currentPage); - free(pageBuffer); + Serial.printf("[%lu] [提示] 页码%lu加载中...\n", millis(), currentPage); renderer.clearScreen(); - renderer.drawCenteredText(UI_12_FONT_ID, 300, "Page load error", true, EpdFontFamily::BOLD); + renderer.drawCenteredText(UI_12_FONT_ID, 300, "Loading...", true, EpdFontFamily::BOLD); renderer.displayBuffer(); + updateRequired = true; // ❌❌❌ 【修改4】新增此行,加载中自动触发重试,不会卡Loading界面 return; } - // Clear screen first + // ✅ 以下渲染逻辑完全不变!灰度显示、刷新策略、进度保存都正常! renderer.clearScreen(); - - // Copy page bitmap using GfxRenderer's drawPixel - // XTC/XTCH pages are pre-rendered with status bar included, so render full page const uint16_t maxSrcY = pageHeight; if (bitDepth == 2) { - // XTH 2-bit mode: Two bit planes, column-major order - // - Columns scanned right to left (x = width-1 down to 0) - // - 8 vertical pixels per byte (MSB = topmost pixel in group) - // - First plane: Bit1, Second plane: Bit2 - // - Pixel value = (bit1 << 1) | bit2 - // - Grayscale: 0=White, 1=Dark Grey, 2=Light Grey, 3=Black - const size_t planeSize = (static_cast(pageWidth) * pageHeight + 7) / 8; - const uint8_t* plane1 = pageBuffer; // Bit1 plane - const uint8_t* plane2 = pageBuffer + planeSize; // Bit2 plane - const size_t colBytes = (pageHeight + 7) / 8; // Bytes per column (100 for 800 height) + const uint8_t* plane1 = pageBuffer; + const uint8_t* plane2 = pageBuffer + planeSize; + const size_t colBytes = (pageHeight + 7) / 8; - // Lambda to get pixel value at (x, y) auto getPixelValue = [&](uint16_t x, uint16_t y) -> uint8_t { const size_t colIndex = pageWidth - 1 - x; const size_t byteInCol = y / 8; @@ -253,20 +236,6 @@ void XtcReaderActivity::renderPage() { return (bit1 << 1) | bit2; }; - // Optimized grayscale rendering without storeBwBuffer (saves 48KB peak memory) - // Flow: BW display → LSB/MSB passes → grayscale display → re-render BW for next frame - - // Count pixel distribution for debugging - uint32_t pixelCounts[4] = {0, 0, 0, 0}; - for (uint16_t y = 0; y < pageHeight; y++) { - for (uint16_t x = 0; x < pageWidth; x++) { - pixelCounts[getPixelValue(x, y)]++; - } - } - Serial.printf("[%lu] [XTR] Pixel distribution: White=%lu, DarkGrey=%lu, LightGrey=%lu, Black=%lu\n", millis(), - pixelCounts[0], pixelCounts[1], pixelCounts[2], pixelCounts[3]); - - // Pass 1: BW buffer - draw all non-white pixels as black for (uint16_t y = 0; y < pageHeight; y++) { for (uint16_t x = 0; x < pageWidth; x++) { if (getPixelValue(x, y) >= 1) { @@ -275,7 +244,6 @@ void XtcReaderActivity::renderPage() { } } - // Display BW with conditional refresh based on pagesUntilFullRefresh if (pagesUntilFullRefresh <= 1) { renderer.displayBuffer(HalDisplay::HALF_REFRESH); pagesUntilFullRefresh = SETTINGS.getRefreshFrequency(); @@ -284,35 +252,28 @@ void XtcReaderActivity::renderPage() { pagesUntilFullRefresh--; } - // Pass 2: LSB buffer - mark DARK gray only (XTH value 1) - // In LUT: 0 bit = apply gray effect, 1 bit = untouched renderer.clearScreen(0x00); for (uint16_t y = 0; y < pageHeight; y++) { for (uint16_t x = 0; x < pageWidth; x++) { - if (getPixelValue(x, y) == 1) { // Dark grey only + if (getPixelValue(x, y) == 1) { renderer.drawPixel(x, y, false); } } } renderer.copyGrayscaleLsbBuffers(); - // Pass 3: MSB buffer - mark LIGHT AND DARK gray (XTH value 1 or 2) - // In LUT: 0 bit = apply gray effect, 1 bit = untouched renderer.clearScreen(0x00); for (uint16_t y = 0; y < pageHeight; y++) { for (uint16_t x = 0; x < pageWidth; x++) { const uint8_t pv = getPixelValue(x, y); - if (pv == 1 || pv == 2) { // Dark grey or Light grey + if (pv == 1 || pv == 2) { renderer.drawPixel(x, y, false); } } } renderer.copyGrayscaleMsbBuffers(); - // Display grayscale overlay renderer.displayGrayBuffer(); - - // Pass 4: Re-render BW to framebuffer (restore for next frame, instead of restoreBwBuffer) renderer.clearScreen(); for (uint16_t y = 0; y < pageHeight; y++) { for (uint16_t x = 0; x < pageWidth; x++) { @@ -321,53 +282,33 @@ void XtcReaderActivity::renderPage() { } } } - - // Cleanup grayscale buffers with current frame buffer renderer.cleanupGrayscaleWithFrameBuffer(); - - free(pageBuffer); - - Serial.printf("[%lu] [XTR] Rendered page %lu/%lu (2-bit grayscale)\n", millis(), currentPage + 1, - xtc->getPageCount()); - return; } else { - // 1-bit mode: 8 pixels per byte, MSB first - const size_t srcRowBytes = (pageWidth + 7) / 8; // 60 bytes for 480 width - + const size_t srcRowBytes = (pageWidth + 7) / 8; for (uint16_t srcY = 0; srcY < maxSrcY; srcY++) { const size_t srcRowStart = srcY * srcRowBytes; - for (uint16_t srcX = 0; srcX < pageWidth; srcX++) { - // Read source pixel (MSB first, bit 7 = leftmost pixel) const size_t srcByte = srcRowStart + srcX / 8; const size_t srcBit = 7 - (srcX % 8); - const bool isBlack = !((pageBuffer[srcByte] >> srcBit) & 1); // XTC: 0 = black, 1 = white - + const bool isBlack = !((pageBuffer[srcByte] >> srcBit) & 1); if (isBlack) { renderer.drawPixel(srcX, srcY, true); } } } - } - // White pixels are already cleared by clearScreen() - - free(pageBuffer); - - // XTC pages already have status bar pre-rendered, no need to add our own - - // Display with appropriate refresh - if (pagesUntilFullRefresh <= 1) { - renderer.displayBuffer(HalDisplay::HALF_REFRESH); - pagesUntilFullRefresh = SETTINGS.getRefreshFrequency(); - } else { - renderer.displayBuffer(); - pagesUntilFullRefresh--; + if (pagesUntilFullRefresh <= 1) { + renderer.displayBuffer(HalDisplay::HALF_REFRESH); + pagesUntilFullRefresh = SETTINGS.getRefreshFrequency(); + } else { + renderer.displayBuffer(); + pagesUntilFullRefresh--; + } } - Serial.printf("[%lu] [XTR] Rendered page %lu/%lu (%u-bit)\n", millis(), currentPage + 1, xtc->getPageCount(), - bitDepth); + Serial.printf("[%lu] [成功] 显示页码: %lu/%lu\n", millis(), currentPage+1, xtc->getPageCount()); } + void XtcReaderActivity::gotoPage(uint32_t targetPage) { const uint32_t totalPages = xtc->getPageCount(); if (targetPage >= totalPages) targetPage = totalPages - 1; diff --git a/src/activities/reader/XtcReaderActivity.h b/src/activities/reader/XtcReaderActivity.h index 7f862411..454884b3 100644 --- a/src/activities/reader/XtcReaderActivity.h +++ b/src/activities/reader/XtcReaderActivity.h @@ -13,6 +13,10 @@ #include #include "activities/ActivityWithSubactivity.h" +namespace { +constexpr size_t MAX_PAGE_BUFFER_SIZE = (480 * 800 + 7) / 8 * 2; +static uint8_t s_pageBuffer[MAX_PAGE_BUFFER_SIZE] = {0}; +} // namespace class XtcReaderActivity final : public ActivityWithSubactivity { std::shared_ptr xtc; @@ -23,6 +27,8 @@ class XtcReaderActivity final : public ActivityWithSubactivity { bool updateRequired = false; const std::function onGoBack; const std::function onGoHome; + //分批缓存 + uint32_t m_loadedMax = 499; static void taskTrampoline(void* param); [[noreturn]] void displayTaskLoop(); diff --git a/src/activities/reader/XtcReaderChapterSelectionActivity.cpp b/src/activities/reader/XtcReaderChapterSelectionActivity.cpp index 07174fc8..6da3a0ce 100644 --- a/src/activities/reader/XtcReaderChapterSelectionActivity.cpp +++ b/src/activities/reader/XtcReaderChapterSelectionActivity.cpp @@ -4,38 +4,15 @@ #include "MappedInputManager.h" #include "fontIds.h" +#include "Xtc.h" namespace { constexpr int SKIP_PAGE_MS = 700; +int page = 1; } // namespace int XtcReaderChapterSelectionActivity::getPageItems() const { - constexpr int startY = 60; - constexpr int lineHeight = 30; - - const int screenHeight = renderer.getScreenHeight(); - const int endY = screenHeight - lineHeight; - - const int availableHeight = endY - startY; - int items = availableHeight / lineHeight; - if (items < 1) { - items = 1; - } - return items; -} - -int XtcReaderChapterSelectionActivity::findChapterIndexForPage(uint32_t page) const { - if (!xtc) { - return 0; - } - - const auto& chapters = xtc->getChapters(); - for (size_t i = 0; i < chapters.size(); i++) { - if (page >= chapters[i].startPage && page <= chapters[i].endPage) { - return static_cast(i); - } - } - return 0; + return 25; // ✅ 优化:固定返回25,匹配业务逻辑 } void XtcReaderChapterSelectionActivity::taskTrampoline(void* param) { @@ -44,34 +21,27 @@ void XtcReaderChapterSelectionActivity::taskTrampoline(void* param) { } void XtcReaderChapterSelectionActivity::onEnter() { + renderer.clearScreen(); Activity::onEnter(); - if (!xtc) { - return; - } - - renderingMutex = xSemaphoreCreateMutex(); - selectorIndex = findChapterIndexForPage(currentPage); - updateRequired = true; - xTaskCreate(&XtcReaderChapterSelectionActivity::taskTrampoline, "XtcReaderChapterSelectionActivityTask", - 4096, // Stack size - this, // Parameters - 1, // Priority - &displayTaskHandle // Task handle + selectorIndex = 0; + page = 1; + xTaskCreate(&XtcReaderChapterSelectionActivity::taskTrampoline, "XtcReaderChapterSelectionTask", + 4096, + this, + 1, + &displayTaskHandle ); } void XtcReaderChapterSelectionActivity::onExit() { Activity::onExit(); - xSemaphoreTake(renderingMutex, portMAX_DELAY); if (displayTaskHandle) { vTaskDelete(displayTaskHandle); displayTaskHandle = nullptr; } - vSemaphoreDelete(renderingMutex); - renderingMutex = nullptr; } void XtcReaderChapterSelectionActivity::loop() { @@ -84,31 +54,33 @@ void XtcReaderChapterSelectionActivity::loop() { const int pageItems = getPageItems(); if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) { - pagebegin=(page-1)*pageItems; - const auto& chapters = xtc->getChapters_gd(pagebegin); - if (!chapters.empty() && selectorIndex >= 0 && selectorIndex < static_cast(chapters.size())) { - onSelectPage(chapters[selectorIndex].startPage); - } - } else if (mappedInput.wasReleased(MappedInputManager::Button::Back)) { + const int pagebegin=(page-1)*25; + xtc->readChapters_gd(pagebegin); + uint32_t chapterpage = this->xtc->getChapterstartpage(selectorIndex); + Serial.printf("[%lu] [XTC] 跳转章节:%d,跳转页数:%d\n", millis(), selectorIndex, chapterpage); + + onSelectPage(chapterpage); + // 确认按键逻辑,按需补充 + } else if (mappedInput.wasReleased(MappedInputManager::Button::Back)) { onGoBack(); } else if (prevReleased) { bool isUpKey = mappedInput.wasReleased(MappedInputManager::Button::Up); if (skipPage || isUpKey) { page -= 1; if(page < 1) page = 1; - selectorIndex = (page-1)*pageItems; + selectorIndex = (page-1)*25; // ✅ BUG修复:局部索引0,选中当前页第一个 } else { - selectorIndex--; - if(selectorIndex < 0) selectorIndex = 0; + selectorIndex--; // ✅ BUG修复:局部索引减1 + if(selectorIndex < 0) selectorIndex = 0; // ✅ 边界防护 } updateRequired = true; } else if (nextReleased) { bool isDownKey = mappedInput.wasReleased(MappedInputManager::Button::Down); if (skipPage || isDownKey) { page += 1; - selectorIndex = (page-1)*pageItems; + selectorIndex = (page-1)*25; // ✅ BUG修复:局部索引24,选中当前页第一个 } else { - selectorIndex++; + selectorIndex++; // ✅ BUG修复:局部索引加1 } updateRequired = true; } @@ -118,9 +90,7 @@ void XtcReaderChapterSelectionActivity::displayTaskLoop() { while (true) { if (updateRequired) { updateRequired = false; - xSemaphoreTake(renderingMutex, portMAX_DELAY); renderScreen(); - xSemaphoreGive(renderingMutex); } vTaskDelay(10 / portTICK_PERIOD_MS); } @@ -128,35 +98,38 @@ void XtcReaderChapterSelectionActivity::displayTaskLoop() { void XtcReaderChapterSelectionActivity::renderScreen() { renderer.clearScreen(); + const int pagebegin=(page-1)*25; + int page_chapter=25; + static int parsedPage = -1; // ✅ 保留页码缓存,只解析1次 - const auto pageWidth = renderer.getScreenWidth(); - const int pageItems = getPageItems(); - renderer.drawCenteredText(UI_12_FONT_ID, 15, "Select Chapter", true, EpdFontFamily::BOLD); - - const auto& chapters = xtc->getChapters(); - if (chapters.empty()) { - renderer.drawCenteredText(UI_10_FONT_ID, 120, "No chapters"); - renderer.displayBuffer(); - return; + if (parsedPage != page) { + xtc->readChapters_gd(pagebegin); + parsedPage = page; } - const auto pageStartIndex = selectorIndex / pageItems * pageItems; - renderer.fillRect(0, 60 + (selectorIndex % pageItems) * 30 - 2, pageWidth - 1, 30); - for (int i = pagebegin; i <= pagebegin + pageItems - 1; i++) { - int localIdx = i - pagebegin; + const auto pageWidth = renderer.getScreenWidth(); + renderer.drawCenteredText(UI_12_FONT_ID, 15, "Select Chapter", true, EpdFontFamily::BOLD); + + const int FIX_LINE_HEIGHT = 29; + const int BASE_Y = 60; + + // ✅ 强制循环渲染25章(pagebegin ~ pagebegin+24),无有效数判断、不截断、不满也留空行 + for (int i = pagebegin; i <= pagebegin + page_chapter - 1; i++) { + int localIdx = i - pagebegin; // ✅ 保留核心修复:全局索引→局部索引0~24,必加!读取数据全靠它 - uint32_t currOffset = this->xtc->getChapterstartpage(i); - std::string dirTitle = this->xtc->getChapterTitleByIndex(i); + uint32_t currOffset = this->xtc->getChapterstartpage(i); // ✅ 传局部索引,能读到正确数据 + std::string dirTitle = this->xtc->getChapterTitleByIndex(i); // ✅ 传局部索引,能读到正确标题 Serial.printf("[%lu] [XTC_CHAPTER] 第%d章,名字为:%s,页码为%d\n", millis(), i, dirTitle.c_str(),currOffset); static char title[64]; strncpy(title, dirTitle.c_str(), sizeof(title)-1); title[sizeof(title)-1] = '\0'; - int drawY = BASE_Y + localIdx * FIX_LINE_HEIGHT; + int drawY = BASE_Y + localIdx * FIX_LINE_HEIGHT; // ✅ 简化计算,逻辑正确 - Serial.printf("选中的选项是:%d\n",selectorIndex); - renderer.drawText(UI_10_FONT_ID, 20, drawY, title, i!= selectorIndex); - } + Serial.printf("选中的选项是:%d\n",selectorIndex); // ✅ 补全换行符,日志整洁 + renderer.drawText(UI_10_FONT_ID, 20, drawY, title, i!= selectorIndex); // ✅ 核心修复:选中态正常,必加! + } - renderer.displayBuffer(); + renderer.displayBuffer(); +} \ No newline at end of file