chore: Remove miniz and modularise inflation logic (#1073)
## Summary * Remove miniz and move completely to uzlib * Move uzlib interfacing to InflateReader to better modularise inflation code --- ### 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, Claude helped with the extraction and refactor
This commit is contained in:
@@ -1,14 +1,11 @@
|
||||
#include "FontDecompressor.h"
|
||||
|
||||
#include <Logging.h>
|
||||
#include <uzlib.h>
|
||||
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
bool FontDecompressor::init() {
|
||||
clearCache();
|
||||
memset(&decomp, 0, sizeof(decomp));
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -82,20 +79,10 @@ bool FontDecompressor::decompressGroup(const EpdFontData* fontData, uint16_t gro
|
||||
return false;
|
||||
}
|
||||
|
||||
// Decompress using uzlib
|
||||
const uint8_t* inputBuf = &fontData->bitmap[group.compressedOffset];
|
||||
|
||||
uzlib_uncompress_init(&decomp, NULL, 0);
|
||||
decomp.source = inputBuf;
|
||||
decomp.source_limit = inputBuf + group.compressedSize;
|
||||
decomp.dest_start = outBuf;
|
||||
decomp.dest = outBuf;
|
||||
decomp.dest_limit = outBuf + group.uncompressedSize;
|
||||
|
||||
int res = uzlib_uncompress(&decomp);
|
||||
|
||||
if (res < 0 || decomp.dest != decomp.dest_limit) {
|
||||
LOG_ERR("FDC", "Decompression failed for group %u (status %d)", groupIndex, res);
|
||||
inflateReader.init(false);
|
||||
inflateReader.setSource(&fontData->bitmap[group.compressedOffset], group.compressedSize);
|
||||
if (!inflateReader.read(outBuf, group.uncompressedSize)) {
|
||||
LOG_ERR("FDC", "Decompression failed for group %u", groupIndex);
|
||||
free(outBuf);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <uzlib.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <InflateReader.h>
|
||||
|
||||
#include "EpdFontData.h"
|
||||
|
||||
@@ -30,7 +28,7 @@ class FontDecompressor {
|
||||
bool valid = false;
|
||||
};
|
||||
|
||||
struct uzlib_uncomp decomp = {};
|
||||
InflateReader inflateReader;
|
||||
CacheEntry cache[CACHE_SLOTS] = {};
|
||||
uint32_t accessCounter = 0;
|
||||
|
||||
|
||||
78
lib/InflateReader/InflateReader.cpp
Normal file
78
lib/InflateReader/InflateReader.cpp
Normal file
@@ -0,0 +1,78 @@
|
||||
#include "InflateReader.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <type_traits>
|
||||
|
||||
namespace {
|
||||
constexpr size_t INFLATE_DICT_SIZE = 32768;
|
||||
}
|
||||
|
||||
// Guarantee the cast pattern in the header comment is valid.
|
||||
static_assert(std::is_standard_layout<InflateReader>::value,
|
||||
"InflateReader must be standard-layout for the uzlib callback cast to work");
|
||||
|
||||
InflateReader::~InflateReader() { deinit(); }
|
||||
|
||||
bool InflateReader::init(const bool streaming) {
|
||||
deinit(); // free any previously allocated ring buffer and reset state
|
||||
|
||||
if (streaming) {
|
||||
ringBuffer = static_cast<uint8_t*>(malloc(INFLATE_DICT_SIZE));
|
||||
if (!ringBuffer) return false;
|
||||
memset(ringBuffer, 0, INFLATE_DICT_SIZE);
|
||||
}
|
||||
|
||||
uzlib_uncompress_init(&decomp, ringBuffer, ringBuffer ? INFLATE_DICT_SIZE : 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
void InflateReader::deinit() {
|
||||
if (ringBuffer) {
|
||||
free(ringBuffer);
|
||||
ringBuffer = nullptr;
|
||||
}
|
||||
memset(&decomp, 0, sizeof(decomp));
|
||||
}
|
||||
|
||||
void InflateReader::setSource(const uint8_t* src, size_t len) {
|
||||
decomp.source = src;
|
||||
decomp.source_limit = src + len;
|
||||
}
|
||||
|
||||
void InflateReader::setReadCallback(int (*cb)(struct uzlib_uncomp*)) { decomp.source_read_cb = cb; }
|
||||
|
||||
void InflateReader::skipZlibHeader() {
|
||||
uzlib_get_byte(&decomp);
|
||||
uzlib_get_byte(&decomp);
|
||||
}
|
||||
|
||||
bool InflateReader::read(uint8_t* dest, size_t len) {
|
||||
if (!ringBuffer) {
|
||||
// One-shot mode: back-references use absolute offset from dest_start.
|
||||
// Valid only when read() is called once with the full output buffer.
|
||||
decomp.dest_start = dest;
|
||||
}
|
||||
decomp.dest = dest;
|
||||
decomp.dest_limit = dest + len;
|
||||
|
||||
const int res = uzlib_uncompress(&decomp);
|
||||
if (res < 0) return false;
|
||||
return decomp.dest == decomp.dest_limit;
|
||||
}
|
||||
|
||||
InflateStatus InflateReader::readAtMost(uint8_t* dest, size_t maxLen, size_t* produced) {
|
||||
if (!ringBuffer) {
|
||||
// One-shot mode: back-references use absolute offset from dest_start.
|
||||
// Valid only when readAtMost() is called once with the full output buffer.
|
||||
decomp.dest_start = dest;
|
||||
}
|
||||
decomp.dest = dest;
|
||||
decomp.dest_limit = dest + maxLen;
|
||||
|
||||
const int res = uzlib_uncompress(&decomp);
|
||||
*produced = static_cast<size_t>(decomp.dest - dest);
|
||||
|
||||
if (res == TINF_DONE) return InflateStatus::Done;
|
||||
if (res < 0) return InflateStatus::Error;
|
||||
return InflateStatus::Ok;
|
||||
}
|
||||
85
lib/InflateReader/InflateReader.h
Normal file
85
lib/InflateReader/InflateReader.h
Normal file
@@ -0,0 +1,85 @@
|
||||
#pragma once
|
||||
|
||||
#include <uzlib.h>
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
// Return value for readAtMost().
|
||||
enum class InflateStatus {
|
||||
Ok, // Output buffer full; more compressed data remains.
|
||||
Done, // Stream ended cleanly (TINF_DONE). produced may be < maxLen.
|
||||
Error, // Decompression failed.
|
||||
};
|
||||
|
||||
// Streaming deflate decompressor wrapping uzlib.
|
||||
//
|
||||
// Two modes:
|
||||
// init(false) — one-shot: input is a contiguous buffer, call read() once.
|
||||
// init(true) — streaming: allocates a 32KB ring buffer for back-references
|
||||
// across multiple read() / readAtMost() calls.
|
||||
//
|
||||
// Streaming callback pattern:
|
||||
// The uzlib read callback receives a `struct uzlib_uncomp*` with no separate
|
||||
// context pointer. To attach context, make InflateReader the *first member* of
|
||||
// your context struct, then cast inside the callback:
|
||||
//
|
||||
// struct MyCtx {
|
||||
// InflateReader reader; // must be first
|
||||
// FsFile* file;
|
||||
// // ...
|
||||
// };
|
||||
// static int myCb(struct uzlib_uncomp* u) {
|
||||
// MyCtx* ctx = reinterpret_cast<MyCtx*>(u); // valid: reader.decomp is at offset 0
|
||||
// // ... fill u->source / u->source_limit, return first byte
|
||||
// }
|
||||
// MyCtx ctx;
|
||||
// ctx.reader.init(true);
|
||||
// ctx.reader.setReadCallback(myCb);
|
||||
//
|
||||
class InflateReader {
|
||||
public:
|
||||
InflateReader() = default;
|
||||
~InflateReader();
|
||||
|
||||
InflateReader(const InflateReader&) = delete;
|
||||
InflateReader& operator=(const InflateReader&) = delete;
|
||||
|
||||
// Initialise decompressor. streaming=true allocates a 32KB ring buffer needed
|
||||
// when read() or readAtMost() will be called multiple times.
|
||||
// Returns false only in streaming mode if the ring buffer allocation fails.
|
||||
bool init(bool streaming = false);
|
||||
|
||||
// Release the ring buffer and reset internal state.
|
||||
void deinit();
|
||||
|
||||
// Set the entire compressed input as a contiguous memory buffer.
|
||||
// Used in one-shot mode; not needed when a read callback is set.
|
||||
void setSource(const uint8_t* src, size_t len);
|
||||
|
||||
// Set a uzlib-compatible read callback for streaming input.
|
||||
// See class-level comment for the expected callback/context struct pattern.
|
||||
void setReadCallback(int (*cb)(uzlib_uncomp*));
|
||||
|
||||
// Consume the 2-byte zlib header (CMF + FLG) from the input stream.
|
||||
// Call this once before the first read() when input is zlib-wrapped (e.g. PNG IDAT).
|
||||
void skipZlibHeader();
|
||||
|
||||
// Decompress exactly len bytes into dest.
|
||||
// Returns false if the stream ends before producing len bytes, or on error.
|
||||
bool read(uint8_t* dest, size_t len);
|
||||
|
||||
// Decompress up to maxLen bytes into dest.
|
||||
// Sets *produced to the number of bytes written.
|
||||
// Returns Done when the stream ends cleanly, Ok when there is more to read,
|
||||
// and Error on failure.
|
||||
InflateStatus readAtMost(uint8_t* dest, size_t maxLen, size_t* produced);
|
||||
|
||||
// Returns a pointer to the underlying TINF_DATA.
|
||||
// Useful for advanced streaming setups where the callback needs access to the
|
||||
// uzlib struct directly (e.g. updating source/source_limit).
|
||||
uzlib_uncomp* raw() { return &decomp; }
|
||||
|
||||
private:
|
||||
uzlib_uncomp decomp = {};
|
||||
uint8_t* ringBuffer = nullptr;
|
||||
};
|
||||
@@ -1,8 +1,8 @@
|
||||
#include "PngToBmpConverter.h"
|
||||
|
||||
#include <HalStorage.h>
|
||||
#include <InflateReader.h>
|
||||
#include <Logging.h>
|
||||
#include <miniz.h>
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
@@ -20,36 +20,6 @@ constexpr int TARGET_MAX_WIDTH = 480;
|
||||
constexpr int TARGET_MAX_HEIGHT = 800;
|
||||
// ============================================================================
|
||||
|
||||
// PNG constants
|
||||
static constexpr uint8_t PNG_SIGNATURE[8] = {137, 80, 78, 71, 13, 10, 26, 10};
|
||||
|
||||
// PNG color types
|
||||
enum PngColorType : uint8_t {
|
||||
PNG_COLOR_GRAYSCALE = 0,
|
||||
PNG_COLOR_RGB = 2,
|
||||
PNG_COLOR_PALETTE = 3,
|
||||
PNG_COLOR_GRAYSCALE_ALPHA = 4,
|
||||
PNG_COLOR_RGBA = 6,
|
||||
};
|
||||
|
||||
// PNG filter types
|
||||
enum PngFilter : uint8_t {
|
||||
PNG_FILTER_NONE = 0,
|
||||
PNG_FILTER_SUB = 1,
|
||||
PNG_FILTER_UP = 2,
|
||||
PNG_FILTER_AVERAGE = 3,
|
||||
PNG_FILTER_PAETH = 4,
|
||||
};
|
||||
|
||||
// Read a big-endian 32-bit value from file
|
||||
static bool readBE32(FsFile& file, uint32_t& value) {
|
||||
uint8_t buf[4];
|
||||
if (file.read(buf, 4) != 4) return false;
|
||||
value = (static_cast<uint32_t>(buf[0]) << 24) | (static_cast<uint32_t>(buf[1]) << 16) |
|
||||
(static_cast<uint32_t>(buf[2]) << 8) | buf[3];
|
||||
return true;
|
||||
}
|
||||
|
||||
// BMP writing helpers (same as JpegToBmpConverter)
|
||||
inline void write16(Print& out, const uint16_t value) {
|
||||
out.write(value & 0xFF);
|
||||
@@ -70,7 +40,49 @@ inline void write32Signed(Print& out, const int32_t value) {
|
||||
out.write((value >> 24) & 0xFF);
|
||||
}
|
||||
|
||||
static void writeBmpHeader8bit(Print& bmpOut, const int width, const int height) {
|
||||
// Paeth predictor function per PNG spec
|
||||
inline uint8_t paethPredictor(uint8_t a, uint8_t b, uint8_t c) {
|
||||
int p = static_cast<int>(a) + b - c;
|
||||
int pa = p > a ? p - a : a - p;
|
||||
int pb = p > b ? p - b : b - p;
|
||||
int pc = p > c ? p - c : c - p;
|
||||
if (pa <= pb && pa <= pc) return a;
|
||||
if (pb <= pc) return b;
|
||||
return c;
|
||||
}
|
||||
|
||||
namespace {
|
||||
// PNG constants
|
||||
uint8_t PNG_SIGNATURE[8] = {137, 80, 78, 71, 13, 10, 26, 10};
|
||||
|
||||
// PNG color types
|
||||
enum PngColorType : uint8_t {
|
||||
PNG_COLOR_GRAYSCALE = 0,
|
||||
PNG_COLOR_RGB = 2,
|
||||
PNG_COLOR_PALETTE = 3,
|
||||
PNG_COLOR_GRAYSCALE_ALPHA = 4,
|
||||
PNG_COLOR_RGBA = 6,
|
||||
};
|
||||
|
||||
// PNG filter types
|
||||
enum PngFilter : uint8_t {
|
||||
PNG_FILTER_NONE = 0,
|
||||
PNG_FILTER_SUB = 1,
|
||||
PNG_FILTER_UP = 2,
|
||||
PNG_FILTER_AVERAGE = 3,
|
||||
PNG_FILTER_PAETH = 4,
|
||||
};
|
||||
|
||||
// Read a big-endian 32-bit value from file
|
||||
bool readBE32(FsFile& file, uint32_t& value) {
|
||||
uint8_t buf[4];
|
||||
if (file.read(buf, 4) != 4) return false;
|
||||
value = (static_cast<uint32_t>(buf[0]) << 24) | (static_cast<uint32_t>(buf[1]) << 16) |
|
||||
(static_cast<uint32_t>(buf[2]) << 8) | buf[3];
|
||||
return true;
|
||||
}
|
||||
|
||||
void writeBmpHeader8bit(Print& bmpOut, const int width, const int height) {
|
||||
const int bytesPerRow = (width + 3) / 4 * 4;
|
||||
const int imageSize = bytesPerRow * height;
|
||||
const uint32_t paletteSize = 256 * 4;
|
||||
@@ -102,7 +114,7 @@ static void writeBmpHeader8bit(Print& bmpOut, const int width, const int height)
|
||||
}
|
||||
}
|
||||
|
||||
static void writeBmpHeader1bit(Print& bmpOut, const int width, const int height) {
|
||||
void writeBmpHeader1bit(Print& bmpOut, const int width, const int height) {
|
||||
const int bytesPerRow = (width + 31) / 32 * 4;
|
||||
const int imageSize = bytesPerRow * height;
|
||||
const uint32_t fileSize = 62 + imageSize;
|
||||
@@ -131,7 +143,7 @@ static void writeBmpHeader1bit(Print& bmpOut, const int width, const int height)
|
||||
}
|
||||
}
|
||||
|
||||
static void writeBmpHeader2bit(Print& bmpOut, const int width, const int height) {
|
||||
void writeBmpHeader2bit(Print& bmpOut, const int width, const int height) {
|
||||
const int bytesPerRow = (width * 2 + 31) / 32 * 4;
|
||||
const int imageSize = bytesPerRow * height;
|
||||
const uint32_t fileSize = 70 + imageSize;
|
||||
@@ -160,21 +172,13 @@ static void writeBmpHeader2bit(Print& bmpOut, const int width, const int height)
|
||||
bmpOut.write(i);
|
||||
}
|
||||
}
|
||||
|
||||
// Paeth predictor function per PNG spec
|
||||
static inline uint8_t paethPredictor(uint8_t a, uint8_t b, uint8_t c) {
|
||||
int p = static_cast<int>(a) + b - c;
|
||||
int pa = p > a ? p - a : a - p;
|
||||
int pb = p > b ? p - b : b - p;
|
||||
int pc = p > c ? p - c : c - p;
|
||||
if (pa <= pb && pa <= pc) return a;
|
||||
if (pb <= pc) return b;
|
||||
return c;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
// Context for streaming PNG decompression
|
||||
// IMPORTANT: reader must be the first field - the uzlib callback casts uzlib_uncomp* to PngDecodeContext*
|
||||
struct PngDecodeContext {
|
||||
FsFile& file;
|
||||
InflateReader reader; // Must be first — callback casts uzlib_uncomp* to PngDecodeContext*
|
||||
FsFile* file;
|
||||
|
||||
// PNG image properties
|
||||
uint32_t width;
|
||||
@@ -188,15 +192,11 @@ struct PngDecodeContext {
|
||||
uint8_t* currentRow; // current defiltered scanline
|
||||
uint8_t* previousRow; // previous defiltered scanline
|
||||
|
||||
// zlib decompression state
|
||||
mz_stream zstream;
|
||||
bool zstreamInitialized;
|
||||
|
||||
// Chunk reading state
|
||||
uint32_t chunkBytesRemaining; // bytes left in current IDAT chunk
|
||||
bool idatFinished; // no more IDAT chunks
|
||||
|
||||
// File read buffer for feeding zlib
|
||||
// File read buffer for feeding uzlib
|
||||
uint8_t readBuf[2048];
|
||||
|
||||
// Palette for indexed color (type 3)
|
||||
@@ -209,10 +209,10 @@ struct PngDecodeContext {
|
||||
static bool findNextIdatChunk(PngDecodeContext& ctx) {
|
||||
while (true) {
|
||||
uint32_t chunkLen;
|
||||
if (!readBE32(ctx.file, chunkLen)) return false;
|
||||
if (!readBE32(*ctx.file, chunkLen)) return false;
|
||||
|
||||
uint8_t chunkType[4];
|
||||
if (ctx.file.read(chunkType, 4) != 4) return false;
|
||||
if (ctx.file->read(chunkType, 4) != 4) return false;
|
||||
|
||||
if (memcmp(chunkType, "IDAT", 4) == 0) {
|
||||
ctx.chunkBytesRemaining = chunkLen;
|
||||
@@ -221,7 +221,7 @@ static bool findNextIdatChunk(PngDecodeContext& ctx) {
|
||||
|
||||
// Skip this chunk's data + 4-byte CRC
|
||||
// Use seek to skip efficiently
|
||||
if (!ctx.file.seekCur(chunkLen + 4)) return false;
|
||||
if (!ctx.file->seekCur(chunkLen + 4)) return false;
|
||||
|
||||
// If we hit IEND, there are no more chunks
|
||||
if (memcmp(chunkType, "IEND", 4) == 0) {
|
||||
@@ -230,72 +230,50 @@ static bool findNextIdatChunk(PngDecodeContext& ctx) {
|
||||
}
|
||||
}
|
||||
|
||||
// Feed compressed data to zlib from IDAT chunks
|
||||
// Returns number of bytes made available in zstream, or -1 on error
|
||||
static int feedZlibInput(PngDecodeContext& ctx) {
|
||||
if (ctx.idatFinished) return 0;
|
||||
// uzlib callback: reads the next batch of IDAT data from the file
|
||||
static int pngIdatReadCallback(uzlib_uncomp* uncomp) {
|
||||
auto* ctx = reinterpret_cast<PngDecodeContext*>(uncomp);
|
||||
|
||||
// If current IDAT chunk is exhausted, skip its CRC and find next
|
||||
while (ctx.chunkBytesRemaining == 0) {
|
||||
// Skip 4-byte CRC of previous IDAT
|
||||
if (!ctx.file.seekCur(4)) return -1;
|
||||
if (ctx->idatFinished) return -1;
|
||||
|
||||
if (!findNextIdatChunk(ctx)) {
|
||||
ctx.idatFinished = true;
|
||||
return 0;
|
||||
// Skip 4-byte CRC and find next IDAT chunk when current chunk is exhausted
|
||||
while (ctx->chunkBytesRemaining == 0) {
|
||||
if (!ctx->file->seekCur(4)) { // skip 4-byte CRC of previous IDAT
|
||||
ctx->idatFinished = true;
|
||||
return -1;
|
||||
}
|
||||
if (!findNextIdatChunk(*ctx)) {
|
||||
ctx->idatFinished = true;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
// Read from current IDAT chunk
|
||||
size_t toRead = sizeof(ctx.readBuf);
|
||||
if (toRead > ctx.chunkBytesRemaining) toRead = ctx.chunkBytesRemaining;
|
||||
// Read from current IDAT chunk into the read buffer
|
||||
size_t toRead = sizeof(ctx->readBuf);
|
||||
if (toRead > ctx->chunkBytesRemaining) toRead = ctx->chunkBytesRemaining;
|
||||
|
||||
int bytesRead = ctx.file.read(ctx.readBuf, toRead);
|
||||
if (bytesRead <= 0) return -1;
|
||||
|
||||
ctx.chunkBytesRemaining -= bytesRead;
|
||||
ctx.zstream.next_in = ctx.readBuf;
|
||||
ctx.zstream.avail_in = bytesRead;
|
||||
|
||||
return bytesRead;
|
||||
}
|
||||
|
||||
// Decompress exactly 'needed' bytes into 'dest'
|
||||
static bool decompressBytes(PngDecodeContext& ctx, uint8_t* dest, size_t needed) {
|
||||
ctx.zstream.next_out = dest;
|
||||
ctx.zstream.avail_out = needed;
|
||||
|
||||
while (ctx.zstream.avail_out > 0) {
|
||||
if (ctx.zstream.avail_in == 0) {
|
||||
int fed = feedZlibInput(ctx);
|
||||
if (fed < 0) return false;
|
||||
if (fed == 0) {
|
||||
// Try one more inflate to flush
|
||||
int ret = mz_inflate(&ctx.zstream, MZ_SYNC_FLUSH);
|
||||
if (ctx.zstream.avail_out == 0) break;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
int ret = mz_inflate(&ctx.zstream, MZ_SYNC_FLUSH);
|
||||
if (ret != MZ_OK && ret != MZ_STREAM_END && ret != MZ_BUF_ERROR) {
|
||||
LOG_ERR("PNG", "zlib inflate error: %d", ret);
|
||||
return false;
|
||||
}
|
||||
if (ret == MZ_STREAM_END) break;
|
||||
int bytesRead = ctx->file->read(ctx->readBuf, toRead);
|
||||
if (bytesRead <= 0) {
|
||||
ctx->idatFinished = true;
|
||||
return -1;
|
||||
}
|
||||
|
||||
return ctx.zstream.avail_out == 0;
|
||||
ctx->chunkBytesRemaining -= bytesRead;
|
||||
|
||||
// Give uzlib the buffer (skip first byte since we return it directly)
|
||||
uncomp->source = ctx->readBuf + 1;
|
||||
uncomp->source_limit = ctx->readBuf + bytesRead;
|
||||
return ctx->readBuf[0];
|
||||
}
|
||||
|
||||
// Decode one scanline: decompress filter byte + raw bytes, then unfilter
|
||||
static bool decodeScanline(PngDecodeContext& ctx) {
|
||||
// Decompress filter byte
|
||||
uint8_t filterType;
|
||||
if (!decompressBytes(ctx, &filterType, 1)) return false;
|
||||
if (!ctx.reader.read(&filterType, 1)) return false;
|
||||
|
||||
// Decompress raw row data into currentRow
|
||||
if (!decompressBytes(ctx, ctx.currentRow, ctx.rawRowBytes)) return false;
|
||||
if (!ctx.reader.read(ctx.currentRow, ctx.rawRowBytes)) return false;
|
||||
|
||||
// Apply reverse filter
|
||||
const int bpp = ctx.bytesPerPixel;
|
||||
@@ -521,22 +499,15 @@ bool PngToBmpConverter::pngFileToBmpStreamInternal(FsFile& pngFile, Print& bmpOu
|
||||
}
|
||||
|
||||
// Initialize decode context
|
||||
PngDecodeContext ctx = {.file = pngFile,
|
||||
.width = width,
|
||||
.height = height,
|
||||
.bitDepth = bitDepth,
|
||||
.colorType = colorType,
|
||||
.bytesPerPixel = bytesPerPixel,
|
||||
.rawRowBytes = rawRowBytes,
|
||||
.currentRow = nullptr,
|
||||
.previousRow = nullptr,
|
||||
.zstream = {},
|
||||
.zstreamInitialized = false,
|
||||
.chunkBytesRemaining = 0,
|
||||
.idatFinished = false,
|
||||
.readBuf = {},
|
||||
.palette = {},
|
||||
.paletteSize = 0};
|
||||
PngDecodeContext ctx = {};
|
||||
ctx.file = &pngFile;
|
||||
ctx.width = width;
|
||||
ctx.height = height;
|
||||
ctx.bitDepth = bitDepth;
|
||||
ctx.colorType = colorType;
|
||||
ctx.bytesPerPixel = bytesPerPixel;
|
||||
ctx.rawRowBytes = rawRowBytes;
|
||||
ctx.paletteSize = 0;
|
||||
|
||||
// Allocate scanline buffers
|
||||
ctx.currentRow = static_cast<uint8_t*>(malloc(rawRowBytes));
|
||||
@@ -585,15 +556,16 @@ bool PngToBmpConverter::pngFileToBmpStreamInternal(FsFile& pngFile, Print& bmpOu
|
||||
return false;
|
||||
}
|
||||
|
||||
// Initialize zlib decompression
|
||||
memset(&ctx.zstream, 0, sizeof(ctx.zstream));
|
||||
if (mz_inflateInit(&ctx.zstream) != MZ_OK) {
|
||||
LOG_ERR("PNG", "Failed to initialize zlib");
|
||||
// Initialize streaming decompressor with 32KB ring buffer for back-reference history
|
||||
if (!ctx.reader.init(true)) {
|
||||
LOG_ERR("PNG", "Failed to init inflate reader");
|
||||
free(ctx.currentRow);
|
||||
free(ctx.previousRow);
|
||||
return false;
|
||||
}
|
||||
ctx.zstreamInitialized = true;
|
||||
ctx.reader.setReadCallback(pngIdatReadCallback);
|
||||
// PNG IDAT data is zlib-wrapped: consume the 2-byte zlib header (CMF + FLG)
|
||||
ctx.reader.skipZlibHeader();
|
||||
|
||||
// Calculate output dimensions (same logic as JpegToBmpConverter)
|
||||
int outWidth = width;
|
||||
@@ -618,8 +590,8 @@ bool PngToBmpConverter::pngFileToBmpStreamInternal(FsFile& pngFile, Print& bmpOu
|
||||
if (outWidth < 1) outWidth = 1;
|
||||
if (outHeight < 1) outHeight = 1;
|
||||
|
||||
scaleX_fp = (static_cast<uint32_t>(width) << 16) / outWidth;
|
||||
scaleY_fp = (static_cast<uint32_t>(height) << 16) / outHeight;
|
||||
scaleX_fp = (width << 16) / outWidth;
|
||||
scaleY_fp = (height << 16) / outHeight;
|
||||
needsScaling = true;
|
||||
|
||||
LOG_DBG("PNG", "Scaling %ux%u -> %dx%d (target %dx%d)", width, height, outWidth, outHeight, targetWidth,
|
||||
@@ -643,7 +615,6 @@ bool PngToBmpConverter::pngFileToBmpStreamInternal(FsFile& pngFile, Print& bmpOu
|
||||
auto* rowBuffer = static_cast<uint8_t*>(malloc(bytesPerRow));
|
||||
if (!rowBuffer) {
|
||||
LOG_ERR("PNG", "Failed to allocate row buffer");
|
||||
mz_inflateEnd(&ctx.zstream);
|
||||
free(ctx.currentRow);
|
||||
free(ctx.previousRow);
|
||||
return false;
|
||||
@@ -687,7 +658,6 @@ bool PngToBmpConverter::pngFileToBmpStreamInternal(FsFile& pngFile, Print& bmpOu
|
||||
delete fsDitherer;
|
||||
delete atkinson1BitDitherer;
|
||||
free(rowBuffer);
|
||||
mz_inflateEnd(&ctx.zstream);
|
||||
free(ctx.currentRow);
|
||||
free(ctx.previousRow);
|
||||
return false;
|
||||
@@ -842,7 +812,6 @@ bool PngToBmpConverter::pngFileToBmpStreamInternal(FsFile& pngFile, Print& bmpOu
|
||||
delete fsDitherer;
|
||||
delete atkinson1BitDitherer;
|
||||
free(rowBuffer);
|
||||
mz_inflateEnd(&ctx.zstream);
|
||||
free(ctx.currentRow);
|
||||
free(ctx.previousRow);
|
||||
|
||||
|
||||
@@ -1,35 +1,38 @@
|
||||
#include "ZipFile.h"
|
||||
|
||||
#include <HalStorage.h>
|
||||
#include <InflateReader.h>
|
||||
#include <Logging.h>
|
||||
#include <miniz.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
static bool inflateOneShot(const uint8_t* inputBuf, const size_t deflatedSize, uint8_t* outputBuf,
|
||||
const size_t inflatedSize) {
|
||||
const auto inflator = static_cast<tinfl_decompressor*>(malloc(sizeof(tinfl_decompressor)));
|
||||
if (!inflator) {
|
||||
LOG_ERR("ZIP", "Failed to allocate memory for inflator");
|
||||
return false;
|
||||
}
|
||||
memset(inflator, 0, sizeof(tinfl_decompressor));
|
||||
tinfl_init(inflator);
|
||||
struct ZipInflateCtx {
|
||||
InflateReader reader; // Must be first — callback casts uzlib_uncomp* to ZipInflateCtx*
|
||||
FsFile* file = nullptr;
|
||||
size_t fileRemaining = 0;
|
||||
uint8_t* readBuf = nullptr;
|
||||
size_t readBufSize = 0;
|
||||
};
|
||||
|
||||
size_t inBytes = deflatedSize;
|
||||
size_t outBytes = inflatedSize;
|
||||
const tinfl_status status = tinfl_decompress(inflator, inputBuf, &inBytes, nullptr, outputBuf, &outBytes,
|
||||
TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF);
|
||||
namespace {
|
||||
constexpr uint16_t ZIP_METHOD_STORED = 0;
|
||||
constexpr uint16_t ZIP_METHOD_DEFLATED = 8;
|
||||
|
||||
free(inflator);
|
||||
int zipReadCallback(uzlib_uncomp* uncomp) {
|
||||
auto* ctx = reinterpret_cast<ZipInflateCtx*>(uncomp);
|
||||
if (ctx->fileRemaining == 0) return -1;
|
||||
|
||||
if (status != TINFL_STATUS_DONE) {
|
||||
LOG_ERR("ZIP", "tinfl_decompress() failed with status %d", status);
|
||||
return false;
|
||||
}
|
||||
const size_t toRead = ctx->fileRemaining < ctx->readBufSize ? ctx->fileRemaining : ctx->readBufSize;
|
||||
const size_t bytesRead = ctx->file->read(ctx->readBuf, toRead);
|
||||
ctx->fileRemaining -= bytesRead;
|
||||
|
||||
return true;
|
||||
if (bytesRead == 0) return -1;
|
||||
|
||||
uncomp->source = ctx->readBuf + 1;
|
||||
uncomp->source_limit = ctx->readBuf + bytesRead;
|
||||
return ctx->readBuf[0];
|
||||
}
|
||||
} // namespace
|
||||
|
||||
bool ZipFile::loadAllFileStatSlims() {
|
||||
const bool wasOpen = isOpen();
|
||||
@@ -111,7 +114,6 @@ bool ZipFile::loadFileStatSlim(const char* filename, FileStatSlim* fileStat) {
|
||||
|
||||
// Phase 1: Try scanning from cursor position first
|
||||
uint32_t startPos = lastCentralDirPosValid ? lastCentralDirPos : zipDetails.centralDirOffset;
|
||||
uint32_t wrapPos = zipDetails.centralDirOffset;
|
||||
bool wrapped = false;
|
||||
bool found = false;
|
||||
|
||||
@@ -201,7 +203,7 @@ long ZipFile::getDataOffset(const FileStatSlim& fileStat) {
|
||||
}
|
||||
|
||||
if (pLocalHeader[0] + (pLocalHeader[1] << 8) + (pLocalHeader[2] << 16) + (pLocalHeader[3] << 24) !=
|
||||
0x04034b50 /* MZ_ZIP_LOCAL_DIR_HEADER_SIG */) {
|
||||
0x04034b50 /* ZIP local file header signature */) {
|
||||
LOG_ERR("ZIP", "Not a valid zip file header");
|
||||
return -1;
|
||||
}
|
||||
@@ -415,7 +417,7 @@ uint8_t* ZipFile::readFileToMemory(const char* filename, size_t* size, const boo
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (fileStat.method == MZ_NO_COMPRESSION) {
|
||||
if (fileStat.method == ZIP_METHOD_STORED) {
|
||||
// no deflation, just read content
|
||||
const size_t dataRead = file.read(data, inflatedDataSize);
|
||||
if (!wasOpen) {
|
||||
@@ -429,7 +431,7 @@ uint8_t* ZipFile::readFileToMemory(const char* filename, size_t* size, const boo
|
||||
}
|
||||
|
||||
// Continue out of block with data set
|
||||
} else if (fileStat.method == MZ_DEFLATED) {
|
||||
} else if (fileStat.method == ZIP_METHOD_DEFLATED) {
|
||||
// Read out deflated content from file
|
||||
const auto deflatedData = static_cast<uint8_t*>(malloc(deflatedDataSize));
|
||||
if (deflatedData == nullptr) {
|
||||
@@ -452,7 +454,13 @@ uint8_t* ZipFile::readFileToMemory(const char* filename, size_t* size, const boo
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool success = inflateOneShot(deflatedData, deflatedDataSize, data, inflatedDataSize);
|
||||
bool success = false;
|
||||
{
|
||||
InflateReader r;
|
||||
r.init(false);
|
||||
r.setSource(deflatedData, deflatedDataSize);
|
||||
success = r.read(data, inflatedDataSize);
|
||||
}
|
||||
free(deflatedData);
|
||||
|
||||
if (!success) {
|
||||
@@ -495,7 +503,7 @@ bool ZipFile::readFileToStream(const char* filename, Print& out, const size_t ch
|
||||
const auto deflatedDataSize = fileStat.compressedSize;
|
||||
const auto inflatedDataSize = fileStat.uncompressedSize;
|
||||
|
||||
if (fileStat.method == MZ_NO_COMPRESSION) {
|
||||
if (fileStat.method == ZIP_METHOD_STORED) {
|
||||
// no deflation, just read content
|
||||
const auto buffer = static_cast<uint8_t*>(malloc(chunkSize));
|
||||
if (!buffer) {
|
||||
@@ -529,127 +537,88 @@ bool ZipFile::readFileToStream(const char* filename, Print& out, const size_t ch
|
||||
return true;
|
||||
}
|
||||
|
||||
if (fileStat.method == MZ_DEFLATED) {
|
||||
auto* inflator = static_cast<tinfl_decompressor*>(malloc(sizeof(tinfl_decompressor)));
|
||||
if (!inflator) {
|
||||
LOG_ERR("ZIP", "Failed to allocate memory for inflator");
|
||||
if (!wasOpen) {
|
||||
close();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
memset(inflator, 0, sizeof(tinfl_decompressor));
|
||||
tinfl_init(inflator);
|
||||
|
||||
// Setup file read buffer
|
||||
const auto fileReadBuffer = static_cast<uint8_t*>(malloc(chunkSize));
|
||||
if (fileStat.method == ZIP_METHOD_DEFLATED) {
|
||||
auto* fileReadBuffer = static_cast<uint8_t*>(malloc(chunkSize));
|
||||
if (!fileReadBuffer) {
|
||||
LOG_ERR("ZIP", "Failed to allocate memory for zip file read buffer");
|
||||
free(inflator);
|
||||
if (!wasOpen) {
|
||||
close();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto outputBuffer = static_cast<uint8_t*>(malloc(TINFL_LZ_DICT_SIZE));
|
||||
auto* outputBuffer = static_cast<uint8_t*>(malloc(chunkSize));
|
||||
if (!outputBuffer) {
|
||||
LOG_ERR("ZIP", "Failed to allocate memory for dictionary");
|
||||
free(inflator);
|
||||
LOG_ERR("ZIP", "Failed to allocate memory for output buffer");
|
||||
free(fileReadBuffer);
|
||||
if (!wasOpen) {
|
||||
close();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
memset(outputBuffer, 0, TINFL_LZ_DICT_SIZE);
|
||||
|
||||
size_t fileRemainingBytes = deflatedDataSize;
|
||||
size_t processedOutputBytes = 0;
|
||||
size_t fileReadBufferFilledBytes = 0;
|
||||
size_t fileReadBufferCursor = 0;
|
||||
size_t outputCursor = 0; // Current offset in the circular dictionary
|
||||
ZipInflateCtx ctx;
|
||||
ctx.file = &file;
|
||||
ctx.fileRemaining = deflatedDataSize;
|
||||
ctx.readBuf = fileReadBuffer;
|
||||
ctx.readBufSize = chunkSize;
|
||||
|
||||
if (!ctx.reader.init(true)) {
|
||||
LOG_ERR("ZIP", "Failed to init inflate reader");
|
||||
free(outputBuffer);
|
||||
free(fileReadBuffer);
|
||||
if (!wasOpen) {
|
||||
close();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
ctx.reader.setReadCallback(zipReadCallback);
|
||||
|
||||
bool success = false;
|
||||
size_t totalProduced = 0;
|
||||
|
||||
while (true) {
|
||||
// Load more compressed bytes when needed
|
||||
if (fileReadBufferCursor >= fileReadBufferFilledBytes) {
|
||||
if (fileRemainingBytes == 0) {
|
||||
// Should not be hit, but a safe protection
|
||||
break; // EOF
|
||||
}
|
||||
size_t produced;
|
||||
const InflateStatus status = ctx.reader.readAtMost(outputBuffer, chunkSize, &produced);
|
||||
|
||||
fileReadBufferFilledBytes =
|
||||
file.read(fileReadBuffer, fileRemainingBytes < chunkSize ? fileRemainingBytes : chunkSize);
|
||||
fileRemainingBytes -= fileReadBufferFilledBytes;
|
||||
fileReadBufferCursor = 0;
|
||||
|
||||
if (fileReadBufferFilledBytes == 0) {
|
||||
// Bad read
|
||||
break; // EOF
|
||||
}
|
||||
totalProduced += produced;
|
||||
if (totalProduced > static_cast<size_t>(inflatedDataSize)) {
|
||||
LOG_ERR("ZIP", "Decompressed size exceeds expected (%zu > %zu)", totalProduced,
|
||||
static_cast<size_t>(inflatedDataSize));
|
||||
break;
|
||||
}
|
||||
|
||||
// Available bytes in fileReadBuffer to process
|
||||
size_t inBytes = fileReadBufferFilledBytes - fileReadBufferCursor;
|
||||
// Space remaining in outputBuffer
|
||||
size_t outBytes = TINFL_LZ_DICT_SIZE - outputCursor;
|
||||
|
||||
const tinfl_status status = tinfl_decompress(inflator, fileReadBuffer + fileReadBufferCursor, &inBytes,
|
||||
outputBuffer, outputBuffer + outputCursor, &outBytes,
|
||||
fileRemainingBytes > 0 ? TINFL_FLAG_HAS_MORE_INPUT : 0);
|
||||
|
||||
// Update input position
|
||||
fileReadBufferCursor += inBytes;
|
||||
|
||||
// Write output chunk
|
||||
if (outBytes > 0) {
|
||||
processedOutputBytes += outBytes;
|
||||
if (out.write(outputBuffer + outputCursor, outBytes) != outBytes) {
|
||||
if (produced > 0) {
|
||||
if (out.write(outputBuffer, produced) != produced) {
|
||||
LOG_ERR("ZIP", "Failed to write all output bytes to stream");
|
||||
if (!wasOpen) {
|
||||
close();
|
||||
}
|
||||
free(outputBuffer);
|
||||
free(fileReadBuffer);
|
||||
free(inflator);
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
// Update output position in buffer (with wraparound)
|
||||
outputCursor = (outputCursor + outBytes) & (TINFL_LZ_DICT_SIZE - 1);
|
||||
}
|
||||
|
||||
if (status < 0) {
|
||||
LOG_ERR("ZIP", "tinfl_decompress() failed with status %d", status);
|
||||
if (!wasOpen) {
|
||||
close();
|
||||
if (status == InflateStatus::Done) {
|
||||
if (totalProduced != static_cast<size_t>(inflatedDataSize)) {
|
||||
LOG_ERR("ZIP", "Decompressed size mismatch (expected %zu, got %zu)", static_cast<size_t>(inflatedDataSize),
|
||||
totalProduced);
|
||||
break;
|
||||
}
|
||||
free(outputBuffer);
|
||||
free(fileReadBuffer);
|
||||
free(inflator);
|
||||
return false;
|
||||
LOG_DBG("ZIP", "Decompressed %d bytes into %d bytes", deflatedDataSize, inflatedDataSize);
|
||||
success = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (status == TINFL_STATUS_DONE) {
|
||||
LOG_ERR("ZIP", "Decompressed %d bytes into %d bytes", deflatedDataSize, inflatedDataSize);
|
||||
if (!wasOpen) {
|
||||
close();
|
||||
}
|
||||
free(inflator);
|
||||
free(fileReadBuffer);
|
||||
free(outputBuffer);
|
||||
return true;
|
||||
if (status == InflateStatus::Error) {
|
||||
LOG_ERR("ZIP", "Decompression failed");
|
||||
break;
|
||||
}
|
||||
// InflateStatus::Ok: output buffer full, continue
|
||||
}
|
||||
|
||||
// If we get here, EOF reached without TINFL_STATUS_DONE
|
||||
LOG_ERR("ZIP", "Unexpected EOF");
|
||||
if (!wasOpen) {
|
||||
close();
|
||||
}
|
||||
free(outputBuffer);
|
||||
free(fileReadBuffer);
|
||||
free(inflator);
|
||||
return false;
|
||||
return success; // ctx.reader destructor frees the ring buffer
|
||||
}
|
||||
|
||||
if (!wasOpen) {
|
||||
|
||||
6900
lib/miniz/miniz.c
6900
lib/miniz/miniz.c
File diff suppressed because it is too large
Load Diff
1462
lib/miniz/miniz.h
1462
lib/miniz/miniz.h
File diff suppressed because it is too large
Load Diff
@@ -22,8 +22,6 @@ board_upload.offset_address = 0x10000
|
||||
build_flags =
|
||||
-DARDUINO_USB_MODE=1
|
||||
-DARDUINO_USB_CDC_ON_BOOT=1
|
||||
-DMINIZ_NO_ZLIB_COMPATIBLE_NAMES=1
|
||||
-DMINIZ_NO_STDIO=1
|
||||
-DEINK_DISPLAY_SINGLE_BUFFER_MODE=1
|
||||
-DDISABLE_FS_H_WARNING=1
|
||||
# https://libexpat.github.io/doc/api/latest/#XML_GE
|
||||
|
||||
Reference in New Issue
Block a user