mirror of
https://github.com/crosspoint-reader/crosspoint-reader.git
synced 2026-08-13 14:21:57 -07:00
Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d09c755cf2 | ||
|
|
bccb4cc43f | ||
|
|
f4378bae4f | ||
|
|
7ab9c21afc |
No files matched your search
+6
-6
@@ -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.
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 картата и го избери"
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
};
|
||||
@@ -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;
|
||||
|
||||
Reference in new issue
Block a user