Align png rendering to upstream
This commit is contained in:
@@ -1,8 +1,6 @@
|
|||||||
#include "PngToFramebufferConverter.h"
|
#include "PngToFramebufferConverter.h"
|
||||||
|
|
||||||
#ifdef ENABLE_IMAGE_DITHERING_EXTENSION
|
|
||||||
#include <BitmapHelpers.h>
|
#include <BitmapHelpers.h>
|
||||||
#endif
|
|
||||||
#include <FsHelpers.h>
|
#include <FsHelpers.h>
|
||||||
#include <GfxRenderer.h>
|
#include <GfxRenderer.h>
|
||||||
#include <HalStorage.h>
|
#include <HalStorage.h>
|
||||||
@@ -39,6 +37,16 @@ struct PngContext {
|
|||||||
bool caching;
|
bool caching;
|
||||||
|
|
||||||
uint8_t* grayLineBuffer;
|
uint8_t* grayLineBuffer;
|
||||||
|
|
||||||
|
// When the renderer is in BW mode the framebuffer is 1 bpp and the
|
||||||
|
// DirectPixelWriter collapses any value < 3 to black. The 4-level dither
|
||||||
|
// path therefore turns mid-grays into solid black. In that case we run a
|
||||||
|
// proper 1-bit Atkinson dither (matching PngToBmpConverter's BW path) and
|
||||||
|
// emit only values 0 or 3, which round-trip cleanly through the BW writer.
|
||||||
|
bool renderModeIsBW;
|
||||||
|
int oneBitDitherRow;
|
||||||
|
Atkinson1BitDitherer* atkinson1BitDitherer;
|
||||||
|
|
||||||
#ifdef ENABLE_IMAGE_DITHERING_EXTENSION
|
#ifdef ENABLE_IMAGE_DITHERING_EXTENSION
|
||||||
int currentDitherRow;
|
int currentDitherRow;
|
||||||
AtkinsonDitherer* atkinsonDitherer;
|
AtkinsonDitherer* atkinsonDitherer;
|
||||||
@@ -57,7 +65,10 @@ struct PngContext {
|
|||||||
dstHeight(0),
|
dstHeight(0),
|
||||||
lastDstY(-1),
|
lastDstY(-1),
|
||||||
caching(false),
|
caching(false),
|
||||||
grayLineBuffer(nullptr)
|
grayLineBuffer(nullptr),
|
||||||
|
renderModeIsBW(false),
|
||||||
|
oneBitDitherRow(-1),
|
||||||
|
atkinson1BitDitherer(nullptr)
|
||||||
#ifdef ENABLE_IMAGE_DITHERING_EXTENSION
|
#ifdef ENABLE_IMAGE_DITHERING_EXTENSION
|
||||||
,
|
,
|
||||||
currentDitherRow(-1),
|
currentDitherRow(-1),
|
||||||
@@ -67,14 +78,33 @@ struct PngContext {
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef ENABLE_IMAGE_DITHERING_EXTENSION
|
|
||||||
~PngContext() {
|
~PngContext() {
|
||||||
|
delete atkinson1BitDitherer;
|
||||||
|
#ifdef ENABLE_IMAGE_DITHERING_EXTENSION
|
||||||
delete atkinsonDitherer;
|
delete atkinsonDitherer;
|
||||||
delete diffusedBayerDitherer;
|
delete diffusedBayerDitherer;
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Advance the 1-bit Atkinson ditherer to the requested destination row.
|
||||||
|
// Like the 4-level path below, this handles re-decode passes that walk source
|
||||||
|
// rows non-monotonically by resetting and replaying when needed.
|
||||||
|
void prepareOneBitDitherRow(PngContext& ctx, int dstY) {
|
||||||
|
if (!ctx.atkinson1BitDitherer) return;
|
||||||
|
|
||||||
|
if (ctx.oneBitDitherRow == -1 || dstY < ctx.oneBitDitherRow) {
|
||||||
|
ctx.atkinson1BitDitherer->reset();
|
||||||
|
ctx.oneBitDitherRow = dstY;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (ctx.oneBitDitherRow < dstY) {
|
||||||
|
ctx.atkinson1BitDitherer->nextRow();
|
||||||
|
ctx.oneBitDitherRow++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef ENABLE_IMAGE_DITHERING_EXTENSION
|
#ifdef ENABLE_IMAGE_DITHERING_EXTENSION
|
||||||
void prepareDitherRow(PngContext& ctx, int dstY) {
|
void prepareDitherRow(PngContext& ctx, int dstY) {
|
||||||
if (!ctx.config || !ctx.config->useDithering) return;
|
if (!ctx.config || !ctx.config->useDithering) return;
|
||||||
@@ -94,6 +124,12 @@ void prepareDitherRow(PngContext& ctx, int dstY) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
uint8_t ditherGray(PngContext& ctx, uint8_t gray, int localX, int outX, int outY) {
|
uint8_t ditherGray(PngContext& ctx, uint8_t gray, int localX, int outX, int outY) {
|
||||||
|
// BW mode: route through 1-bit Atkinson and emit only 0/3 so the
|
||||||
|
// DirectPixelWriter's `pixelValue < 3` rule maps cleanly to black/white.
|
||||||
|
if (ctx.atkinson1BitDitherer) {
|
||||||
|
return ctx.atkinson1BitDitherer->processPixel(gray, localX) ? 3 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
if (!ctx.config || !ctx.config->useDithering) {
|
if (!ctx.config || !ctx.config->useDithering) {
|
||||||
return quantizeGray4Level(gray);
|
return quantizeGray4Level(gray);
|
||||||
}
|
}
|
||||||
@@ -119,7 +155,9 @@ uint8_t ditherGray(PngContext& ctx, uint8_t gray, int localX, int outX, int outY
|
|||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
uint8_t ditherGray(PngContext& ctx, uint8_t gray, int localX, int outX, int outY) {
|
uint8_t ditherGray(PngContext& ctx, uint8_t gray, int localX, int outX, int outY) {
|
||||||
(void)ctx;
|
if (ctx.atkinson1BitDitherer) {
|
||||||
|
return ctx.atkinson1BitDitherer->processPixel(gray, localX) ? 3 : 0;
|
||||||
|
}
|
||||||
(void)localX;
|
(void)localX;
|
||||||
return applyBayerDither4Level(gray, outX, outY);
|
return applyBayerDither4Level(gray, outX, outY);
|
||||||
}
|
}
|
||||||
@@ -291,6 +329,7 @@ int pngDrawCallback(PNGDRAW* pDraw) {
|
|||||||
cw.beginRow(outY, ctx->config->y);
|
cw.beginRow(outY, ctx->config->y);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
prepareOneBitDitherRow(*ctx, dstY);
|
||||||
#ifdef ENABLE_IMAGE_DITHERING_EXTENSION
|
#ifdef ENABLE_IMAGE_DITHERING_EXTENSION
|
||||||
prepareDitherRow(*ctx, dstY);
|
prepareDitherRow(*ctx, dstY);
|
||||||
#endif
|
#endif
|
||||||
@@ -455,7 +494,22 @@ bool PngToFramebufferConverter::decodeToFramebuffer(const std::string& imagePath
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.useDithering) {
|
// When the renderer is in BW mode, use a 1-bit Atkinson ditherer instead of
|
||||||
|
// the 4-level paths below. The 4-level dither produces values 1-2 for mid
|
||||||
|
// grays, which DirectPixelWriter then collapses to black under its `< 3`
|
||||||
|
// BW rule, making images render very dark. The 1-bit ditherer emits only
|
||||||
|
// 0 or 3 so the BW writer maps cleanly to black/white. Caching still uses
|
||||||
|
// the 2-bit cache file format, but caching is disabled in this path
|
||||||
|
// (BmpViewerActivity passes an empty cachePath).
|
||||||
|
ctx.renderModeIsBW = (renderer.getRenderMode() == GfxRenderer::BW);
|
||||||
|
if (ctx.renderModeIsBW) {
|
||||||
|
ctx.atkinson1BitDitherer = new (std::nothrow) Atkinson1BitDitherer(ctx.dstWidth);
|
||||||
|
if (!ctx.atkinson1BitDitherer) {
|
||||||
|
LOG_ERR("PNG", "Failed to allocate 1-bit Atkinson ditherer, falling back to 4-level dither");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.useDithering && !ctx.atkinson1BitDitherer) {
|
||||||
#ifdef ENABLE_IMAGE_DITHERING_EXTENSION
|
#ifdef ENABLE_IMAGE_DITHERING_EXTENSION
|
||||||
switch (config.ditherMode) {
|
switch (config.ditherMode) {
|
||||||
case ImageDitherMode::Atkinson:
|
case ImageDitherMode::Atkinson:
|
||||||
|
|||||||
Reference in New Issue
Block a user