Apply PR 1807 by WuToFu

This commit is contained in:
jpirnay
2026-05-06 21:55:54 +02:00
parent 08f3203969
commit e13ba3e180
@@ -35,9 +35,14 @@ struct JpegContext {
int dstWidth{0};
int dstHeight{0};
// Fine scale in 16.16 fixed-point (ESP32-C3 has no FPU)
int32_t fineScaleFP{1 << 16}; // src -> dst mapping
int32_t invScaleFP{1 << 16}; // dst -> src mapping
// Fine scale in 16.16 fixed-point (ESP32-C3 has no FPU).
// X and Y use separate scale factors because dstWidth/dstHeight may differ from
// scaledSrcWidth/scaledSrcHeight in aspect ratio (integer rounding of displayHeight),
// so a single X-derived factor would map Y rows incorrectly and crop visible content.
int32_t fineScaleFPX{1 << 16}; // src -> dst mapping (X axis)
int32_t invScaleFPX{1 << 16}; // dst -> src mapping (X axis)
int32_t fineScaleFPY{1 << 16}; // src -> dst mapping (Y axis)
int32_t invScaleFPY{1 << 16}; // dst -> src mapping (Y axis)
PixelCache cache;
bool caching{false};
@@ -301,8 +306,10 @@ int jpegDrawCallback(JPEGDRAW* pDraw) {
if (stride <= 0 || blockH <= 0 || validW <= 0) return 1;
const bool caching = ctx->caching;
const int32_t fineScaleFP = ctx->fineScaleFP;
const int32_t invScaleFP = ctx->invScaleFP;
const int32_t fineScaleFPX = ctx->fineScaleFPX;
const int32_t invScaleFPX = ctx->invScaleFPX;
const int32_t fineScaleFPY = ctx->fineScaleFPY;
const int32_t invScaleFPY = ctx->invScaleFPY;
GfxRenderer& renderer = *ctx->renderer;
const int cfgX = ctx->config->x;
const int cfgY = ctx->config->y;
@@ -313,10 +320,10 @@ int jpegDrawCallback(JPEGDRAW* pDraw) {
const int srcYEnd = blockY + blockH;
const int srcXEnd = blockX + validW;
int dstYStart = (int)((int64_t)blockY * fineScaleFP >> FP_SHIFT);
int dstYEnd = (srcYEnd >= ctx->scaledSrcHeight) ? ctx->dstHeight : (int)((int64_t)srcYEnd * fineScaleFP >> FP_SHIFT);
int dstXStart = (int)((int64_t)blockX * fineScaleFP >> FP_SHIFT);
int dstXEnd = (srcXEnd >= ctx->scaledSrcWidth) ? ctx->dstWidth : (int)((int64_t)srcXEnd * fineScaleFP >> FP_SHIFT);
int dstYStart = (int)((int64_t)blockY * fineScaleFPY >> FP_SHIFT);
int dstYEnd = (srcYEnd >= ctx->scaledSrcHeight) ? ctx->dstHeight : (int)((int64_t)srcYEnd * fineScaleFPY >> FP_SHIFT);
int dstXStart = (int)((int64_t)blockX * fineScaleFPX >> FP_SHIFT);
int dstXEnd = (srcXEnd >= ctx->scaledSrcWidth) ? ctx->dstWidth : (int)((int64_t)srcXEnd * fineScaleFPX >> FP_SHIFT);
// Pre-clamp destination ranges to screen bounds (eliminates per-pixel screen checks)
int clampYMax = ctx->dstHeight;
@@ -341,7 +348,7 @@ int jpegDrawCallback(JPEGDRAW* pDraw) {
}
// === 1:1 fast path: no scaling math ===
if (fineScaleFP == FP_ONE) {
if (fineScaleFPX == FP_ONE && fineScaleFPY == FP_ONE) {
for (int dstY = dstYStart; dstY < dstYEnd; dstY++) {
const int outY = cfgY + dstY;
prepareOneBitDitherRow(*ctx, dstY);
@@ -365,11 +372,11 @@ int jpegDrawCallback(JPEGDRAW* pDraw) {
// === Bilinear interpolation (upscale: fineScale > 1.0) ===
// Smooths block boundaries that would otherwise create visible banding
// on progressive JPEG DC-only decode (1/8 resolution upscaled to target).
if (fineScaleFP > FP_ONE) {
if (fineScaleFPX > FP_ONE && fineScaleFPY > FP_ONE) {
// Pre-compute safe X range where lx0 and lx0+1 are both in [0, validW-1].
// Only the left/right edge pixels (typically 0-2 and 1-8 respectively) need clamping.
int safeXStart = (int)(((int64_t)blockX * fineScaleFP + FP_MASK) >> FP_SHIFT);
int safeXEnd = (int)((int64_t)(blockX + validW - 1) * fineScaleFP >> FP_SHIFT);
int safeXStart = (int)(((int64_t)blockX * fineScaleFPX + FP_MASK) >> FP_SHIFT);
int safeXEnd = (int)((int64_t)(blockX + validW - 1) * fineScaleFPX >> FP_SHIFT);
if (safeXStart < dstXStart) safeXStart = dstXStart;
if (safeXEnd > dstXEnd) safeXEnd = dstXEnd;
if (safeXStart > safeXEnd) safeXEnd = safeXStart;
@@ -382,7 +389,7 @@ int jpegDrawCallback(JPEGDRAW* pDraw) {
#endif
pw.beginRow(outY);
if (caching) cw.beginRow(outY, ctx->config->y);
const int32_t srcFyFP = dstY * invScaleFP;
const int32_t srcFyFP = dstY * invScaleFPY;
const int32_t fy = srcFyFP & FP_MASK;
const int32_t fyInv = FP_ONE - fy;
int ly0 = (srcFyFP >> FP_SHIFT) - blockY;
@@ -397,7 +404,7 @@ int jpegDrawCallback(JPEGDRAW* pDraw) {
// Left edge (with X boundary clamping)
for (int dstX = dstXStart; dstX < safeXStart; dstX++) {
const int outX = cfgX + dstX;
const int32_t srcFxFP = dstX * invScaleFP;
const int32_t srcFxFP = dstX * invScaleFPX;
const int32_t fx = srcFxFP & FP_MASK;
const int32_t fxInv = FP_ONE - fx;
int lx0 = (srcFxFP >> FP_SHIFT) - blockX;
@@ -419,7 +426,7 @@ int jpegDrawCallback(JPEGDRAW* pDraw) {
// Interior (no X boundary checks — lx0 and lx0+1 guaranteed in bounds)
for (int dstX = safeXStart; dstX < safeXEnd; dstX++) {
const int outX = cfgX + dstX;
const int32_t srcFxFP = dstX * invScaleFP;
const int32_t srcFxFP = dstX * invScaleFPX;
const int32_t fx = srcFxFP & FP_MASK;
const int32_t fxInv = FP_ONE - fx;
const int lx0 = (srcFxFP >> FP_SHIFT) - blockX;
@@ -436,7 +443,7 @@ int jpegDrawCallback(JPEGDRAW* pDraw) {
// Right edge (with X boundary clamping)
for (int dstX = safeXEnd; dstX < dstXEnd; dstX++) {
const int outX = cfgX + dstX;
const int32_t srcFxFP = dstX * invScaleFP;
const int32_t srcFxFP = dstX * invScaleFPX;
const int32_t fx = srcFxFP & FP_MASK;
const int32_t fxInv = FP_ONE - fx;
int lx0 = (srcFxFP >> FP_SHIFT) - blockX;
@@ -465,7 +472,7 @@ int jpegDrawCallback(JPEGDRAW* pDraw) {
#endif
pw.beginRow(outY);
if (caching) cw.beginRow(outY, ctx->config->y);
const int32_t srcFyFP = dstY * invScaleFP;
const int32_t srcFyFP = dstY * invScaleFPY;
int ly = (srcFyFP >> FP_SHIFT) - blockY;
if (ly < 0) ly = 0;
if (ly >= blockH) ly = blockH - 1;
@@ -473,7 +480,7 @@ int jpegDrawCallback(JPEGDRAW* pDraw) {
for (int dstX = dstXStart; dstX < dstXEnd; dstX++) {
const int outX = cfgX + dstX;
const int32_t srcFxFP = dstX * invScaleFP;
const int32_t srcFxFP = dstX * invScaleFPX;
int lx = (srcFxFP >> FP_SHIFT) - blockX;
if (lx < 0) lx = 0;
if (lx >= validW) lx = validW - 1;
@@ -580,8 +587,14 @@ bool JpegToFramebufferConverter::decodeToFramebuffer(const std::string& imagePat
ctx.scaledSrcHeight = (srcHeight + jpegScaleDenom - 1) / jpegScaleDenom;
ctx.dstWidth = destWidth;
ctx.dstHeight = destHeight;
ctx.fineScaleFP = (int32_t)((int64_t)destWidth * FP_ONE / ctx.scaledSrcWidth);
ctx.invScaleFP = (int32_t)((int64_t)ctx.scaledSrcWidth * FP_ONE / destWidth);
if (destWidth <= 0 || destHeight <= 0) {
LOG_ERR("JPG", "Zero-sized output (%dx%d), aborting", destWidth, destHeight);
return false;
}
ctx.fineScaleFPX = (int32_t)((int64_t)destWidth * FP_ONE / ctx.scaledSrcWidth);
ctx.invScaleFPX = (int32_t)((int64_t)ctx.scaledSrcWidth * FP_ONE / destWidth);
ctx.fineScaleFPY = (int32_t)((int64_t)destHeight * FP_ONE / ctx.scaledSrcHeight);
ctx.invScaleFPY = (int32_t)((int64_t)ctx.scaledSrcHeight * FP_ONE / destHeight);
LOG_DBG("JPG", "JPEG %dx%d -> %dx%d (scale %.2f, jpegScale 1/%d, fineScale %.2f)%s", srcWidth, srcHeight, destWidth,
destHeight, targetScale, jpegScaleDenom, (float)destWidth / ctx.scaledSrcWidth,