diff --git a/lib/Epub/Epub/converters/DitherUtils.h b/lib/Epub/Epub/converters/DitherUtils.h index 8107f2eb..19de605c 100644 --- a/lib/Epub/Epub/converters/DitherUtils.h +++ b/lib/Epub/Epub/converters/DitherUtils.h @@ -32,6 +32,7 @@ inline uint8_t applyBayerDither4Level(uint8_t gray, int x, int y) { return quantizeGray4Level((uint8_t)adjusted); } +#ifdef ENABLE_IMAGE_DITHERING_EXTENSION class DiffusedBayerDitherer { public: explicit DiffusedBayerDitherer(int width) : width(width) { @@ -85,3 +86,4 @@ class DiffusedBayerDitherer { int16_t* errorCurRow; int16_t* errorNextRow; }; +#endif diff --git a/lib/Epub/Epub/converters/ImageToFramebufferDecoder.h b/lib/Epub/Epub/converters/ImageToFramebufferDecoder.h index 0bdc4a86..c938785d 100644 --- a/lib/Epub/Epub/converters/ImageToFramebufferDecoder.h +++ b/lib/Epub/Epub/converters/ImageToFramebufferDecoder.h @@ -13,12 +13,15 @@ struct ImageDimensions { enum class ImageDitherMode : uint8_t { Bayer = 0, +#ifdef ENABLE_IMAGE_DITHERING_EXTENSION Atkinson = 1, DiffusedBayer = 2, +#endif COUNT, }; inline ImageDitherMode imageDitherModeFromSetting(uint8_t value) { +#ifdef ENABLE_IMAGE_DITHERING_EXTENSION switch (static_cast(value)) { case ImageDitherMode::Bayer: case ImageDitherMode::Atkinson: @@ -28,14 +31,20 @@ inline ImageDitherMode imageDitherModeFromSetting(uint8_t value) { default: return ImageDitherMode::Bayer; } +#else + (void)value; + return ImageDitherMode::Bayer; +#endif } inline const char* getImageDitherCacheSuffix(ImageDitherMode mode) { switch (mode) { +#ifdef ENABLE_IMAGE_DITHERING_EXTENSION case ImageDitherMode::Atkinson: return ".atkinson"; case ImageDitherMode::DiffusedBayer: return ".diffused-bayer"; +#endif case ImageDitherMode::Bayer: case ImageDitherMode::COUNT: default: diff --git a/lib/Epub/Epub/converters/JpegToFramebufferConverter.cpp b/lib/Epub/Epub/converters/JpegToFramebufferConverter.cpp index f76613a6..e8394f84 100644 --- a/lib/Epub/Epub/converters/JpegToFramebufferConverter.cpp +++ b/lib/Epub/Epub/converters/JpegToFramebufferConverter.cpp @@ -1,6 +1,8 @@ #include "JpegToFramebufferConverter.h" +#ifdef ENABLE_IMAGE_DITHERING_EXTENSION #include +#endif #include #include #include @@ -40,9 +42,11 @@ struct JpegContext { PixelCache cache; bool caching; +#ifdef ENABLE_IMAGE_DITHERING_EXTENSION int currentDitherRow; AtkinsonDitherer* atkinsonDitherer; DiffusedBayerDitherer* diffusedBayerDitherer; +#endif JpegContext() : renderer(nullptr), @@ -55,17 +59,25 @@ struct JpegContext { dstHeight(0), fineScaleFP(1 << 16), invScaleFP(1 << 16), - caching(false), + caching(false) +#ifdef ENABLE_IMAGE_DITHERING_EXTENSION + , currentDitherRow(-1), atkinsonDitherer(nullptr), - diffusedBayerDitherer(nullptr) {} + diffusedBayerDitherer(nullptr) +#endif + { + } +#ifdef ENABLE_IMAGE_DITHERING_EXTENSION ~JpegContext() { delete atkinsonDitherer; delete diffusedBayerDitherer; } +#endif }; +#ifdef ENABLE_IMAGE_DITHERING_EXTENSION void prepareDitherRow(JpegContext& ctx, int dstY) { if (!ctx.config || !ctx.config->useDithering) return; @@ -107,6 +119,13 @@ uint8_t ditherGray(JpegContext& ctx, uint8_t gray, int localX, int outX, int out return applyBayerDither4Level(gray, outX, outY); } +#else +uint8_t ditherGray(JpegContext& ctx, uint8_t gray, int localX, int outX, int outY) { + (void)ctx; + (void)localX; + return applyBayerDither4Level(gray, outX, outY); +} +#endif // File I/O callbacks use pFile->fHandle to access the FsFile*, // avoiding the need for global file state. @@ -235,7 +254,9 @@ int jpegDrawCallback(JPEGDRAW* pDraw) { if (fineScaleFP == FP_ONE) { for (int dstY = dstYStart; dstY < dstYEnd; dstY++) { const int outY = cfgY + dstY; +#ifdef ENABLE_IMAGE_DITHERING_EXTENSION prepareDitherRow(*ctx, dstY); +#endif pw.beginRow(outY); if (caching) cw.beginRow(outY, ctx->config->y); const uint8_t* row = &pixels[(dstY - blockY) * stride]; @@ -264,7 +285,9 @@ int jpegDrawCallback(JPEGDRAW* pDraw) { for (int dstY = dstYStart; dstY < dstYEnd; dstY++) { const int outY = cfgY + dstY; +#ifdef ENABLE_IMAGE_DITHERING_EXTENSION prepareDitherRow(*ctx, dstY); +#endif pw.beginRow(outY); if (caching) cw.beginRow(outY, ctx->config->y); const int32_t srcFyFP = dstY * invScaleFP; @@ -344,7 +367,9 @@ int jpegDrawCallback(JPEGDRAW* pDraw) { // === Nearest-neighbor (downscale: fineScale < 1.0) === for (int dstY = dstYStart; dstY < dstYEnd; dstY++) { const int outY = cfgY + dstY; +#ifdef ENABLE_IMAGE_DITHERING_EXTENSION prepareDitherRow(*ctx, dstY); +#endif pw.beginRow(outY); if (caching) cw.beginRow(outY, ctx->config->y); const int32_t srcFyFP = dstY * invScaleFP; @@ -507,6 +532,7 @@ bool JpegToFramebufferConverter::decodeToFramebuffer(const std::string& imagePat } if (config.useDithering) { +#ifdef ENABLE_IMAGE_DITHERING_EXTENSION switch (config.ditherMode) { case ImageDitherMode::Atkinson: ctx.atkinsonDitherer = new (std::nothrow) AtkinsonDitherer(destWidth); @@ -525,6 +551,7 @@ bool JpegToFramebufferConverter::decodeToFramebuffer(const std::string& imagePat default: break; } +#endif } unsigned long decodeStart = millis(); diff --git a/lib/Epub/Epub/converters/PngToFramebufferConverter.cpp b/lib/Epub/Epub/converters/PngToFramebufferConverter.cpp index 7ef7826a..e9aa0aad 100644 --- a/lib/Epub/Epub/converters/PngToFramebufferConverter.cpp +++ b/lib/Epub/Epub/converters/PngToFramebufferConverter.cpp @@ -1,6 +1,8 @@ #include "PngToFramebufferConverter.h" +#ifdef ENABLE_IMAGE_DITHERING_EXTENSION #include +#endif #include #include #include @@ -37,9 +39,11 @@ struct PngContext { bool caching; uint8_t* grayLineBuffer; +#ifdef ENABLE_IMAGE_DITHERING_EXTENSION int currentDitherRow; AtkinsonDitherer* atkinsonDitherer; DiffusedBayerDitherer* diffusedBayerDitherer; +#endif PngContext() : renderer(nullptr), @@ -53,17 +57,25 @@ struct PngContext { dstHeight(0), lastDstY(-1), caching(false), - grayLineBuffer(nullptr), + grayLineBuffer(nullptr) +#ifdef ENABLE_IMAGE_DITHERING_EXTENSION + , currentDitherRow(-1), atkinsonDitherer(nullptr), - diffusedBayerDitherer(nullptr) {} + diffusedBayerDitherer(nullptr) +#endif + { + } +#ifdef ENABLE_IMAGE_DITHERING_EXTENSION ~PngContext() { delete atkinsonDitherer; delete diffusedBayerDitherer; } +#endif }; +#ifdef ENABLE_IMAGE_DITHERING_EXTENSION void prepareDitherRow(PngContext& ctx, int dstY) { if (!ctx.config || !ctx.config->useDithering) return; @@ -105,6 +117,13 @@ uint8_t ditherGray(PngContext& ctx, uint8_t gray, int localX, int outX, int outY return applyBayerDither4Level(gray, outX, outY); } +#else +uint8_t ditherGray(PngContext& ctx, uint8_t gray, int localX, int outX, int outY) { + (void)ctx; + (void)localX; + return applyBayerDither4Level(gray, outX, outY); +} +#endif // File I/O callbacks use pFile->fHandle to access the FsFile*, // avoiding the need for global file state. @@ -272,7 +291,9 @@ int pngDrawCallback(PNGDRAW* pDraw) { cw.beginRow(outY, ctx->config->y); } +#ifdef ENABLE_IMAGE_DITHERING_EXTENSION prepareDitherRow(*ctx, dstY); +#endif int srcX = 0; int error = 0; @@ -435,6 +456,7 @@ bool PngToFramebufferConverter::decodeToFramebuffer(const std::string& imagePath } if (config.useDithering) { +#ifdef ENABLE_IMAGE_DITHERING_EXTENSION switch (config.ditherMode) { case ImageDitherMode::Atkinson: ctx.atkinsonDitherer = new (std::nothrow) AtkinsonDitherer(ctx.dstWidth); @@ -453,6 +475,7 @@ bool PngToFramebufferConverter::decodeToFramebuffer(const std::string& imagePath default: break; } +#endif } unsigned long decodeStart = millis(); diff --git a/src/CrossPointSettings.h b/src/CrossPointSettings.h index 7d1a3484..7c6ea750 100644 --- a/src/CrossPointSettings.h +++ b/src/CrossPointSettings.h @@ -137,12 +137,16 @@ class CrossPointSettings { // Image rendering in EPUB reader enum IMAGE_RENDERING { IMAGES_DISPLAY = 0, IMAGES_PLACEHOLDER = 1, IMAGES_SUPPRESS = 2, IMAGE_RENDERING_COUNT }; +#ifdef ENABLE_IMAGE_DITHERING_EXTENSION enum IMAGE_DITHERING { IMAGE_DITHER_BAYER = 0, IMAGE_DITHER_ATKINSON = 1, IMAGE_DITHER_DIFFUSED_BAYER = 2, IMAGE_DITHERING_COUNT }; +#else + enum IMAGE_DITHERING { IMAGE_DITHER_BAYER = 0, IMAGE_DITHERING_COUNT }; +#endif // Timezone options (POSIX TZ rules for DST support) enum TIMEZONE { diff --git a/src/SettingsList.h b/src/SettingsList.h index a891b45e..8bfa98e3 100644 --- a/src/SettingsList.h +++ b/src/SettingsList.h @@ -69,10 +69,12 @@ inline const std::vector& getSettingsList() { SettingInfo::Enum(StrId::STR_IMAGES, &CrossPointSettings::imageRendering, {StrId::STR_IMAGES_DISPLAY, StrId::STR_IMAGES_PLACEHOLDER, StrId::STR_IMAGES_SUPPRESS}, "imageRendering", StrId::STR_CAT_READER), +#ifdef ENABLE_IMAGE_DITHERING_EXTENSION SettingInfo::Enum( StrId::STR_IMAGE_DITHERING, &CrossPointSettings::imageDithering, {StrId::STR_IMAGE_DITHER_BAYER, StrId::STR_IMAGE_DITHER_ATKINSON, StrId::STR_IMAGE_DITHER_DIFFUSED_BAYER}, "imageDithering", StrId::STR_CAT_READER), +#endif SettingInfo::Toggle(StrId::STR_CREATE_FALLBACK_FOR_INVALID_TOC, &CrossPointSettings::syntheticTocFallback, "syntheticTocFallback", StrId::STR_CAT_READER), // --- Controls --- diff --git a/src/activities/util/BmpViewerActivity.cpp b/src/activities/util/BmpViewerActivity.cpp index f8bd8a6c..b0126c24 100644 --- a/src/activities/util/BmpViewerActivity.cpp +++ b/src/activities/util/BmpViewerActivity.cpp @@ -20,7 +20,9 @@ constexpr const char* SLEEP_BMP_PATH = "/sleep.bmp"; constexpr const char* SLEEP_BMP_TMP_PATH = "/sleep.bmp.tmp"; constexpr const char* SLEEP_BMP_BACKUP_PATH = "/sleep.bmp.bak"; +#ifdef ENABLE_IMAGE_DITHERING_EXTENSION uint8_t normalizeImageDitherModeValue(uint8_t mode) { return static_cast(imageDitherModeFromSetting(mode)); } +#endif bool isBmpFile(const std::string& path) { return FsHelpers::hasBmpExtension(path); } @@ -87,8 +89,15 @@ bool replaceSleepBmpFromTemp() { BmpViewerActivity::BmpViewerActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, std::string path) : Activity("BmpViewer", renderer, mappedInput), - filePath(std::move(path)), - imageDitherMode(normalizeImageDitherModeValue(SETTINGS.imageDithering)) {} + filePath(std::move(path)) +#ifdef ENABLE_IMAGE_DITHERING_EXTENSION + , + imageDitherMode(normalizeImageDitherModeValue(SETTINGS.imageDithering)) { +} +#else +{ +} +#endif bool BmpViewerActivity::renderCurrentImage(const bool showControls) { return isBmpFile(filePath) ? renderBmpImage(showControls) : renderDecodedImage(showControls); @@ -139,8 +148,12 @@ bool BmpViewerActivity::renderBmpImage(const bool showControls) { renderer.clearScreen(); renderer.drawBitmap(bitmap, x, y, pageWidth, pageHeight, 0, 0); if (showControls) { +#ifdef ENABLE_IMAGE_DITHERING_EXTENSION const auto labels = mappedInput.mapLabels(tr(STR_BACK), "", I18N.get(getCurrentDitherModeLabel()), tr(STR_SET_SLEEP_SCREEN)); +#else + const auto labels = mappedInput.mapLabels(tr(STR_BACK), "", "", tr(STR_SET_SLEEP_SCREEN)); +#endif GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4); } renderer.displayBuffer(HalDisplay::HALF_REFRESH); @@ -179,21 +192,30 @@ bool BmpViewerActivity::renderDecodedImage(const bool showControls) { config.useExactDimensions = true; config.useGrayscale = true; config.useDithering = true; +#ifdef ENABLE_IMAGE_DITHERING_EXTENSION config.ditherMode = imageDitherModeFromSetting(imageDitherMode); +#else + config.ditherMode = ImageDitherMode::Bayer; +#endif if (!decoder->decodeToFramebuffer(filePath, renderer, config)) { return false; } if (showControls) { +#ifdef ENABLE_IMAGE_DITHERING_EXTENSION const auto labels = mappedInput.mapLabels(tr(STR_BACK), "", I18N.get(getCurrentDitherModeLabel()), tr(STR_SET_SLEEP_SCREEN)); +#else + const auto labels = mappedInput.mapLabels(tr(STR_BACK), "", "", tr(STR_SET_SLEEP_SCREEN)); +#endif GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4); } renderer.displayBuffer(HalDisplay::HALF_REFRESH); return true; } +#ifdef ENABLE_IMAGE_DITHERING_EXTENSION StrId BmpViewerActivity::getCurrentDitherModeLabel() const { switch (imageDitherModeFromSetting(imageDitherMode)) { case ImageDitherMode::Atkinson: @@ -216,6 +238,7 @@ void BmpViewerActivity::cycleDitherMode() { renderError("Could not render image"); } } +#endif void BmpViewerActivity::renderError(const char* message) { const auto pageHeight = renderer.getScreenHeight(); @@ -278,10 +301,12 @@ void BmpViewerActivity::loop() { return; } +#ifdef ENABLE_IMAGE_DITHERING_EXTENSION if (mappedInput.wasReleased(MappedInputManager::Button::Left)) { cycleDitherMode(); return; } +#endif // Next/Right button: set this image as the sleep screen if (mappedInput.wasReleased(MappedInputManager::Button::Right)) { diff --git a/src/activities/util/BmpViewerActivity.h b/src/activities/util/BmpViewerActivity.h index 5fc1f692..4e88166a 100644 --- a/src/activities/util/BmpViewerActivity.h +++ b/src/activities/util/BmpViewerActivity.h @@ -19,12 +19,16 @@ class BmpViewerActivity final : public Activity { private: std::string filePath; +#ifdef ENABLE_IMAGE_DITHERING_EXTENSION uint8_t imageDitherMode; +#endif bool renderCurrentImage(bool showControls = true); bool renderBmpImage(bool showControls = true); bool renderDecodedImage(bool showControls = true); +#ifdef ENABLE_IMAGE_DITHERING_EXTENSION void cycleDitherMode(); StrId getCurrentDitherModeLabel() const; +#endif void renderError(const char* message); void setAsSleepScreen(); };