From d461d93e766f3843aa844f33ed9060839bfda0f0 Mon Sep 17 00:00:00 2001 From: Arthur Tazhitdinov Date: Thu, 19 Feb 2026 19:51:38 +0300 Subject: [PATCH] fix: Increase PNGdec buffer size to support wide images (#995) ## Summary * Increased `PNG_MAX_BUFFERED_PIXELS` from 6402 to 16416 in `platformio.ini` to support up to 2048px wide RGBA images * adds a check to abort decoding and log an error if the required PNG scanline buffer exceeds the configured `PNG_MAX_BUFFERED_PIXELS`, preventing possible buffer overruns. * fixes https://github.com/crosspoint-reader/crosspoint-reader/issues/993 --- ### AI Usage While CrossPoint doesn't have restrictions on AI tools in contributing, please be transparent about their usage as it helps set the right context for reviewers. Did you use AI tools to help write this code? _**< YES >**_ --- .../converters/PngToFramebufferConverter.cpp | 38 +++++++++++++++++++ platformio.ini | 4 +- 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/lib/Epub/Epub/converters/PngToFramebufferConverter.cpp b/lib/Epub/Epub/converters/PngToFramebufferConverter.cpp index f54e5e66..7925fa62 100644 --- a/lib/Epub/Epub/converters/PngToFramebufferConverter.cpp +++ b/lib/Epub/Epub/converters/PngToFramebufferConverter.cpp @@ -90,6 +90,32 @@ int32_t pngSeekWithHandle(PNGFILE* pFile, int32_t pos) { constexpr size_t PNG_DECODER_APPROX_SIZE = 44 * 1024; // ~42 KB + overhead constexpr size_t MIN_FREE_HEAP_FOR_PNG = PNG_DECODER_APPROX_SIZE + 16 * 1024; // decoder + 16 KB headroom +// PNGdec keeps TWO scanlines in its internal ucPixels buffer (current + previous) +// and each scanline includes a leading filter byte. +// Required storage is therefore approximately: 2 * (pitch + 1) + alignment slack. +// If PNG_MAX_BUFFERED_PIXELS is smaller than this requirement for a given image, +// PNGdec can overrun its internal buffer before our draw callback executes. +int bytesPerPixelFromType(int pixelType) { + switch (pixelType) { + case PNG_PIXEL_TRUECOLOR: + return 3; + case PNG_PIXEL_GRAY_ALPHA: + return 2; + case PNG_PIXEL_TRUECOLOR_ALPHA: + return 4; + case PNG_PIXEL_GRAYSCALE: + case PNG_PIXEL_INDEXED: + default: + return 1; + } +} + +int requiredPngInternalBufferBytes(int srcWidth, int pixelType) { + // +1 filter byte per scanline, *2 for current+previous lines, +32 for alignment margin. + int pitch = srcWidth * bytesPerPixelFromType(pixelType); + return ((pitch + 1) * 2) + 32; +} + // Convert entire source line to grayscale with alpha blending to white background. // For indexed PNGs with tRNS chunk, alpha values are stored at palette[768] onwards. // Processing the whole line at once improves cache locality and reduces per-pixel overhead. @@ -304,6 +330,18 @@ bool PngToFramebufferConverter::decodeToFramebuffer(const std::string& imagePath LOG_DBG("PNG", "PNG %dx%d -> %dx%d (scale %.2f), bpp: %d", ctx.srcWidth, ctx.srcHeight, ctx.dstWidth, ctx.dstHeight, ctx.scale, png->getBpp()); + const int pixelType = png->getPixelType(); + const int requiredInternal = requiredPngInternalBufferBytes(ctx.srcWidth, pixelType); + if (requiredInternal > PNG_MAX_BUFFERED_PIXELS) { + LOG_ERR("PNG", + "PNG row buffer too small: need %d bytes for width=%d type=%d, configured PNG_MAX_BUFFERED_PIXELS=%d", + requiredInternal, ctx.srcWidth, pixelType, PNG_MAX_BUFFERED_PIXELS); + LOG_ERR("PNG", "Aborting decode to avoid PNGdec internal buffer overflow"); + png->close(); + delete png; + return false; + } + if (png->getBpp() != 8) { warnUnsupportedFeature("bit depth (" + std::to_string(png->getBpp()) + "bpp)", imagePath); } diff --git a/platformio.ini b/platformio.ini index f6ad828b..58e01882 100644 --- a/platformio.ini +++ b/platformio.ini @@ -31,9 +31,9 @@ build_flags = -std=gnu++2a # Enable UTF-8 long file names in SdFat -DUSE_UTF8_LONG_NAMES=1 -# Increase PNG scanline buffer to support up to 800px wide images +# Increase PNG scanline buffer to support up to 2048px wide images # Default is (320*4+1)*2=2562, we need more for larger images - -DPNG_MAX_BUFFERED_PIXELS=6402 + -DPNG_MAX_BUFFERED_PIXELS=16416 build_unflags = -std=gnu++11