feat: context-aware screenshot filenames with book title (#1589)
## Summary I love the new screenshot feature but I can tell that soon I'm gonna have a folder full of screenshots and not know what's what. It would be great if the folder could self organize. When a screenshot is taken while reading, the filename would now include the book title, chapter (EPUB), page number, and progress percentage. Example: My-Great-Book_ch3_p5_42pct_12345.bmp * **What is the goal of this PR?** (e.g., Implements the new feature for file uploading.) * **What changes are included?** ## Additional Context * Non-reader screens keep the existing screenshot-<millis>.bmp naming. --- ### AI Usage While CrossPoint doesn't have restrictions on AI tools in contributing, please be transparent about their usage as it helps set the right context for reviewers. Did you use AI tools to help write this code? _**< YES >**_ I reviewed all the changes but I don't have any experience with this project so this is my best guess
This commit is contained in:
@@ -2,26 +2,88 @@
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <BitmapHelpers.h>
|
||||
#include <FsHelpers.h>
|
||||
#include <GfxRenderer.h>
|
||||
#include <HalStorage.h>
|
||||
#include <Logging.h>
|
||||
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
|
||||
#include "Bitmap.h" // Required for BmpHeader struct definition
|
||||
#include "activities/Activity.h"
|
||||
|
||||
void ScreenshotUtil::buildFilename(const ScreenshotInfo& info, char* buf, size_t bufSize) {
|
||||
const unsigned long ts = millis();
|
||||
|
||||
if (info.readerType == ScreenshotInfo::ReaderType::None || info.title[0] == '\0') {
|
||||
snprintf(buf, bufSize, "/screenshots/screenshot-%lu.bmp", ts);
|
||||
return;
|
||||
}
|
||||
|
||||
char sanitizedTitle[64];
|
||||
FsHelpers::sanitizePathComponentForFat32(info.title, sanitizedTitle, sizeof(sanitizedTitle));
|
||||
if (sanitizedTitle[0] == '\0') {
|
||||
snprintf(buf, bufSize, "/screenshots/screenshot-%lu.bmp", ts);
|
||||
return;
|
||||
}
|
||||
|
||||
int pct = info.progressPercent;
|
||||
if (pct < 0) pct = 0;
|
||||
if (pct > 100) pct = 100;
|
||||
|
||||
// Display spine index as 1-based for user-facing filenames
|
||||
const int chapterNum = info.spineIndex + 1;
|
||||
|
||||
if (info.readerType == ScreenshotInfo::ReaderType::Epub && info.spineIndex >= 0) {
|
||||
snprintf(buf, bufSize, "/screenshots/%s/%s_ch%d_p%d_%dpct_%lu.bmp", sanitizedTitle, sanitizedTitle, chapterNum,
|
||||
info.currentPage, pct, ts);
|
||||
} else {
|
||||
snprintf(buf, bufSize, "/screenshots/%s/%s_p%d_%dpct_%lu.bmp", sanitizedTitle, sanitizedTitle, info.currentPage,
|
||||
pct, ts);
|
||||
}
|
||||
|
||||
// Truncate title if total path exceeds FAT32 limit
|
||||
if (strlen(buf) > 255) {
|
||||
size_t titleLen = strlen(sanitizedTitle);
|
||||
size_t overhead = strlen(buf) - 2 * titleLen;
|
||||
if (overhead < 255) {
|
||||
size_t maxTitleLen = (255 - overhead) / 2;
|
||||
// Walk back to a valid UTF-8 boundary to avoid corrupting multibyte characters
|
||||
while (maxTitleLen > 0 && (sanitizedTitle[maxTitleLen] & 0xC0) == 0x80) {
|
||||
maxTitleLen--;
|
||||
}
|
||||
sanitizedTitle[maxTitleLen] = '\0';
|
||||
if (info.readerType == ScreenshotInfo::ReaderType::Epub && info.spineIndex >= 0) {
|
||||
snprintf(buf, bufSize, "/screenshots/%s/%s_ch%d_p%d_%dpct_%lu.bmp", sanitizedTitle, sanitizedTitle, chapterNum,
|
||||
info.currentPage, pct, ts);
|
||||
} else {
|
||||
snprintf(buf, bufSize, "/screenshots/%s/%s_p%d_%dpct_%lu.bmp", sanitizedTitle, sanitizedTitle, info.currentPage,
|
||||
pct, ts);
|
||||
}
|
||||
} else {
|
||||
snprintf(buf, bufSize, "/screenshots/screenshot-%lu.bmp", ts);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ScreenshotUtil::takeScreenshot(GfxRenderer& renderer) {
|
||||
const uint8_t* fb = renderer.getFrameBuffer();
|
||||
if (fb) {
|
||||
String filename_str = "/screenshots/screenshot-" + String(millis()) + ".bmp";
|
||||
if (ScreenshotUtil::saveFramebufferAsBmp(filename_str.c_str(), fb, renderer.getDisplayWidth(),
|
||||
renderer.getDisplayHeight())) {
|
||||
LOG_DBG("SCR", "Screenshot saved to %s", filename_str.c_str());
|
||||
} else {
|
||||
LOG_ERR("SCR", "Failed to save screenshot");
|
||||
}
|
||||
} else {
|
||||
if (!fb) {
|
||||
LOG_ERR("SCR", "Framebuffer not available");
|
||||
return;
|
||||
}
|
||||
|
||||
ScreenshotInfo info = activityManager.getScreenshotInfo();
|
||||
char filename[256];
|
||||
buildFilename(info, filename, sizeof(filename));
|
||||
|
||||
bool saved = saveFramebufferAsBmp(filename, fb, renderer.getDisplayWidth(), renderer.getDisplayHeight());
|
||||
if (saved) {
|
||||
LOG_DBG("SCR", "Screenshot saved to %s", filename);
|
||||
} else {
|
||||
LOG_ERR("SCR", "Failed to save screenshot");
|
||||
return;
|
||||
}
|
||||
|
||||
// Display a border around the screen to indicate a screenshot was taken
|
||||
|
||||
Reference in New Issue
Block a user