Add dithering algorithms
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
#include "JpegToFramebufferConverter.h"
|
||||
|
||||
#include <BitmapHelpers.h>
|
||||
#include <FsHelpers.h>
|
||||
#include <GfxRenderer.h>
|
||||
#include <HalStorage.h>
|
||||
@@ -39,6 +40,10 @@ struct JpegContext {
|
||||
PixelCache cache;
|
||||
bool caching;
|
||||
|
||||
int currentDitherRow;
|
||||
AtkinsonDitherer* atkinsonDitherer;
|
||||
DiffusedBayerDitherer* diffusedBayerDitherer;
|
||||
|
||||
JpegContext()
|
||||
: renderer(nullptr),
|
||||
config(nullptr),
|
||||
@@ -50,9 +55,59 @@ struct JpegContext {
|
||||
dstHeight(0),
|
||||
fineScaleFP(1 << 16),
|
||||
invScaleFP(1 << 16),
|
||||
caching(false) {}
|
||||
caching(false),
|
||||
currentDitherRow(-1),
|
||||
atkinsonDitherer(nullptr),
|
||||
diffusedBayerDitherer(nullptr) {}
|
||||
|
||||
~JpegContext() {
|
||||
delete atkinsonDitherer;
|
||||
delete diffusedBayerDitherer;
|
||||
}
|
||||
};
|
||||
|
||||
void prepareDitherRow(JpegContext& ctx, int dstY) {
|
||||
if (!ctx.config || !ctx.config->useDithering) return;
|
||||
|
||||
if (ctx.currentDitherRow == -1 || dstY < ctx.currentDitherRow) {
|
||||
if (ctx.atkinsonDitherer) ctx.atkinsonDitherer->reset();
|
||||
if (ctx.diffusedBayerDitherer) ctx.diffusedBayerDitherer->reset();
|
||||
ctx.currentDitherRow = dstY;
|
||||
return;
|
||||
}
|
||||
|
||||
while (ctx.currentDitherRow < dstY) {
|
||||
if (ctx.atkinsonDitherer) ctx.atkinsonDitherer->nextRow();
|
||||
if (ctx.diffusedBayerDitherer) ctx.diffusedBayerDitherer->nextRow();
|
||||
ctx.currentDitherRow++;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t ditherGray(JpegContext& ctx, uint8_t gray, int localX, int outX, int outY) {
|
||||
if (!ctx.config || !ctx.config->useDithering) {
|
||||
return quantizeGray4Level(gray);
|
||||
}
|
||||
|
||||
switch (ctx.config->ditherMode) {
|
||||
case ImageDitherMode::Atkinson:
|
||||
if (ctx.atkinsonDitherer) {
|
||||
return ctx.atkinsonDitherer->processPixel(gray, localX);
|
||||
}
|
||||
break;
|
||||
case ImageDitherMode::DiffusedBayer:
|
||||
if (ctx.diffusedBayerDitherer) {
|
||||
return ctx.diffusedBayerDitherer->processPixel(gray, localX, outX, outY);
|
||||
}
|
||||
break;
|
||||
case ImageDitherMode::Bayer:
|
||||
case ImageDitherMode::COUNT:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return applyBayerDither4Level(gray, outX, outY);
|
||||
}
|
||||
|
||||
// File I/O callbacks use pFile->fHandle to access the FsFile*,
|
||||
// avoiding the need for global file state.
|
||||
void* jpegOpen(const char* filename, int32_t* size) {
|
||||
@@ -136,7 +191,6 @@ int jpegDrawCallback(JPEGDRAW* pDraw) {
|
||||
|
||||
if (stride <= 0 || blockH <= 0 || validW <= 0) return 1;
|
||||
|
||||
const bool useDithering = ctx->config->useDithering;
|
||||
const bool caching = ctx->caching;
|
||||
const int32_t fineScaleFP = ctx->fineScaleFP;
|
||||
const int32_t invScaleFP = ctx->invScaleFP;
|
||||
@@ -181,19 +235,14 @@ int jpegDrawCallback(JPEGDRAW* pDraw) {
|
||||
if (fineScaleFP == FP_ONE) {
|
||||
for (int dstY = dstYStart; dstY < dstYEnd; dstY++) {
|
||||
const int outY = cfgY + dstY;
|
||||
prepareDitherRow(*ctx, dstY);
|
||||
pw.beginRow(outY);
|
||||
if (caching) cw.beginRow(outY, ctx->config->y);
|
||||
const uint8_t* row = &pixels[(dstY - blockY) * stride];
|
||||
for (int dstX = dstXStart; dstX < dstXEnd; dstX++) {
|
||||
const int outX = cfgX + dstX;
|
||||
uint8_t gray = row[dstX - blockX];
|
||||
uint8_t dithered;
|
||||
if (useDithering) {
|
||||
dithered = applyBayerDither4Level(gray, outX, outY);
|
||||
} else {
|
||||
dithered = gray / 85;
|
||||
if (dithered > 3) dithered = 3;
|
||||
}
|
||||
uint8_t dithered = ditherGray(*ctx, gray, dstX, outX, outY);
|
||||
pw.writePixel(outX, dithered);
|
||||
if (caching) cw.writePixel(outX, dithered);
|
||||
}
|
||||
@@ -215,6 +264,7 @@ int jpegDrawCallback(JPEGDRAW* pDraw) {
|
||||
|
||||
for (int dstY = dstYStart; dstY < dstYEnd; dstY++) {
|
||||
const int outY = cfgY + dstY;
|
||||
prepareDitherRow(*ctx, dstY);
|
||||
pw.beginRow(outY);
|
||||
if (caching) cw.beginRow(outY, ctx->config->y);
|
||||
const int32_t srcFyFP = dstY * invScaleFP;
|
||||
@@ -246,13 +296,7 @@ int jpegDrawCallback(JPEGDRAW* pDraw) {
|
||||
int bot = ((int)row1[lx0] * fxInv + (int)row1[lx1] * fx) >> FP_SHIFT;
|
||||
uint8_t gray = (uint8_t)((top * fyInv + bot * fy) >> FP_SHIFT);
|
||||
|
||||
uint8_t dithered;
|
||||
if (useDithering) {
|
||||
dithered = applyBayerDither4Level(gray, outX, outY);
|
||||
} else {
|
||||
dithered = gray / 85;
|
||||
if (dithered > 3) dithered = 3;
|
||||
}
|
||||
uint8_t dithered = ditherGray(*ctx, gray, dstX, outX, outY);
|
||||
pw.writePixel(outX, dithered);
|
||||
if (caching) cw.writePixel(outX, dithered);
|
||||
}
|
||||
@@ -269,13 +313,7 @@ int jpegDrawCallback(JPEGDRAW* pDraw) {
|
||||
int bot = ((int)row1[lx0] * fxInv + (int)row1[lx0 + 1] * fx) >> FP_SHIFT;
|
||||
uint8_t gray = (uint8_t)((top * fyInv + bot * fy) >> FP_SHIFT);
|
||||
|
||||
uint8_t dithered;
|
||||
if (useDithering) {
|
||||
dithered = applyBayerDither4Level(gray, outX, outY);
|
||||
} else {
|
||||
dithered = gray / 85;
|
||||
if (dithered > 3) dithered = 3;
|
||||
}
|
||||
uint8_t dithered = ditherGray(*ctx, gray, dstX, outX, outY);
|
||||
pw.writePixel(outX, dithered);
|
||||
if (caching) cw.writePixel(outX, dithered);
|
||||
}
|
||||
@@ -295,13 +333,7 @@ int jpegDrawCallback(JPEGDRAW* pDraw) {
|
||||
int bot = ((int)row1[lx0] * fxInv + (int)row1[lx1] * fx) >> FP_SHIFT;
|
||||
uint8_t gray = (uint8_t)((top * fyInv + bot * fy) >> FP_SHIFT);
|
||||
|
||||
uint8_t dithered;
|
||||
if (useDithering) {
|
||||
dithered = applyBayerDither4Level(gray, outX, outY);
|
||||
} else {
|
||||
dithered = gray / 85;
|
||||
if (dithered > 3) dithered = 3;
|
||||
}
|
||||
uint8_t dithered = ditherGray(*ctx, gray, dstX, outX, outY);
|
||||
pw.writePixel(outX, dithered);
|
||||
if (caching) cw.writePixel(outX, dithered);
|
||||
}
|
||||
@@ -312,6 +344,7 @@ int jpegDrawCallback(JPEGDRAW* pDraw) {
|
||||
// === Nearest-neighbor (downscale: fineScale < 1.0) ===
|
||||
for (int dstY = dstYStart; dstY < dstYEnd; dstY++) {
|
||||
const int outY = cfgY + dstY;
|
||||
prepareDitherRow(*ctx, dstY);
|
||||
pw.beginRow(outY);
|
||||
if (caching) cw.beginRow(outY, ctx->config->y);
|
||||
const int32_t srcFyFP = dstY * invScaleFP;
|
||||
@@ -328,13 +361,7 @@ int jpegDrawCallback(JPEGDRAW* pDraw) {
|
||||
if (lx >= validW) lx = validW - 1;
|
||||
uint8_t gray = row[lx];
|
||||
|
||||
uint8_t dithered;
|
||||
if (useDithering) {
|
||||
dithered = applyBayerDither4Level(gray, outX, outY);
|
||||
} else {
|
||||
dithered = gray / 85;
|
||||
if (dithered > 3) dithered = 3;
|
||||
}
|
||||
uint8_t dithered = ditherGray(*ctx, gray, dstX, outX, outY);
|
||||
pw.writePixel(outX, dithered);
|
||||
if (caching) cw.writePixel(outX, dithered);
|
||||
}
|
||||
@@ -479,6 +506,27 @@ bool JpegToFramebufferConverter::decodeToFramebuffer(const std::string& imagePat
|
||||
}
|
||||
}
|
||||
|
||||
if (config.useDithering) {
|
||||
switch (config.ditherMode) {
|
||||
case ImageDitherMode::Atkinson:
|
||||
ctx.atkinsonDitherer = new (std::nothrow) AtkinsonDitherer(destWidth);
|
||||
if (!ctx.atkinsonDitherer) {
|
||||
LOG_ERR("JPG", "Failed to allocate Atkinson ditherer, falling back to Bayer");
|
||||
}
|
||||
break;
|
||||
case ImageDitherMode::DiffusedBayer:
|
||||
ctx.diffusedBayerDitherer = new (std::nothrow) DiffusedBayerDitherer(destWidth);
|
||||
if (!ctx.diffusedBayerDitherer) {
|
||||
LOG_ERR("JPG", "Failed to allocate diffused Bayer ditherer, falling back to Bayer");
|
||||
}
|
||||
break;
|
||||
case ImageDitherMode::Bayer:
|
||||
case ImageDitherMode::COUNT:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
unsigned long decodeStart = millis();
|
||||
rc = jpeg->decode(0, 0, jpegScaleOption);
|
||||
unsigned long decodeTime = millis() - decodeStart;
|
||||
|
||||
Reference in New Issue
Block a user