#include "display/bitmap_store.h" #include #include #include #include "display/bitmaps.h" static const char *DIR = "/bitmaps"; struct BuiltinBitmap { const char *name; const uint8_t *data; // PROGMEM, BITMAP_BYTES }; static const BuiltinBitmap BUILTIN[] = { {"Nintendo", nintendo_bitmap_Nintendo_new}, // The Pikachus of the Clock page are icons (display/icon_store) }; static bool mounted = false; static uint32_t revision = 0; // Bitmaps read before, also the names that don't exist (images try a name per state, then // "default"). Reading one from LittleFS takes about 100 ms, and pages are drawn again for // every frame (scrolling, seconds) and for every preview in the web UI. Cleared when a // bitmap is saved or deleted, and when it holds CACHE_MAX names. struct CachedBitmap { bool exists; uint8_t data[BITMAP_BYTES]; }; static std::map cache; static const size_t CACHE_MAX = 64; static const BuiltinBitmap *builtin(const char *name) { for (const BuiltinBitmap &b : BUILTIN) if (strcasecmp(b.name, name) == 0) return &b; return nullptr; } static String path(const char *name) { return String(DIR) + "/" + name + ".bin"; } bool bitmapStoreBegin() { mounted = LittleFS.begin(true); for (const char *dir : {DIR, "/pages", "/fonts", "/icons"}) if (mounted && !LittleFS.exists(dir)) LittleFS.mkdir(dir); Serial.printf("Bitmap storage %s\n", mounted ? "ready" : "not available (old flash layout?)"); return mounted; } bool bitmapStorageAvailable() { return mounted; } void bitmapStorageUsage(size_t &used, size_t &total) { used = mounted ? LittleFS.usedBytes() : 0; total = mounted ? LittleFS.totalBytes() : 0; } std::vector storedNames(const char *dir_path, const char *extension) { std::vector names; if (!mounted) return names; File dir = LittleFS.open(dir_path); if (!dir) return names; for (File file = dir.openNextFile(); file; file = dir.openNextFile()) { String name = file.name(); int slash = name.lastIndexOf('/'); if (slash >= 0) name = name.substring(slash + 1); if (!name.endsWith(extension)) continue; name.remove(name.length() - strlen(extension)); if (validBitmapName(name.c_str())) names.push_back(name); } return names; } std::vector listBitmaps() { std::vector list; for (const BuiltinBitmap &b : BUILTIN) list.push_back({b.name, true}); for (const String &name : storedNames(DIR, ".bin")) if (!builtin(name.c_str())) list.push_back({name, false}); return list; } uint32_t bitmapListRevision() { return revision; } bool builtinBitmap(const char *name) { return builtin(name) != nullptr; } bool bitmapExists(const char *name) { if (builtin(name)) return true; uint8_t bitmap[BITMAP_BYTES]; return loadBitmap(name, bitmap); // through the cache } bool loadBitmap(const char *name, uint8_t *bitmap) { const BuiltinBitmap *b = builtin(name); if (b) { memcpy_P(bitmap, b->data, BITMAP_BYTES); return true; } if (!mounted || !validBitmapName(name)) return false; auto cached = cache.find(name); if (cached != cache.end()) { if (cached->second.exists) memcpy(bitmap, cached->second.data, BITMAP_BYTES); return cached->second.exists; } // Checked first: open() logs an error for a missing file (images try names by state) bool ok = false; if (LittleFS.exists(path(name))) { File file = LittleFS.open(path(name), "r"); if (file) { ok = file.read(bitmap, BITMAP_BYTES) == (size_t)BITMAP_BYTES; file.close(); } } if (cache.size() >= CACHE_MAX) cache.clear(); CachedBitmap &entry = cache[name]; entry.exists = ok; if (ok) memcpy(entry.data, bitmap, BITMAP_BYTES); return ok; } const char *saveBitmap(const char *name, const uint8_t *bitmap) { if (!validBitmapName(name)) return "Invalid name: use 1-24 letters, digits, spaces, - or _"; if (builtin(name)) return "A built-in bitmap has this name"; if (!mounted) return "No bitmap storage (install the new flash layout over USB, see README)"; if (!LittleFS.exists(path(name)) && listBitmaps().size() >= (size_t)MAX_BITMAPS + (sizeof(BUILTIN) / sizeof(BUILTIN[0]))) return "Too many bitmaps, delete some first"; File file = LittleFS.open(path(name), "w"); if (!file) return "Could not write the file"; bool ok = file.write(bitmap, BITMAP_BYTES) == (size_t)BITMAP_BYTES; file.close(); revision++; cache.clear(); return ok ? nullptr : "Storage full"; } const char *deleteBitmap(const char *name) { if (builtin(name)) return "Built-in bitmaps can't be deleted"; if (!mounted || !validBitmapName(name) || !LittleFS.exists(path(name))) return "Bitmap not found"; if (!LittleFS.remove(path(name))) return "Could not delete the file"; revision++; cache.clear(); return nullptr; }