(Conditionally) add further dithering algorithms

This commit is contained in:
jpirnay
2026-04-06 17:23:29 +02:00
parent 0ba4626157
commit 4f031b00b1
8 changed files with 102 additions and 6 deletions
+2
View File
@@ -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
@@ -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<ImageDitherMode>(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:
@@ -1,6 +1,8 @@
#include "JpegToFramebufferConverter.h"
#ifdef ENABLE_IMAGE_DITHERING_EXTENSION
#include <BitmapHelpers.h>
#endif
#include <FsHelpers.h>
#include <GfxRenderer.h>
#include <HalStorage.h>
@@ -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();
@@ -1,6 +1,8 @@
#include "PngToFramebufferConverter.h"
#ifdef ENABLE_IMAGE_DITHERING_EXTENSION
#include <BitmapHelpers.h>
#endif
#include <FsHelpers.h>
#include <GfxRenderer.h>
#include <HalStorage.h>
@@ -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();