Compare commits

..
17 changed files with 537 additions and 314 deletions

No files matched your search

+6 -6
View File
@@ -501,7 +501,7 @@ The **Sleep Screen** setting controls what is displayed when the device goes to
| **Custom** | A custom image from the SD card (see below). Falls back to **Dark** if no custom image is found. |
| **Cover** | The cover of the currently open book. Falls back to **Dark** if no book is open. |
| **Cover + Custom** | The cover of the currently open book, shown only while actively reading. Falls back to **Custom** behavior when not reading. |
| **Transparent** | A BMP or PNG overlay drawn over the current screen. Supports PNG and 32-bit BGRA alpha transparency, and treats white as transparent in regular BMPs. Falls back to **Dark** if no valid overlay image is found. |
| **Transparent** | A BMP overlay drawn over the current screen. Supports alpha transparency in 32-bit BGRA images and treats white as transparent in regular BMPs. Falls back to **Dark** if no valid overlay image is found. |
| **None** | A blank screen. |
#### Cover settings
@@ -520,17 +520,17 @@ To use custom sleep images, set the sleep screen mode to **Custom** or **Cover +
#### Transparent overlay images
To use transparent sleep overlays, set the sleep screen mode to **Transparent**, then place BMP or PNG files on the SD card:
To use transparent sleep overlays, set the sleep screen mode to **Transparent**, then place BMP files on the SD card:
- **Multiple Images (recommended):** Create a `.sleep-overlay` directory in the root of the SD card and place any number of valid overlay `.bmp` or `.png` images inside. One will be randomly selected each time the device sleeps. A directory named `sleep-overlay` is also accepted as a fallback.
- **Single Image:** Place `sleep-overlay.bmp` or `sleep-overlay.png` in the root directory. A root BMP takes priority over a root PNG, and both take priority over the `.sleep-overlay`/`sleep-overlay` directories.
- **Multiple Images (recommended):** Create a `.sleep-overlay` directory in the root of the SD card and place any number of valid overlay `.bmp` images inside. One will be randomly selected each time the device sleeps. A directory named `sleep-overlay` is also accepted as a fallback.
- **Single Image:** Place a file named `sleep-overlay.bmp` in the root directory. This takes priority over the `.sleep-overlay`/`sleep-overlay` directories.
Transparent overlay files are intentionally separate from normal sleep images. Regular BMP formats supported by CrossPoint are accepted; white pixels leave the existing screen unchanged. For per-pixel alpha transparency, use a PNG with an alpha channel or a 32-bit BGRA BMP with both visible and non-opaque pixels. Opaque white pixels in alpha images erase the content behind them.
Transparent overlay files are intentionally separate from normal sleep images. Regular BMP formats supported by CrossPoint are accepted; white pixels leave the existing screen unchanged. For per-pixel alpha transparency, use a 32-bit BGRA BMP with both visible and non-opaque pixels. Opaque white pixels in these alpha images erase the content behind them.
> [!TIP]
> For best results:
> - For non-transparent **Custom** mode, use uncompressed BMP files with 24-bit color depth.
> - For **Transparent** mode, use a PNG or uncompressed 32-bit BGRA BMP for per-pixel alpha, or a regular BMP for white-as-transparent artwork.
> - For **Transparent** mode, use a regular BMP for white-as-transparent artwork or an uncompressed 32-bit BGRA BMP for per-pixel alpha.
> - X4: Use a resolution of 480x800 pixels to match the device's screen resolution.
> - X3: Use a resolution of 528x792 pixels to match the device's screen resolution.
+3 -3
View File
@@ -144,14 +144,14 @@ struct DirectPixelWriter {
// Write a single 2-bit dithered pixel value to the framebuffer.
// Must be called after beginRow() for the current row.
// No bounds checking — caller guarantees coordinates are valid.
inline void writePixel(int logicalX, uint8_t pixelValue, bool writeWhiteInBw = false) const {
inline void writePixel(int logicalX, uint8_t pixelValue) const {
// Determine whether to draw based on render mode
bool draw;
bool state;
switch (mode) {
case GfxRenderer::BW:
draw = writeWhiteInBw || pixelValue < 3;
state = pixelValue < 3;
draw = (pixelValue < 3);
state = true;
break;
case GfxRenderer::GRAYSCALE_MSB:
draw = (pixelValue == 1 || pixelValue == 2);
@@ -18,9 +18,6 @@ struct RenderConfig {
bool useDithering = true;
bool performanceMode = false;
bool useExactDimensions = false; // If true, use maxWidth/maxHeight as exact output size (no recalculation)
float sourceCropX = 0.0f; // Fraction cropped equally from the left and right edges
float sourceCropY = 0.0f; // Fraction cropped equally from the top and bottom edges
bool preserveAlpha = false; // Skip transparent pixels instead of compositing them against white
std::string cachePath; // If non-empty, decoder will write pixel cache to this path
};
@@ -7,7 +7,6 @@
#include <Memory.h>
#include <PNGdec.h>
#include <algorithm>
#include <cstdlib>
#include <memory>
#include <new>
@@ -22,7 +21,6 @@ namespace {
// The draw callback receives this via pDraw->pUser (set by png.decode()).
// The file I/O callbacks receive the HalFile* via pFile->fHandle (set by pngOpen()).
struct PngContext {
PNG* decoder{nullptr};
GfxRenderer* renderer{nullptr};
const RenderConfig* config{nullptr};
int screenWidth{0};
@@ -32,18 +30,14 @@ struct PngContext {
float scale{1.f};
int srcWidth{0};
int srcHeight{0};
int cropLeft{0};
int cropTop{0};
int visibleWidth{0};
int visibleHeight{0};
int dstWidth{0};
int dstHeight{0};
int lastDstY{-1}; // Track last rendered destination Y to avoid duplicates
PixelCache cache;
bool caching{false};
uint8_t* grayLineBuffer{nullptr};
uint8_t* alphaLineBuffer{nullptr};
};
// File I/O callbacks use pFile->fHandle to access the HalFile*,
@@ -137,31 +131,19 @@ uint8_t expandSampleToByte(uint8_t sample, int bitsPerSample) {
return static_cast<uint8_t>((sample * 255U) / maxSample);
}
uint8_t alphaThreshold4x4(const int x, const int y) {
static constexpr uint8_t BAYER_4X4[16] = {0, 128, 32, 160, 192, 64, 224, 96, 48, 176, 16, 144, 240, 112, 208, 80};
return BAYER_4X4[((y & 0x03) << 2) | (x & 0x03)];
}
// Convert an entire source line to grayscale and optionally retain alpha.
// Convert entire source line to grayscale with alpha blending to white background.
// Low-bit-depth grayscale/indexed scanlines are packed most-significant sample first.
// For indexed PNGs with tRNS chunk, alpha values are stored at palette[768] onwards.
// Processing the whole line at once improves cache locality and reduces per-pixel overhead.
void convertLineToGray(const uint8_t* pPixels, uint8_t* grayLine, int width, int pixelType, int bitsPerSample,
uint8_t* palette, int hasAlpha, uint32_t transparentColor, uint8_t* alphaLine) {
const auto store = [grayLine, alphaLine](const int x, const uint8_t gray, const uint8_t alpha) {
grayLine[x] = alphaLine ? gray : static_cast<uint8_t>((gray * alpha + 255u * (255u - alpha)) / 255u);
if (alphaLine) alphaLine[x] = alpha;
};
uint8_t* palette, int hasAlpha) {
switch (pixelType) {
case PNG_PIXEL_GRAYSCALE:
if (bitsPerSample == 8 && !hasAlpha && !alphaLine) {
if (bitsPerSample == 8) {
memcpy(grayLine, pPixels, width);
} else {
for (int x = 0; x < width; x++) {
const uint8_t sample = readPackedSample(pPixels, x, bitsPerSample);
const uint8_t gray = expandSampleToByte(sample, bitsPerSample);
store(x, gray, hasAlpha && sample == static_cast<uint8_t>(transparentColor) ? 0 : 255);
grayLine[x] = expandSampleToByte(readPackedSample(pPixels, x, bitsPerSample), bitsPerSample);
}
}
break;
@@ -169,44 +151,53 @@ void convertLineToGray(const uint8_t* pPixels, uint8_t* grayLine, int width, int
case PNG_PIXEL_TRUECOLOR:
for (int x = 0; x < width; x++) {
const uint8_t* p = &pPixels[x * 3];
const uint8_t gray = static_cast<uint8_t>((p[0] * 77 + p[1] * 150 + p[2] * 29) >> 8);
const uint32_t color = (static_cast<uint32_t>(p[0]) << 16) | (static_cast<uint32_t>(p[1]) << 8) | p[2];
store(x, gray, hasAlpha && color == transparentColor ? 0 : 255);
grayLine[x] = (uint8_t)((p[0] * 77 + p[1] * 150 + p[2] * 29) >> 8);
}
break;
case PNG_PIXEL_INDEXED:
if (palette) {
for (int x = 0; x < width; x++) {
const uint8_t idx = readPackedSample(pPixels, x, bitsPerSample);
const uint8_t* p = &palette[idx * 3];
const uint8_t gray = static_cast<uint8_t>((p[0] * 77 + p[1] * 150 + p[2] * 29) >> 8);
store(x, gray, hasAlpha ? palette[768 + idx] : 255);
if (hasAlpha) {
for (int x = 0; x < width; x++) {
uint8_t idx = readPackedSample(pPixels, x, bitsPerSample);
uint8_t* p = &palette[idx * 3];
uint8_t gray = (uint8_t)((p[0] * 77 + p[1] * 150 + p[2] * 29) >> 8);
uint8_t alpha = palette[768 + idx];
grayLine[x] = (uint8_t)((gray * alpha + 255 * (255 - alpha)) / 255);
}
} else {
for (int x = 0; x < width; x++) {
uint8_t idx = readPackedSample(pPixels, x, bitsPerSample);
uint8_t* p = &palette[idx * 3];
grayLine[x] = (uint8_t)((p[0] * 77 + p[1] * 150 + p[2] * 29) >> 8);
}
}
} else {
for (int x = 0; x < width; x++) {
store(x, expandSampleToByte(readPackedSample(pPixels, x, bitsPerSample), bitsPerSample), 255);
grayLine[x] = expandSampleToByte(readPackedSample(pPixels, x, bitsPerSample), bitsPerSample);
}
}
break;
case PNG_PIXEL_GRAY_ALPHA:
for (int x = 0; x < width; x++) {
store(x, pPixels[x * 2], pPixels[x * 2 + 1]);
uint8_t gray = pPixels[x * 2];
uint8_t alpha = pPixels[x * 2 + 1];
grayLine[x] = (uint8_t)((gray * alpha + 255 * (255 - alpha)) / 255);
}
break;
case PNG_PIXEL_TRUECOLOR_ALPHA:
for (int x = 0; x < width; x++) {
const uint8_t* p = &pPixels[x * 4];
const uint8_t gray = static_cast<uint8_t>((p[0] * 77 + p[1] * 150 + p[2] * 29) >> 8);
store(x, gray, p[3]);
uint8_t gray = (uint8_t)((p[0] * 77 + p[1] * 150 + p[2] * 29) >> 8);
uint8_t alpha = p[3];
grayLine[x] = (uint8_t)((gray * alpha + 255 * (255 - alpha)) / 255);
}
break;
default:
memset(grayLine, 128, width);
if (alphaLine) memset(alphaLine, 255, width);
break;
}
}
@@ -217,29 +208,25 @@ int pngDrawCallback(PNGDRAW* pDraw) {
int srcY = pDraw->y;
int srcWidth = ctx->srcWidth;
if (srcY < ctx->cropTop || srcY >= ctx->cropTop + ctx->visibleHeight) return 1;
const int visibleSrcY = srcY - ctx->cropTop;
// Map source rows with the exact output-height ratio. During downscaling,
// multiple source rows can select the same output row; during upscaling, one
// source row must be repeated across every output row in its range. Emitting
// only the first row of an upscale leaves zero-filled (black) gaps in the
// streamed pixel cache.
int firstDstY = (visibleSrcY * ctx->dstHeight) / ctx->visibleHeight;
int firstDstY = (srcY * ctx->dstHeight) / ctx->srcHeight;
int endDstY = firstDstY + 1;
if (ctx->dstHeight > ctx->visibleHeight) {
endDstY = ((visibleSrcY + 1) * ctx->dstHeight) / ctx->visibleHeight;
if (ctx->dstHeight > ctx->srcHeight) {
endDstY = ((srcY + 1) * ctx->dstHeight) / ctx->srcHeight;
}
if (firstDstY <= ctx->lastDstY) firstDstY = ctx->lastDstY + 1;
if (firstDstY >= endDstY || firstDstY >= ctx->dstHeight) return 1;
if (endDstY > ctx->dstHeight) endDstY = ctx->dstHeight;
// PNGdec parses tRNS while decoding, after open() returns, so query the
// transparent color here rather than caching its pre-decode value.
const uint32_t transparentColor = ctx->decoder ? ctx->decoder->getTransparentColor() : 0;
// Convert entire source line to grayscale (improves cache locality)
convertLineToGray(pDraw->pPixels, ctx->grayLineBuffer, srcWidth, pDraw->iPixelType, pDraw->iBpp, pDraw->pPalette,
pDraw->iHasAlpha, transparentColor, ctx->alphaLineBuffer);
pDraw->iHasAlpha);
// Render scaled rows using Bresenham-style integer stepping (no floating-point division)
int dstWidth = ctx->dstWidth;
@@ -274,30 +261,27 @@ int pngDrawCallback(PNGDRAW* pDraw) {
}
}
int srcX = ctx->cropLeft;
int srcX = 0;
int error = 0;
for (int dstX = 0; dstX < dstWidth; dstX++) {
int outX = outXBase + dstX;
if (outX < screenWidth) {
const uint8_t alpha = ctx->alphaLineBuffer ? ctx->alphaLineBuffer[srcX] : 255;
if (alpha >= 8 && alpha > alphaThreshold4x4(outX, outY)) {
uint8_t gray = ctx->grayLineBuffer[srcX];
uint8_t gray = ctx->grayLineBuffer[srcX];
uint8_t ditheredGray;
if (useDithering) {
ditheredGray = applyBayerDither4Level(gray, outX, outY);
} else {
ditheredGray = gray / 85;
if (ditheredGray > 3) ditheredGray = 3;
}
pw.writePixel(outX, ditheredGray, ctx->alphaLineBuffer != nullptr);
if (caching) cw.writePixel(outX, ditheredGray);
uint8_t ditheredGray;
if (useDithering) {
ditheredGray = applyBayerDither4Level(gray, outX, outY);
} else {
ditheredGray = gray / 85;
if (ditheredGray > 3) ditheredGray = 3;
}
pw.writePixel(outX, ditheredGray);
if (caching) cw.writePixel(outX, ditheredGray);
}
// Bresenham-style stepping: advance srcX based on ratio visibleWidth/dstWidth
error += ctx->visibleWidth;
// Bresenham-style stepping: advance srcX based on ratio srcWidth/dstWidth
error += srcWidth;
while (error >= dstWidth) {
error -= dstWidth;
srcX++;
@@ -356,7 +340,6 @@ bool PngToFramebufferConverter::decodeToFramebuffer(const std::string& imagePath
}
PngContext ctx;
ctx.decoder = png.get();
ctx.renderer = &renderer;
ctx.config = &config;
ctx.screenWidth = renderer.getScreenWidth();
@@ -377,36 +360,28 @@ bool PngToFramebufferConverter::decodeToFramebuffer(const std::string& imagePath
// Calculate output dimensions
ctx.srcWidth = png->getWidth();
ctx.srcHeight = png->getHeight();
ctx.cropLeft = static_cast<int>(ctx.srcWidth * std::clamp(config.sourceCropX, 0.0f, 0.99f) / 2.0f);
ctx.cropTop = static_cast<int>(ctx.srcHeight * std::clamp(config.sourceCropY, 0.0f, 0.99f) / 2.0f);
ctx.visibleWidth = ctx.srcWidth - 2 * ctx.cropLeft;
ctx.visibleHeight = ctx.srcHeight - 2 * ctx.cropTop;
if (ctx.visibleWidth <= 0 || ctx.visibleHeight <= 0) {
LOG_ERR("PNG", "PNG crop leaves no visible pixels");
return false;
}
if (config.useExactDimensions && config.maxWidth > 0 && config.maxHeight > 0) {
// Use exact dimensions as specified (avoids rounding mismatches with pre-calculated sizes)
ctx.dstWidth = config.maxWidth;
ctx.dstHeight = config.maxHeight;
ctx.scale = (float)ctx.dstWidth / ctx.visibleWidth;
ctx.scale = (float)ctx.dstWidth / ctx.srcWidth;
} else {
// Calculate scale factor to fit within maxWidth/maxHeight
float scaleX = (float)config.maxWidth / ctx.visibleWidth;
float scaleY = (float)config.maxHeight / ctx.visibleHeight;
float scaleX = (float)config.maxWidth / ctx.srcWidth;
float scaleY = (float)config.maxHeight / ctx.srcHeight;
ctx.scale = (scaleX < scaleY) ? scaleX : scaleY;
if (ctx.scale > 1.0f) ctx.scale = 1.0f; // Don't upscale
ctx.dstWidth = (int)(ctx.visibleWidth * ctx.scale);
ctx.dstHeight = (int)(ctx.visibleHeight * ctx.scale);
ctx.dstWidth = (int)(ctx.srcWidth * ctx.scale);
ctx.dstHeight = (int)(ctx.srcHeight * ctx.scale);
}
ctx.lastDstY = -1; // Reset row tracking
const int pixelType = png->getPixelType();
const int bitsPerSample = png->getBpp();
LOG_DBG("PNG", "PNG %dx%d (visible %dx%d) -> %dx%d (scale %.2f), type: %d, bpp: %d", ctx.srcWidth, ctx.srcHeight,
ctx.visibleWidth, ctx.visibleHeight, ctx.dstWidth, ctx.dstHeight, ctx.scale, pixelType, bitsPerSample);
LOG_DBG("PNG", "PNG %dx%d -> %dx%d (scale %.2f), type: %d, bpp: %d", ctx.srcWidth, ctx.srcHeight, ctx.dstWidth,
ctx.dstHeight, ctx.scale, pixelType, bitsPerSample);
const int requiredInternal = requiredPngInternalBufferBytes(ctx.srcWidth, pixelType, bitsPerSample);
if (requiredInternal > PNG_MAX_BUFFERED_PIXELS) {
@@ -435,18 +410,12 @@ bool PngToFramebufferConverter::decodeToFramebuffer(const std::string& imagePath
return false;
}
// tRNS chunks are parsed during decode(), so hasAlpha() is not reliable yet
// for color-key and indexed transparency. Reserve the alpha line whenever
// the caller requests preservation; opaque pixels simply store alpha 255.
const bool retainAlpha = config.preserveAlpha;
const size_t lineBufferBytes = grayBufSize * (retainAlpha ? 2u : 1u);
auto lineBuffers = makeUniqueNoThrow<uint8_t[]>(lineBufferBytes);
if (!lineBuffers) {
LOG_ERR("PNG", "Failed to allocate PNG line buffers (%u bytes)", static_cast<unsigned>(lineBufferBytes));
auto grayLineBuffer = makeUniqueNoThrow<uint8_t[]>(grayBufSize);
if (!grayLineBuffer) {
LOG_ERR("PNG", "Failed to allocate gray line buffer");
return false;
}
ctx.grayLineBuffer = lineBuffers.get();
ctx.alphaLineBuffer = retainAlpha ? ctx.grayLineBuffer + grayBufSize : nullptr;
ctx.grayLineBuffer = grayLineBuffer.get();
// Stream the pixel cache to disk. PNGdec delivers source scanlines top to
// bottom and we emit at most one (downscaled) output row per callback, so the
@@ -454,7 +423,7 @@ bool PngToFramebufferConverter::decodeToFramebuffer(const std::string& imagePath
// unlike the old full-image buffer it neither competes with the ~44KB decoder
// nor forces larger images to skip caching - which previously meant a full
// re-decode on every one of an image page's ~14 render passes.
ctx.caching = !config.preserveAlpha && !config.cachePath.empty();
ctx.caching = !config.cachePath.empty();
if (ctx.caching) {
if (!ctx.cache.begin(config.cachePath, ctx.dstWidth, ctx.dstHeight, config.x, config.y, 1)) {
LOG_ERR("PNG", "Failed to start cache stream, continuing without caching");
@@ -467,7 +436,6 @@ bool PngToFramebufferConverter::decodeToFramebuffer(const std::string& imagePath
unsigned long decodeTime = millis() - decodeStart;
ctx.grayLineBuffer = nullptr;
ctx.alphaLineBuffer = nullptr;
if (rc != PNG_SUCCESS) {
LOG_ERR("PNG", "Decode failed: %d", rc);
+431
View File
@@ -0,0 +1,431 @@
_language_name: "Български"
_language_code: "BG"
_order: "31"
STR_CROSSPOINT: "CrossPoint"
STR_BOOTING: "ЗАРЕЖДАНЕ"
STR_SLEEPING: "СПЯЩ РЕЖИМ"
STR_ENTERING_SLEEP: "Преминаване в спящ режим"
STR_BROWSE_FILES: "Разглеждане на файлове"
STR_FILE_TRANSFER: "Прехвърляне на файлове"
STR_SETTINGS_TITLE: "Настройки"
STR_CONTINUE_READING: "Продължи четенето"
STR_RESUME: "Продължи"
STR_NO_OPEN_BOOK: "Няма отворена книга"
STR_START_READING: "Започни четене по-долу"
STR_NO_FILES_FOUND: "Не са намерени файлове"
STR_SELECT_CHAPTER: "Избери глава"
STR_NO_CHAPTERS: "Няма глави"
STR_END_OF_BOOK: "Край на книгата"
STR_EMPTY_CHAPTER: "Празна глава"
STR_INDEXING: "Индексиране"
STR_INDEX_FAILED: "Неуспешно индексиране - невалидна книга"
STR_MEMORY_ERROR: "Грешка в паметта"
STR_PAGE_LOAD_ERROR: "Грешка при зареждане на страницата"
STR_EMPTY_FILE: "Празен файл"
STR_INVALID_BMP_FILE: "Невалиден BMP файл"
STR_FILE_OPEN_FAILED: "Файлът не може да бъде отворен"
STR_OUT_OF_BOUNDS: "Извън обхвата"
STR_LOADING: "Зареждане..."
STR_LOADING_POPUP: "Зареждане"
STR_LOOKUP: "Търсене"
STR_DICT_LOOKING_UP: "Търсене..."
STR_DICT_INDEXING: "Индексиране на речника..."
STR_DICT_NOT_FOUND: "Не е намерено"
STR_DICT_NO_DICT_SET: "Не е зададен речник"
STR_DICT_ERROR: "Грешка в речника"
STR_DICT_READ_FAILED: "Определението не може да бъде прочетено"
STR_DICT_DECOMPRESS_ERROR: "Определението не може да бъде декомпресирано"
STR_DICT_LOW_MEMORY: "Недостатъчна памет"
STR_WIFI_NETWORKS: "Wi-Fi мрежи"
STR_NO_NETWORKS: "Не са намерени мрежи"
STR_NETWORKS_FOUND: "Намерени мрежи: %zu"
STR_SCANNING: "Сканиране..."
STR_FINDING_SAVED_WIFI: "Търсене на запазен Wi-Fi..."
STR_CONNECTING: "Свързване..."
STR_CONNECTING_SAVED_WIFI: "Свързване към запазен Wi-Fi..."
STR_SHOW_NETWORKS: "Покажи"
STR_CONNECTED: "Свързан!"
STR_CONNECTION_FAILED: "Неуспешна връзка"
STR_FORGET_NETWORK: "Забравяне на мрежата?"
STR_SAVE_PASSWORD: "Запазване на паролата за следващия път?"
STR_PRESS_OK_SCAN: "Натисни OK за ново сканиране"
STR_JOIN_NETWORK: "Присъединяване към мрежа"
STR_CREATE_HOTSPOT: "Създаване на hotspot"
STR_JOIN_DESC: "Свързване към съществуваща Wi-Fi мрежа"
STR_HOTSPOT_DESC: "Създаване на Wi-Fi мрежа, за други устройства"
STR_STARTING_HOTSPOT: "Стартиране на hotspot..."
STR_HOTSPOT_MODE: "Режим на hotspot"
STR_CONNECT_WIFI_HINT: "Свържи устройството си към Wi-Fi мрежа"
STR_OPEN_URL_HINT: "Отвори този URL адрес в браузъра си"
STR_OR_HTTP_PREFIX: "или http://"
STR_SCAN_QR_HINT: "или сканирай QR кода с телефона си:"
STR_CALIBRE_WIRELESS: "Calibre безжично"
STR_NETWORK_LEGEND: "* = Криптирана | + = Запазена"
STR_MAC_ADDRESS: "MAC адрес:"
STR_CHECKING_WIFI: "Проверка на Wi-Fi..."
STR_ENTER_WIFI_PASSWORD: "Въведи Wi-Fi парола"
STR_ADD_HIDDEN_NETWORK: "Добави скрита мрежа..."
STR_ENTER_WIFI_SSID: "Въведи име на мрежата (SSID)"
STR_TO_PREFIX: "към "
STR_CALIBRE_RECEIVING: "Получаване: "
STR_CALIBRE_RECEIVED: "Получено: "
STR_CALIBRE_INSTRUCTION_1: "1) Инсталирай плъгина CrossPoint Reader"
STR_CALIBRE_INSTRUCTION_2: "2) Бъди в същата Wi-Fi мрежа"
STR_CALIBRE_INSTRUCTION_3: "3) В Calibre: \"Изпрати към устройство\""
STR_CALIBRE_INSTRUCTION_4: "\"Дръж този екран отворен по време на изпращане\""
STR_CAT_DISPLAY: "Екран"
STR_CAT_READER: "Четец"
STR_CAT_CONTROLS: "Контрол"
STR_CAT_SYSTEM: "Система"
STR_SLEEP_SCREEN: "Екран при спящ режим"
STR_QUICK_RESUME_TIMEOUT: "Бързо продълж. при неактивност"
STR_SLEEP_COVER_MODE: "Мащабиране на спящ екран"
STR_HIDE_BATTERY: "Скрий % на батерията"
STR_EXTRA_SPACING: "Доп. интервал между абзаци"
STR_TEXT_AA: "Изглаждане на текста"
STR_IMAGES: "Изображения"
STR_IMAGES_DISPLAY: "Показване"
STR_IMAGES_PLACEHOLDER: "Празен слот"
STR_IMAGES_SUPPRESS: "Скриване"
STR_EOB_HOME: "Начало"
STR_EOB_CONTINUE_WITH: "Продължи с"
STR_SHORT_PWR_BTN: "Натиск. бутона за захранване"
STR_ORIENTATION: "Ориентация на четене"
STR_SIDE_BTN_LAYOUT: "Странични бутони"
STR_TOUCH_READER_CONTROLS: "Сензорно управление на четеца"
STR_FRONT_BTN_FOLLOW_ORIENTATION: "Ориентация на предните бутони"
STR_LONG_PRESS_BEHAVIOR: "Задържане на преден бутон"
STR_LONG_PRESS_BEHAVIOR_OFF: "ИЗКЛ."
STR_LONG_PRESS_BEHAVIOR_SKIP: "Пропускане на глава"
STR_LONG_PRESS_BEHAVIOR_ORIENTATION: "Промяна на ориентацията"
STR_LONG_PRESS_MENU: "Задържане на меню бутон"
STR_FONT_PREVIEW_TEXT: "Под южно дърво, цъфтящо в синьо, бягаше малко пухкаво зайче."
STR_FONT_FAMILY: "Шрифт на четеца"
STR_FONT_SIZE: "Размер на шрифта на четеца"
STR_TEXT_SETTINGS: "Настройки на текста"
STR_FONT: "Шрифт"
STR_SIZE: "Размер"
STR_LAYOUT: "Изглед"
STR_ALIGNMENT: "Подравняване"
STR_STYLE: "Стил"
STR_NOT_IN_PREVIEW: "Не се показва в предварителния преглед"
STR_LINE_SPACING: "Интервал между редовете"
STR_SCREEN_MARGIN: "Отстъпи на екрана"
STR_PARA_ALIGNMENT: "Подравняване на абзаца"
STR_HYPHENATION: "Разделяне на срички"
STR_TIME_TO_SLEEP: "Време до заспиване"
STR_SHOW_HIDDEN_FILES: "Показване на скритите файлове"
STR_REMOVE_READ_FROM_RECENTS: "Махни прочетените от скорошни"
STR_MOVE_FINISHED_TO_READ: "Измести завършените в папка read"
STR_REFRESH_FREQ: "Честота на опресняване"
STR_KOREADER_SYNC: "KOReader синхронизация"
STR_CHECK_UPDATES: "Проверка за актуализации"
STR_LANGUAGE: "Език"
STR_CLEAR_READING_CACHE: "Изчисти кеша на четенето"
STR_USERNAME: "Потребителско име"
STR_PASSWORD: "Парола"
STR_SYNC_SERVER_URL: "URL адрес на сървъра за синхронизация"
STR_DOCUMENT_MATCHING: "Съпоставяне на документи"
STR_SEND_METADATA: "Изпращане на метаданни на документа"
STR_SYNC_BEHAVIOR: "Поведение при синхронизация"
STR_ASK_EVERY_TIME: "Питай всеки път"
STR_SMART_SYNC: "Умна синхронизация"
STR_AUTHENTICATE: "Удостовери се"
STR_SIGN_UP: "Регистрация"
STR_CREATING_ACCOUNT: "Създаване на профил..."
STR_ACCOUNT_CREATED: "Профилът е създаден"
STR_SIGNUP_FAILED: "Неуспешна регистрация"
STR_USERNAME_TAKEN: "Потребителското име вече е регистрирано"
STR_KOREADER_USERNAME: "KOReader потребителско име"
STR_KOREADER_PASSWORD: "KOReader парола"
STR_FILENAME: "Име на файла"
STR_BINARY: "Двоичен"
STR_SET_CREDENTIALS_FIRST: "Първо задай данни за вход"
STR_WIFI_CONN_FAILED: "Неуспешна Wi-Fi връзка"
STR_AUTHENTICATING: "Удостоверяване..."
STR_AUTH_SUCCESS: "Успешно удостоверяване!"
STR_KOREADER_AUTH: "KOReader удостоверяване"
STR_SYNC_READY: "KOReader синхронизацията е готова за използване"
STR_AUTH_FAILED: "Неуспешно удостоверяване"
STR_DONE: "Готово"
STR_CLEAR_CACHE_WARNING_1: "Това ще изчисти всички кеширани данни за книгите."
STR_CLEAR_CACHE_WARNING_2: "Целият прогрес на четенето ще бъде загубен!"
STR_CLEAR_CACHE_WARNING_3: "Книгите ще трябва да бъдат преиндексирани"
STR_CLEAR_CACHE_WARNING_4: "при следващо отваряне."
STR_CLEARING_CACHE: "Изчистване на кеша..."
STR_CACHE_CLEARED: "Кешът е изчистен"
STR_ITEMS_REMOVED: "премахнати елемента"
STR_FAILED_LOWER: "неуспешно"
STR_CLEAR_CACHE_FAILED: "Неуспешно изчистване на кеша"
STR_CHECK_SERIAL_OUTPUT: "Провери серийния изход за подробности"
STR_DARK: "Тъмен"
STR_LIGHT: "Светъл"
STR_CUSTOM: "Персонализиран"
STR_COVER: "Корица"
STR_NONE_OPT: "Няма"
STR_FIT: "Побиране"
STR_CROP: "Изрязване"
STR_NEVER: "Никога"
STR_IN_READER: "В четеца"
STR_ALWAYS: "Винаги"
STR_IGNORE: "Игнорирай"
STR_SLEEP: "Спящ режим"
STR_PAGE_TURN: "Обръщане на страница"
STR_FORCE_REFRESH: "Опресни екрана"
STR_PORTRAIT: "Портрет"
STR_LANDSCAPE_CW: "Пейзаж (по часовниковата стрелка)"
STR_INVERTED: "Обърнато"
STR_ORIENTATION_INVERTED: "Портрет 180°"
STR_LANDSCAPE_CCW: "Пейзаж (срещу часовниковата стрелка)"
STR_PREV_NEXT: "Предишен/Следващ"
STR_NEXT_PREV: "Следващ/Предишен"
STR_KOSYNC: "KOSync"
STR_BOOKMARK_OPTION: "Отметка"
STR_DICTIONARY: "Речник"
STR_DISABLED: "Изключено"
STR_NOTO_SERIF: "Noto Serif"
STR_NOTO_SANS: "Noto Sans"
STR_TIGHT: "Тесен"
STR_NORMAL: "Нормален"
STR_WIDE: "Широк"
STR_JUSTIFY: "Двустранно"
STR_ALIGN_LEFT: "Ляво"
STR_CENTER: "Центрирано"
STR_ALIGN_RIGHT: "Дясно"
STR_PAGES_1: "1 страница"
STR_PAGES_5: "5 страници"
STR_PAGES_10: "10 страници"
STR_PAGES_15: "15 страници"
STR_PAGES_30: "30 страници"
STR_UPDATE: "Актуализация"
STR_CHECKING_UPDATE: "Проверка за актуализация..."
STR_NEW_UPDATE: "Налична е нова актуализация!"
STR_CURRENT_VERSION: "Текуща версия: "
STR_NEW_VERSION: "Нова версия: "
STR_UPDATING: "Актуализиране..."
STR_NO_UPDATE: "Няма налична актуализация"
STR_UPDATE_FAILED: "Неуспешна актуализация"
STR_UPDATE_COMPLETE: "Актуализацията завърши"
STR_POWER_ON_HINT: "Натисни и задръж бутона за захранване за включване"
STR_RESTARTING_HINT: "Рестартиране... Ако устройството не се рестартира, задръж бутона за захранване за няколко секунди."
STR_NO_ENTRIES: "Не са намерени записи"
STR_DOWNLOADING: "Изтегляне..."
STR_DOWNLOAD_FAILED: "Неуспешно изтегляне"
STR_ERROR_MSG: "Грешка:"
STR_UNNAMED: "Без име"
STR_HOLD_OPEN_TO_DELETE: "Задръж отворено за изтриване"
STR_NO_SERVER_URL: "Не е зададен URL адрес на сървъра"
STR_FETCH_FEED_FAILED: "Неуспешно извличане на емисията"
STR_PARSE_FEED_FAILED: "Неуспешен анализ на емисията"
STR_NEXT_PAGE: "Следваща страница »"
STR_PREV_PAGE: "« Предишна страница"
STR_NETWORK_PREFIX: "Мрежа: "
STR_IP_ADDRESS_PREFIX: "IP адрес: "
STR_ERROR_GENERAL_FAILURE: "Грешка: Обща неуспешна операция"
STR_ERROR_NETWORK_NOT_FOUND: "Грешка: Мрежата не е намерена"
STR_ERROR_CONNECTION_TIMEOUT: "Грешка: Изтекло време за връзка"
STR_SD_CARD: "SD карта"
STR_BACK: "« Назад"
STR_EXIT: "« Изход"
STR_HOME: "« Начало"
STR_SELECT: "Избери"
STR_SELECTED: "Избрано"
STR_TOGGLE: "Избор"
STR_TOGGLE_BOOKMARK: "Превключи отметка"
STR_CONFIRM: "Потвърди"
STR_CANCEL: "Отказ"
STR_CONNECT: "Свържи"
STR_OPEN: "Отвори"
STR_DOWNLOAD: "Изтегли"
STR_RETRY: "Повтори"
STR_TAP_TO_RETRY: "Докосни за нов опит"
STR_YES: "Да"
STR_NO: "Не"
STR_SHOW: "Покажи"
STR_HIDE: "Скрий"
STR_STATE_ON: "ВКЛ."
STR_STATE_OFF: "ИЗКЛ."
STR_NOT_SET: "Незададено"
STR_DIR_LEFT: "Ляво"
STR_DIR_RIGHT: "Дясно"
STR_DIR_UP: "Нагоре"
STR_DIR_DOWN: "Надолу"
STR_OK_BUTTON: "OK"
STR_KEY_SHIFT: "Shift"
STR_KEY_MODE_SYMBOLS: "?123"
STR_KEY_MODE_ABC: "abc"
STR_SLEEP_COVER_FILTER: "Филтър корица на спящ екран"
STR_FILTER_CONTRAST: "Контраст"
STR_CUSTOMISE_STATUS_BAR: "Персонализирай лентата на състоянието"
STR_CHAPTER_PAGE_COUNT: "Брой страници на главата"
STR_BOOK_PROGRESS_PERCENTAGE: "Процент прогрес на книгата"
STR_PROGRESS_BAR: "Лента за прогрес"
STR_PROGRESS_BAR_THICKNESS: "Дебелина на лентата за прогрес"
STR_PROGRESS_BAR_THIN: "Тънка"
STR_PROGRESS_BAR_MEDIUM: "Средна"
STR_PROGRESS_BAR_THICK: "Дебела"
STR_BOOK: "Книга"
STR_CHAPTER: "Глава"
STR_EXAMPLE_CHAPTER: "Глава 21"
STR_EXAMPLE_BOOK: "Заглавие на книгата"
STR_PREVIEW: "Преглед"
STR_TITLE: "Заглавие"
STR_BATTERY: "Батерия"
STR_XTC_STATUS_BAR: "XTC лента на състоянието"
STR_BOTTOM: "Долу"
STR_TOP: "Горе"
STR_CLOCK: "Часовник"
STR_CLOCK_UTC_OFFSET: "UTC отместване на часовника"
STR_CLOCK_FORMAT: "Формат на часовника"
STR_CLOCK_FORMAT_24H: "24-часов"
STR_CLOCK_FORMAT_12H: "12-часов"
STR_CURRENT_TIME: "Текущо време:"
STR_NEXT_FIELD: "Следващо"
STR_CLOCK_SYNC: "Синхронизирай часовника"
STR_CLOCK_SYNC_NOW: "Синхронизирай часовника сега"
STR_CLOCK_SYNCING: "Синхронизиране от NTP..."
STR_CLOCK_SYNC_OK: "Часовникът е синхронизиран"
STR_CLOCK_SYNC_FAIL: "Неуспешна синхронизация"
STR_CLOCK_SYNC_NO_WIFI: "Wi-Fi не е свързан"
STR_CLOCK_SYNC_NO_WIFI_HINT: "Свържи се с Wi-Fi и опитай отново."
STR_CLOCK_SYNCED: "Часовникът е синхронизиран"
STR_UI_THEME: "Тема на интерфейса"
STR_THEME_CLASSIC: "Класическа"
STR_THEME_LYRA: "Lyra"
STR_THEME_ROUNDEDRAFF: "RoundedRaff"
STR_THEME_LYRA_EXTENDED: "Lyra Extended"
STR_SUNLIGHT_FADING_FIX: "Защита от избледняване при слънце"
STR_REMAP_FRONT_BUTTONS: "Преназначаване на предните бутони"
STR_BOOKMARKS: "Отметки"
STR_BOOKMARK_ADDED: "Отметката е добавена."
STR_BOOKMARK_REMOVED: "Отметката е премахната."
STR_OPDS_BROWSER: "OPDS браузър"
STR_SEARCH: "Търсене"
STR_COVER_CUSTOM: "Корица + Персонализиран"
STR_QUICK_RESUME: "Бързо продължаване"
STR_MENU_RECENT_BOOKS: "Скорошни книги"
STR_REMOVE_FROM_RECENTS: "Премахване от скорошните книги?"
STR_NO_RECENT_BOOKS: "Няма скорошни книги"
STR_CALIBRE_DESC: "Използвай безжично прехвърляне през Calibre"
STR_FORGET_AND_REMOVE: "Забравяне на мрежата и премахване на запазената парола?"
STR_FORGET_BUTTON: "Забрави"
STR_CALIBRE_STARTING: "Стартиране на Calibre..."
STR_CALIBRE_SETUP: "Настройка"
STR_CALIBRE_STATUS: "Състояние"
STR_CLEAR_BUTTON: "Изчисти"
STR_DEFAULT_VALUE: "По подразбиране"
STR_REMAP_PROMPT: "Натисни предния бутон за всяка роля"
STR_UNASSIGNED: "Незададено"
STR_ALREADY_ASSIGNED: "Вече е зададено"
STR_REMAP_RESET_HINT: "Страничен бутон Нагоре: Рестарт на настройки"
STR_REMAP_CANCEL_HINT: "Страничен бутон Надолу: Отказ от преназначаване"
STR_HW_BACK_LABEL: "Назад (1-ви бутон)"
STR_HW_CONFIRM_LABEL: "Потвърди (2-ри бутон)"
STR_HW_LEFT_LABEL: "Ляво (3-ти бутон)"
STR_HW_RIGHT_LABEL: "Дясно (4-ти бутон)"
STR_GO_TO_PERCENT: "Отиди на %"
STR_GO_HOME_BUTTON: "Начало"
STR_SYNC_PROGRESS: "Синхронизирай прогреса"
STR_DELETE_CACHE: "Изтрий кеша на книгата"
STR_DELETE: "Изтрий"
STR_CONFIRM_DELETE_BOOKMARK: "Изтриване на тази отметка?"
STR_DISPLAY_QR: "Покажи страницата като QR код"
STR_CHAPTER_PREFIX: "Глава: "
STR_PAGES_SEPARATOR: " страници | "
STR_BOOK_PREFIX: "Книга: "
STR_CALIBRE_URL_HINT: "За Calibre добави /opds към твоя URL адрес"
STR_SYNCING_TIME: "Синхронизиране на времето..."
STR_CALC_HASH: "Изчисляване на хеш на документа..."
STR_HASH_FAILED: "Неуспешно изчисляване на хеша на документа"
STR_FETCH_PROGRESS: "Извличане на прогреса от сървъра..."
STR_UPLOAD_PROGRESS: "Качване на прогреса..."
STR_NO_CREDENTIALS_MSG: "Не са зададени данни за вход"
STR_KOREADER_SETUP_HINT: "Настрой KOReader профил в Настройки"
STR_PROGRESS_FOUND: "Прогресът е намерен!"
STR_REMOTE_LABEL: "От сървъра:"
STR_LOCAL_LABEL: "Локален:"
STR_PAGE_OVERALL_FORMAT: "Страница %d, %.2f%% общо"
STR_PAGE_TOTAL_OVERALL_FORMAT: "Страница %d/%d, %.2f%% общо"
STR_DEVICE_FROM_FORMAT: " От: %s"
STR_APPLY_REMOTE: "Приложи прогреса от сървъра"
STR_UPLOAD_LOCAL: "Качи локалния прогрес"
STR_NO_REMOTE_MSG: "Не е намерен прогрес на сървъра"
STR_UPLOAD_PROMPT: "Качване на текущата позиция?"
STR_UPLOAD_SUCCESS: "Прогресът е качен!"
STR_ALREADY_SYNCED: "Вече е синхронизиран"
STR_SYNC_FAILED_MSG: "Неуспешна синхронизация"
STR_SAVE_PROGRESS_FAILED: "Прогресът не може да бъде запазен"
STR_SECTION_PREFIX: "Раздел "
STR_UPLOAD: "Качи"
STR_BOOK_S_STYLE: "Стил на книгата"
STR_EMBEDDED_STYLE: "Вграден стил"
STR_FOCUS_READING: "Фокусирано четене"
STR_OPDS_SERVER_URL: "URL адрес на OPDS сървъра"
STR_PWR_BTN_FOOTNOTE_BACK: "Бързо връщане от бележки под линия"
STR_BACK_SHORT_TO_FILE_BROWSER: "Натиск. назад към файловия браузър"
STR_SET_SLEEP_COVER: "Задай корица"
STR_FOOTNOTES: "Бележки под линия"
STR_NO_FOOTNOTES: "Няма бележки под линия на тази страница"
STR_LINK: "[връзка]"
STR_SCREENSHOT_BUTTON: "Направи снимка на екрана"
STR_SLEEP_TIMER_VALUE_FORMAT: "%u мин"
STR_SLEEP_NEVER: "Никога"
STR_STEP_HINT_FRONT: "Предни бутони:"
STR_STEP_HINT_SIDE: "Странични бутони:"
STR_ADD_SERVER: "Добави сървър"
STR_SERVER_NAME: "Име на сървъра"
STR_NO_SERVERS: "Няма конфигурирани OPDS сървъри"
STR_DELETE_SERVER: "Изтрий сървъра"
STR_OPDS_SERVERS: "OPDS сървъри"
STR_OPDS_DOWNLOAD_FOLDER: "Папка за изтегляне"
STR_OPDS_FILENAME_FORMAT: "Формат на името на файла"
STR_FMT_AUTHOR_TITLE: "Автор - Заглавие"
STR_FMT_TITLE_AUTHOR: "Заглавие - Автор"
STR_FMT_TITLE: "Заглавие"
STR_OPDS_SD_ROOT: "Корен на SD картата"
STR_AUTO_TURN_ENABLED: "Автоматично обръщане на страница: "
STR_AUTO_TURN_PAGES_PER_MIN: "Автоматично обръщане (страници в минута)"
STR_MANAGE_FONTS: "Управление на шрифтовете"
STR_FONT_BROWSER: "Браузър на шрифтове"
STR_LOADING_FONT_LIST: "Зареждане на списъка с шрифтове..."
STR_NO_FONTS_AVAILABLE: "Няма налични шрифтове"
STR_FONT_INSTALLED: "Шрифтът е инсталиран!"
STR_FONT_INSTALL_FAILED: "Неуспешна инсталация на шрифта"
STR_INSTALLED: "Инсталиран"
STR_DOWNLOAD_ALL: "Изтегли всички"
STR_UPDATE_ALL: "Актуализирай всички"
STR_UPDATE_AVAILABLE: "Актуализация"
STR_CRASH_TITLE: "Системен срив"
STR_CRASH_DESCRIPTION: "Подробен отчет е запазен в crash_report.txt. Моля, приложи този файл към твоя бъг репорт."
STR_CRASH_REASON: "Причина за срива:"
STR_CRASH_NO_REASON: "(Не е записана причина)"
STR_TILT_PAGE_TURN: "Обръщане на страница чрез накланяне"
STR_KB_HINT_MOVE_CURSOR: "Натисни ЛЯВО или ДЯСНО за преместване на курсора"
STR_KB_HINT_RETURN_CURSOR: "Натисни ЛЯВО за връщане към позицията на курсора"
STR_KB_HINT_HIDE_PASSWORD: "Задръж ДЯСНО и натисни [***] за скриване на паролата"
STR_KB_HINT_SHOW_PASSWORD: "Задръж ДЯСНО и натисни [abc] за показване на паролата"
STR_KB_HINT_TOGGLE_HIDE_PASSWORD: "Натисни [***] за скриване на паролата"
STR_KB_HINT_TOGGLE_SHOW_PASSWORD: "Натисни [abc] за показване на паролата"
STR_KB_HINT_EDIT_ENTRY: "Задръж НАГОРЕ за редактиране на записа"
STR_KB_TIPS: "Съвети:"
STR_KB_HINT_RETURN_KEYBOARD: "Натисни НАДОЛУ за връщане към клавиатурата"
STR_KB_HINT_EXIT_URL_MODE: "Натисни ABC за изход от URL режим"
STR_KB_HINT_CLEAR_TEXT: "Задръж DEL за изчистване на целия текст"
STR_KB_HINT_SECONDARY_CHAR: "Задръж SELECT за символи"
STR_KB_HINT_UPPER_SECONDARY: "Задръж SELECT за ГЛАВНИ БУКВИ или символи"
STR_KB_HINT_LOWER_SECONDARY: "Задръж SELECT за малки букви или символи"
STR_KB_HINT_URL_SNIPPETS: "Натисни URL за фрагменти"
STR_SD_FIRMWARE_UPDATE: "Актуализация на фърмуера чрез SD карта"
STR_SELECT_FIRMWARE_FILE: "Избери файл с фърмуер (.bin)"
STR_NO_BIN_FILES: "Не са намерени .bin файлове"
STR_VALIDATING_FIRMWARE: "Валидиране на фърмуера..."
STR_INVALID_FIRMWARE: "Невалиден файл с фърмуер"
STR_FIRMWARE_TOO_LARGE: "Фърмуерът е твърде голям за дяла"
STR_FIRMWARE_TOO_SMALL: "Файлът с фърмуер е твърде малък"
STR_FIRMWARE_UPDATE_PROMPT: "Актуализиране на фърмуера?"
STR_FIRMWARE_FILE_OPEN_FAILED: "Файлът не може да бъде отворен"
STR_FIRMWARE_WRITE_FAILED: "Неуспешен запис на фърмуера"
STR_FIRMWARE_UPDATE_DO_NOT_POWER_OFF: "Не изключвай захранването!"
STR_RECOVERY_MODE: "Режим за възстановяване"
STR_RECOVERY_MODE_HINT: "Постави firmware.bin в основната директория на SD картата и го избери"
+2
View File
@@ -273,6 +273,7 @@ STR_SUNLIGHT_FADING_FIX: "Correcció de l'esvaïment pel sol"
STR_QUICK_RESUME_TIMEOUT: "Represa ràpida per inactivitat"
STR_REMAP_FRONT_BUTTONS: "Reassigna els botons frontals"
STR_BOOKMARKS: "Punts de llibre"
STR_NO_BOOKMARKS: "No hi ha punts de llibre"
STR_BOOKMARK_ADDED: "S'ha afegit el punt de llibre."
STR_BOOKMARK_REMOVED: "S'ha eliminat el punt de llibre."
STR_OPDS_BROWSER: "Navegador OPDS"
@@ -423,6 +424,7 @@ STR_VALIDATING_FIRMWARE: "S'està validant el firmware..."
STR_INVALID_FIRMWARE: "Fitxer de firmware no vàlid"
STR_FIRMWARE_TOO_LARGE: "El firmware és massa gran per a la partició"
STR_FIRMWARE_TOO_SMALL: "El fitxer de firmware és massa petit"
STR_FIRMWARE_WRONG_DEVICE: "El firmware és per a un altre dispositiu"
STR_FIRMWARE_UPDATE_PROMPT: "Vols actualitzar el firmware?"
STR_FIRMWARE_FILE_OPEN_FAILED: "No es pot obrir el fitxer"
STR_FIRMWARE_WRITE_FAILED: "Ha fallat l'escriptura del firmware"
+2
View File
@@ -297,6 +297,7 @@ STR_SUNLIGHT_FADING_FIX: "Corrección de desvanecimiento"
STR_QUICK_RESUME_TIMEOUT: "Reanudación rápida tras tiempo"
STR_REMAP_FRONT_BUTTONS: "Reconfigurar botones frontales"
STR_BOOKMARKS: "Marcadores"
STR_NO_BOOKMARKS: "No hay marcadores"
STR_BOOKMARK_ADDED: "Marcador añadido."
STR_BOOKMARK_REMOVED: "Marcador eliminado."
STR_OPDS_BROWSER: "Navegador OPDS"
@@ -423,6 +424,7 @@ STR_VALIDATING_FIRMWARE: "Validando el firmware..."
STR_INVALID_FIRMWARE: "Archivo de firmware no válido"
STR_FIRMWARE_TOO_LARGE: "Firmware demasiado grande p/la partición"
STR_FIRMWARE_TOO_SMALL: "Firmware demasiado pequeño"
STR_FIRMWARE_WRONG_DEVICE: "El firmware es para otro dispositivo"
STR_FIRMWARE_UPDATE_PROMPT: "¿Actualizar el firmware?"
STR_FIRMWARE_FILE_OPEN_FAILED: "No se puede abrir el archivo"
STR_FIRMWARE_WRITE_FAILED: "Falló la escritura del firmware"
+1
View File
@@ -381,6 +381,7 @@ STR_FIRMWARE_TOO_SMALL: "Firmware dosyası çok küçük"
STR_FIRMWARE_UPDATE_DO_NOT_POWER_OFF: "Cihazı kapatmayın!"
STR_FIRMWARE_UPDATE_PROMPT: "Firmware güncellensin mi?"
STR_FIRMWARE_WRITE_FAILED: "Firmware yazılamadı"
STR_FIRMWARE_WRONG_DEVICE: "Firmware farklı bir cihaz için"
STR_FONT_BROWSER: "Yazı Tipi Tarayıcısı"
STR_FONT_INSTALLED: "Yazı tipi yüklendi!"
STR_FONT_INSTALL_FAILED: "Yazı tipi yüklenemedi"
+2
View File
@@ -278,6 +278,7 @@ STR_THEME_LYRA_EXTENDED: "Lyra Ampliat"
STR_SUNLIGHT_FADING_FIX: "Correcció de l'esvaïment pel sol"
STR_REMAP_FRONT_BUTTONS: "Reassigna els botons frontals"
STR_BOOKMARKS: "Punts de llibre"
STR_NO_BOOKMARKS: "No hi ha punts de llibre"
STR_BOOKMARK_ADDED: "S'ha afegit el punt de llibre."
STR_BOOKMARK_REMOVED: "S'ha eliminat el punt de llibre."
STR_OPDS_BROWSER: "Navegador OPDS"
@@ -423,6 +424,7 @@ STR_VALIDATING_FIRMWARE: "S'està validant el firmware..."
STR_INVALID_FIRMWARE: "Arxiu de firmware no vàlid"
STR_FIRMWARE_TOO_LARGE: "El firmware és massa gran per a la partició"
STR_FIRMWARE_TOO_SMALL: "L'arxiu de firmware és massa menut"
STR_FIRMWARE_WRONG_DEVICE: "El firmware és per a un altre dispositiu"
STR_FIRMWARE_UPDATE_PROMPT: "Vols actualitzar el firmware?"
STR_FIRMWARE_FILE_OPEN_FAILED: "No es pot obrir l'arxiu"
STR_FIRMWARE_WRITE_FAILED: "Ha fallat l'escriptura del firmware"
+20 -147
View File
@@ -1,7 +1,6 @@
#include "SleepActivity.h"
#include <Epub.h>
#include <Epub/converters/PngToFramebufferConverter.h>
#include <FsHelpers.h>
#include <GfxRenderer.h>
#include <HalDisplay.h>
@@ -9,7 +8,6 @@
#include <HalStorage.h>
#include <I18n.h>
#include <Memory.h>
#include <PNGdec.h>
#include <Txt.h>
#include <Xtc.h>
@@ -31,8 +29,7 @@
namespace {
// Kept separate from /sleep.bmp and /.sleep so alpha-overlay art does not mix with full-screen wallpapers.
constexpr char TRANSPARENT_SLEEP_ROOT_BMP[] = "/sleep-overlay.bmp";
constexpr char TRANSPARENT_SLEEP_ROOT_PNG[] = "/sleep-overlay.png";
constexpr char TRANSPARENT_SLEEP_ROOT[] = "/sleep-overlay.bmp";
constexpr char TRANSPARENT_SLEEP_DIR[] = "/.sleep-overlay";
constexpr char TRANSPARENT_SLEEP_LEGACY_DIR[] = "/sleep-overlay";
constexpr size_t MAX_SLEEP_FILE_NAME_LEN = 256;
@@ -74,50 +71,6 @@ uint32_t readLE32(HalFile& file) {
(static_cast<uint32_t>(b3) << 24);
}
uint32_t readBE32(HalFile& file) {
const int c0 = file.read();
const int c1 = file.read();
const int c2 = file.read();
const int c3 = file.read();
if (c0 < 0 || c1 < 0 || c2 < 0 || c3 < 0) return 0;
return (static_cast<uint32_t>(c0) << 24) | (static_cast<uint32_t>(c1) << 16) | (static_cast<uint32_t>(c2) << 8) |
static_cast<uint32_t>(c3);
}
bool isValidPngHeader(HalFile& file) {
static constexpr uint8_t PNG_SIGNATURE[8] = {0x89, 'P', 'N', 'G', 0x0D, 0x0A, 0x1A, 0x0A};
static constexpr uint32_t MAX_SOURCE_PIXELS = 2048u * 1536u;
uint8_t signature[8];
if (!file.seek(0) || file.read(signature, sizeof(signature)) != static_cast<int>(sizeof(signature)) ||
!std::equal(std::begin(signature), std::end(signature), std::begin(PNG_SIGNATURE))) {
return false;
}
const uint32_t ihdrLength = readBE32(file);
char chunkType[4];
if (file.read(reinterpret_cast<uint8_t*>(chunkType), sizeof(chunkType)) != static_cast<int>(sizeof(chunkType)) ||
ihdrLength != 13 || !std::equal(std::begin(chunkType), std::end(chunkType), "IHDR")) {
return false;
}
const uint32_t width = readBE32(file);
const uint32_t height = readBE32(file);
const int bitDepth = file.read();
const int colorType = file.read();
const int compression = file.read();
const int filter = file.read();
const int interlace = file.read();
const bool supportedBitDepth =
bitDepth == 8 || ((colorType == PNG_PIXEL_GRAYSCALE || colorType == PNG_PIXEL_INDEXED) &&
(bitDepth == 1 || bitDepth == 2 || bitDepth == 4));
const bool supportedColorType = colorType == PNG_PIXEL_GRAYSCALE || colorType == PNG_PIXEL_TRUECOLOR ||
colorType == PNG_PIXEL_INDEXED || colorType == PNG_PIXEL_GRAY_ALPHA ||
colorType == PNG_PIXEL_TRUECOLOR_ALPHA;
return width > 0 && height > 0 && width <= 2048 && height <= 3072 && width * height <= MAX_SOURCE_PIXELS &&
supportedBitDepth && supportedColorType && compression == 0 && filter == 0 && interlace == 0;
}
BitmapPlacement calculateBitmapPlacement(const int bitmapWidth, const int bitmapHeight, const GfxRenderer& renderer) {
BitmapPlacement placement;
const auto pageWidth = renderer.getScreenWidth();
@@ -382,26 +335,20 @@ void pushRecentSleepIndex(const SleepRecentKind recentKind, const uint16_t idx)
}
}
bool findNextValidSleepImage(HalFile& dir, const SleepRecentKind recentKind, char* name) {
bool findNextValidSleepBmp(HalFile& dir, char* name) {
for (auto dirFile = dir.openNextFile(); dirFile; dirFile = dir.openNextFile()) {
if (dirFile.isDirectory()) continue;
dirFile.getName(name, MAX_SLEEP_FILE_NAME_LEN);
if (name[0] == '\0' || name[0] == '.') continue;
const bool isBmp = FsHelpers::hasBmpExtension(name);
const bool isPng = recentKind == SleepRecentKind::Overlay && FsHelpers::hasPngExtension(std::string_view{name});
if (!isBmp && !isPng) {
if (!FsHelpers::hasBmpExtension(name)) {
LOG_DBG("SLP", "Skipping unsupported sleep image: %s", name);
continue;
}
const bool isValid = isBmp ? [&dirFile]() {
Bitmap bitmap(dirFile);
return bitmap.parseHeaders() == BmpReaderError::Ok;
}()
: isValidPngHeader(dirFile);
if (!isValid) {
Bitmap bitmap(dirFile);
if (bitmap.parseHeaders() != BmpReaderError::Ok) {
LOG_DBG("SLP", "Skipping invalid sleep image: %s", name);
continue;
}
@@ -421,7 +368,7 @@ bool selectRandomSleepFile(const char* dirPath, const SleepRecentKind recentKind
}
uint16_t fileCount = 0;
while (fileCount < UINT16_MAX && findNextValidSleepImage(dir, recentKind, name.get())) ++fileCount;
while (fileCount < UINT16_MAX && findNextValidSleepBmp(dir, name.get())) ++fileCount;
if (fileCount == 0) return false;
// Pick a random wallpaper, excluding recently shown ones.
@@ -436,7 +383,7 @@ bool selectRandomSleepFile(const char* dirPath, const SleepRecentKind recentKind
dir.rewindDirectory();
for (uint16_t index = 0; index <= randomFileIndex; ++index) {
if (!findNextValidSleepImage(dir, recentKind, name.get())) return false;
if (!findNextValidSleepBmp(dir, name.get())) return false;
}
selectedPath.reserve(strlen(dirPath) + 1 + strlen(name.get()));
@@ -448,34 +395,6 @@ bool selectRandomSleepFile(const char* dirPath, const SleepRecentKind recentKind
return true;
}
bool drawSleepPopupPreservingFrame(GfxRenderer& renderer) {
const auto& metrics = UITheme::getInstance().getMetrics();
const int frameThickness = metrics.popupFrameThickness;
const int popupY = static_cast<int>(renderer.getScreenHeight() * metrics.popupTopOffsetRatio);
const int popupHeight = renderer.getLineHeight(UI_12_FONT_ID) + metrics.popupMarginY * 2;
const int bandTop = std::max(0, popupY - frameThickness);
const int bandBottom = std::min(renderer.getScreenHeight(), popupY + popupHeight + frameThickness);
const int bandHeight = bandBottom - bandTop;
const size_t bandBytes = renderer.getRegionByteSize(0, bandTop, renderer.getScreenWidth(), bandHeight);
auto savedBand = makeUniqueNoThrow<uint8_t[]>(bandBytes);
if (!savedBand) {
LOG_ERR("SLP", "OOM: sleep popup background (%u bytes)", static_cast<unsigned>(bandBytes));
return false;
}
if (!renderer.copyRegionToBuffer(0, bandTop, renderer.getScreenWidth(), bandHeight, savedBand.get(), bandBytes)) {
LOG_ERR("SLP", "Failed to save sleep popup background");
return false;
}
GUI.drawPopup(renderer, tr(STR_ENTERING_SLEEP));
if (!renderer.copyBufferToRegion(0, bandTop, renderer.getScreenWidth(), bandHeight, savedBand.get(), bandBytes)) {
LOG_ERR("SLP", "Failed to restore sleep popup background");
return false;
}
return true;
}
} // namespace
void SleepActivity::onEnter() {
@@ -502,10 +421,6 @@ void SleepActivity::onEnter() {
// output-level inversion first so the retained content keeps its visible
// polarity after the display driver returns to normal.
if (frameWasInverted) renderer.invertScreen();
if (APP_STATE.lastSleepFromReader) {
ReaderUtils::applyOrientation(renderer, SETTINGS.orientation);
}
drawSleepPopupPreservingFrame(renderer);
if (APP_STATE.lastSleepFromReader) {
renderer.setOrientation(GfxRenderer::Orientation::Portrait);
}
@@ -670,68 +585,26 @@ bool SleepActivity::renderSleepOverlayFile(HalFile& file, const char* pathForLog
return true;
}
bool SleepActivity::renderTransparentOverlayPng(const std::string& path) const {
ImageDimensions dimensions;
if (!PngToFramebufferConverter::getDimensionsStatic(path, dimensions)) return false;
const auto placement = calculateBitmapPlacement(dimensions.width, dimensions.height, renderer);
RenderConfig config;
config.x = placement.x;
config.y = placement.y;
config.maxWidth = renderer.getScreenWidth();
config.maxHeight = renderer.getScreenHeight();
config.useDithering = false;
config.sourceCropX = placement.cropX;
config.sourceCropY = placement.cropY;
config.useExactDimensions = placement.cropX > 0.0f || placement.cropY > 0.0f;
config.preserveAlpha = true;
PngToFramebufferConverter converter;
LOG_DBG("SLP", "Rendering transparent PNG overlay: %s (%dx%d)", path.c_str(), dimensions.width, dimensions.height);
if (!converter.decodeToFramebuffer(path, renderer, config)) return false;
renderer.displayGrayscaleBase(HalDisplay::HALF_REFRESH);
renderer.clearScreen(0x00);
renderer.setRenderMode(GfxRenderer::GRAYSCALE_LSB);
if (!converter.decodeToFramebuffer(path, renderer, config)) {
renderer.setRenderMode(GfxRenderer::BW);
return true;
}
renderer.copyGrayscaleLsbBuffers();
renderer.clearScreen(0x00);
renderer.setRenderMode(GfxRenderer::GRAYSCALE_MSB);
if (!converter.decodeToFramebuffer(path, renderer, config)) {
renderer.setRenderMode(GfxRenderer::BW);
return true;
}
renderer.copyGrayscaleMsbBuffers();
renderer.displayGrayBuffer();
renderer.setRenderMode(GfxRenderer::BW);
return true;
}
bool SleepActivity::renderSleepOverlayPath(const std::string& path) const {
if (FsHelpers::hasPngExtension(path)) {
return Storage.exists(path.c_str()) && renderTransparentOverlayPng(path);
}
HalFile file;
return Storage.openFileForRead("SLP", path, file) && renderSleepOverlayFile(file, path.c_str());
}
void SleepActivity::renderTransparentCustomSleepScreen() const {
if (renderSleepOverlayPath(TRANSPARENT_SLEEP_ROOT_BMP)) return;
if (renderSleepOverlayPath(TRANSPARENT_SLEEP_ROOT_PNG)) return;
{
HalFile rootFile;
if (Storage.openFileForRead("SLP", TRANSPARENT_SLEEP_ROOT, rootFile)) {
if (renderSleepOverlayFile(rootFile, TRANSPARENT_SLEEP_ROOT)) return;
}
}
std::string selectedPath;
if (!selectRandomSleepFile(TRANSPARENT_SLEEP_DIR, SleepRecentKind::Overlay, selectedPath)) {
selectRandomSleepFile(TRANSPARENT_SLEEP_LEGACY_DIR, SleepRecentKind::Overlay, selectedPath);
}
if (!selectedPath.empty() && renderSleepOverlayPath(selectedPath)) return;
if (!selectedPath.empty()) {
HalFile overlayFile;
if (Storage.openFileForRead("SLP", selectedPath, overlayFile) &&
renderSleepOverlayFile(overlayFile, selectedPath.c_str())) {
return;
}
}
LOG_ERR("SLP", "No valid transparent sleep overlay found");
renderDefaultSleepScreen();
@@ -1,6 +1,4 @@
#pragma once
#include <string>
#include "activities/Activity.h"
class Bitmap;
@@ -18,8 +16,6 @@ class SleepActivity final : public Activity {
void renderCoverSleepScreen() const;
void renderBitmapSleepScreen(const Bitmap& bitmap, bool preserveBackground = false) const;
bool renderSleepOverlayFile(HalFile& file, const char* pathForLog) const;
bool renderTransparentOverlayPng(const std::string& path) const;
bool renderSleepOverlayPath(const std::string& path) const;
void renderLastScreenSleepScreen() const;
void renderTransparentCustomSleepScreen() const;
void renderBlankSleepScreen() const;
+1 -1
View File
@@ -69,7 +69,7 @@ void FileBrowserActivity::loadFiles() {
}
} else if (FsHelpers::hasEpubExtension(filename) || FsHelpers::hasXtcExtension(filename) ||
FsHelpers::hasTxtExtension(filename) || FsHelpers::hasMarkdownExtension(filename) ||
FsHelpers::hasBmpExtension(filename) || FsHelpers::hasPngExtension(filename)) {
FsHelpers::hasBmpExtension(filename)) {
files.emplace_back(filename);
}
}
+2 -4
View File
@@ -26,9 +26,7 @@ bool ReaderActivity::isTxtFile(const std::string& path) {
FsHelpers::hasMarkdownExtension(path); // Treat .md as txt files (until we have a markdown reader)
}
bool ReaderActivity::isImageFile(const std::string& path) {
return FsHelpers::hasBmpExtension(path) || FsHelpers::hasPngExtension(path);
}
bool ReaderActivity::isBmpFile(const std::string& path) { return FsHelpers::hasBmpExtension(path); }
int ReaderActivity::initialRefreshCountdown() const {
if (!allowFastInitialRefresh) return 0;
@@ -154,7 +152,7 @@ void ReaderActivity::onEnter() {
sdFontSystem.ensureLoaded(renderer);
currentBookPath = initialBookPath;
if (isImageFile(initialBookPath)) {
if (isBmpFile(initialBookPath)) {
onGoToBmpViewer(initialBookPath);
} else if (isXtcFile(initialBookPath)) {
auto xtc = loadXtc(initialBookPath);
+1 -1
View File
@@ -18,7 +18,7 @@ class ReaderActivity final : public Activity {
static std::unique_ptr<Txt> loadTxt(const std::string& path);
static bool isXtcFile(const std::string& path);
static bool isTxtFile(const std::string& path);
static bool isImageFile(const std::string& path);
static bool isBmpFile(const std::string& path);
void goToLibrary(const std::string& fromBookPath = "");
void onGoToEpubReader(std::unique_ptr<Epub> epub);
+8 -53
View File
@@ -1,7 +1,6 @@
#include "BmpViewerActivity.h"
#include <Bitmap.h>
#include <Epub/converters/PngToFramebufferConverter.h>
#include <FsHelpers.h>
#include <GfxRenderer.h>
#include <HalStorage.h>
@@ -17,7 +16,6 @@
namespace {
constexpr char CUSTOM_SLEEP_ROOT_BMP[] = "/sleep.bmp";
constexpr char TRANSPARENT_SLEEP_ROOT_BMP[] = "/sleep-overlay.bmp";
constexpr char TRANSPARENT_SLEEP_ROOT_PNG[] = "/sleep-overlay.png";
constexpr size_t COPY_BUFFER_SIZE = 2048;
} // namespace
@@ -46,7 +44,7 @@ void BmpViewerActivity::loadSiblingImages() {
file.getName(name, sizeof(name));
if (name[0] != '.') {
std::string fname(name);
if (FsHelpers::hasBmpExtension(fname) || FsHelpers::hasPngExtension(fname)) {
if (FsHelpers::hasBmpExtension(fname)) {
siblingImages.push_back(fname);
}
}
@@ -65,28 +63,6 @@ void BmpViewerActivity::loadSiblingImages() {
}
}
bool BmpViewerActivity::canSetSleepCover() const {
return FsHelpers::hasBmpExtension(filePath) ||
(SETTINGS.sleepScreen == CrossPointSettings::SLEEP_SCREEN_MODE::TRANSPARENT_CUSTOM &&
FsHelpers::hasPngExtension(filePath));
}
bool BmpViewerActivity::renderPng() {
ImageDimensions dimensions;
if (!PngToFramebufferConverter::getDimensionsStatic(filePath, dimensions)) return false;
if (dimensions.width <= 0 || dimensions.height <= 0) return false;
const float scale = std::min(static_cast<float>(renderer.getScreenWidth()) / dimensions.width,
static_cast<float>(renderer.getScreenHeight()) / dimensions.height);
const int width = std::min(renderer.getScreenWidth(), static_cast<int>(dimensions.width * std::min(scale, 1.0f)));
const int height = std::min(renderer.getScreenHeight(), static_cast<int>(dimensions.height * std::min(scale, 1.0f)));
RenderConfig config{(renderer.getScreenWidth() - width) / 2, (renderer.getScreenHeight() - height) / 2, width,
height};
PngToFramebufferConverter converter;
return converter.decodeToFramebuffer(filePath, renderer, config);
}
void BmpViewerActivity::onEnter() {
Activity::onEnter();
@@ -94,30 +70,13 @@ void BmpViewerActivity::onEnter() {
loadSiblingImages();
}
HalFile file;
const auto pageWidth = renderer.getScreenWidth();
const auto pageHeight = renderer.getScreenHeight();
Rect popupRect = GUI.drawPopup(renderer, tr(STR_LOADING_POPUP));
GUI.fillPopupProgress(renderer, popupRect, 20); // Initial 20% progress
if (FsHelpers::hasPngExtension(filePath)) {
renderer.clearScreen();
const bool hasPrevious = siblingImages.size() > 1 && currentImageIndex > 0;
const bool hasNext = siblingImages.size() > 1 && currentImageIndex != -1 &&
currentImageIndex < static_cast<int>(siblingImages.size()) - 1;
const auto labels = mappedInput.mapLabels(tr(STR_BACK), canSetSleepCover() ? tr(STR_SET_SLEEP_COVER) : "",
hasPrevious ? "<" : "", hasNext ? ">" : "");
if (renderPng()) {
GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
renderer.displayBuffer(HalDisplay::FAST_REFRESH);
} else {
renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2, tr(STR_FILE_OPEN_FAILED));
GUI.drawButtonHints(renderer, labels.btn1, "", "", "");
renderer.displayBuffer(HalDisplay::HALF_REFRESH);
}
return;
}
HalFile file;
// 1. Open the BMP file
// 1. Open the file
if (Storage.openFileForRead("BMP", filePath, file)) {
Bitmap bitmap(file, true);
@@ -149,8 +108,8 @@ void BmpViewerActivity::onEnter() {
bool hasNext = (siblingImages.size() > 1 && currentImageIndex != -1 &&
currentImageIndex < static_cast<int>(siblingImages.size()) - 1);
const auto labels = mappedInput.mapLabels(tr(STR_BACK), canSetSleepCover() ? tr(STR_SET_SLEEP_COVER) : "",
(hasPrevious ? "<" : ""), (hasNext ? ">" : ""));
const auto labels =
mappedInput.mapLabels(tr(STR_BACK), tr(STR_SET_SLEEP_COVER), (hasPrevious ? "<" : ""), (hasNext ? ">" : ""));
GUI.fillPopupProgress(renderer, popupRect, 50);
@@ -195,11 +154,7 @@ void BmpViewerActivity::doSetSleepCover() {
GUI.drawPopup(renderer, tr(STR_LOADING_POPUP));
const bool transparentMode = SETTINGS.sleepScreen == CrossPointSettings::SLEEP_SCREEN_MODE::TRANSPARENT_CUSTOM;
if (!canSetSleepCover()) return;
const char* destination =
transparentMode ? (FsHelpers::hasPngExtension(filePath) ? TRANSPARENT_SLEEP_ROOT_PNG : TRANSPARENT_SLEEP_ROOT_BMP)
: CUSTOM_SLEEP_ROOT_BMP;
const char* destination = transparentMode ? TRANSPARENT_SLEEP_ROOT_BMP : CUSTOM_SLEEP_ROOT_BMP;
bool success = filePath == destination;
if (!success) {
@@ -271,7 +226,7 @@ void BmpViewerActivity::loop() {
}
if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) {
if (canSetSleepCover()) doSetSleepCover();
doSetSleepCover();
return;
}
+1 -3
View File
@@ -17,10 +17,8 @@ class BmpViewerActivity final : public Activity {
private:
void loadSiblingImages();
void doSetSleepCover();
bool canSetSleepCover() const;
bool renderPng();
std::string filePath;
std::vector<std::string> siblingImages;
int currentImageIndex = -1;
};
};
+1 -1
View File
@@ -139,7 +139,7 @@ UIIcon UITheme::getFileIcon(const std::string& filename) {
if (FsHelpers::hasTxtExtension(filename) || FsHelpers::hasMarkdownExtension(filename)) {
return Text;
}
if (FsHelpers::hasBmpExtension(filename) || FsHelpers::hasPngExtension(filename)) {
if (FsHelpers::hasBmpExtension(filename)) {
return Image;
}
return File;