Xteink-X4-crosspoint-reader/lib/Epub/Epub/converters/PixelCache.h
Martin Brook b8e61130f2 refactor: extract shared DitherUtils.h and PixelCache.h
Address review comments #2, #3, and #10:
- Extract duplicated bayer4x4 matrix, applyBayerDither4Level(), and
  drawPixelWithRenderMode() into shared DitherUtils.h
- Extract duplicated PixelCache struct into shared PixelCache.h so both
  JPEG and PNG decoders use the same implementation
- Add MAX_CACHE_BYTES (256KB) size limit to PixelCache::allocate() to
  proactively guard against oversized allocations on embedded targets
2026-02-01 09:24:04 +00:00

84 lines
2.5 KiB
C++

#pragma once
#include <HardwareSerial.h>
#include <SDCardManager.h>
#include <SdFat.h>
#include <stdint.h>
#include <cstring>
#include <string>
// Cache buffer for storing 2-bit pixels (4 levels) during decode.
// Packs 4 pixels per byte, MSB first.
struct PixelCache {
uint8_t* buffer;
int width;
int height;
int bytesPerRow;
int originX; // config.x - to convert screen coords to cache coords
int originY; // config.y
PixelCache() : buffer(nullptr), width(0), height(0), bytesPerRow(0), originX(0), originY(0) {}
static constexpr size_t MAX_CACHE_BYTES = 256 * 1024; // 256KB limit for embedded targets
bool allocate(int w, int h, int ox, int oy) {
width = w;
height = h;
originX = ox;
originY = oy;
bytesPerRow = (w + 3) / 4; // 2 bits per pixel, 4 pixels per byte
size_t bufferSize = (size_t)bytesPerRow * h;
if (bufferSize > MAX_CACHE_BYTES) {
Serial.printf("[%lu] [IMG] Cache buffer too large: %d bytes for %dx%d (limit %d)\n", millis(), bufferSize, w, h,
MAX_CACHE_BYTES);
return false;
}
buffer = (uint8_t*)malloc(bufferSize);
if (buffer) {
memset(buffer, 0, bufferSize);
Serial.printf("[%lu] [IMG] Allocated cache buffer: %d bytes for %dx%d\n", millis(), bufferSize, w, h);
}
return buffer != nullptr;
}
void setPixel(int screenX, int screenY, uint8_t value) {
if (!buffer) return;
int localX = screenX - originX;
int localY = screenY - originY;
if (localX < 0 || localX >= width || localY < 0 || localY >= height) return;
int byteIdx = localY * bytesPerRow + localX / 4;
int bitShift = 6 - (localX % 4) * 2; // MSB first: pixel 0 at bits 6-7
buffer[byteIdx] = (buffer[byteIdx] & ~(0x03 << bitShift)) | ((value & 0x03) << bitShift);
}
bool writeToFile(const std::string& cachePath) {
if (!buffer) return false;
FsFile cacheFile;
if (!SdMan.openFileForWrite("IMG", cachePath, cacheFile)) {
Serial.printf("[%lu] [IMG] Failed to open cache file for writing: %s\n", millis(), cachePath.c_str());
return false;
}
uint16_t w = width;
uint16_t h = height;
cacheFile.write(&w, 2);
cacheFile.write(&h, 2);
cacheFile.write(buffer, bytesPerRow * height);
cacheFile.close();
Serial.printf("[%lu] [IMG] Cache written: %s (%dx%d, %d bytes)\n", millis(), cachePath.c_str(), width, height,
4 + bytesPerRow * height);
return true;
}
~PixelCache() {
if (buffer) {
free(buffer);
buffer = nullptr;
}
}
};