mirror of
https://github.com/daveallie/crosspoint-reader.git
synced 2026-02-05 07:07:38 +03:00
refactor: use shared DitherUtils and PixelCache in JPEG decoder
Address review comments #2 and #3: - Remove duplicated bayer4x4, applyBayerDither4Level(), drawPixelWithRenderMode(), and PixelCache from JPEG decoder - Include shared DitherUtils.h and PixelCache.h instead
This commit is contained in:
parent
b0f28a59e7
commit
ea5b2c06df
@ -9,6 +9,9 @@
|
|||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
||||||
|
#include "DitherUtils.h"
|
||||||
|
#include "PixelCache.h"
|
||||||
|
|
||||||
struct JpegContext {
|
struct JpegContext {
|
||||||
FsFile& file;
|
FsFile& file;
|
||||||
uint8_t buffer[512];
|
uint8_t buffer[512];
|
||||||
@ -17,108 +20,6 @@ struct JpegContext {
|
|||||||
JpegContext(FsFile& f) : file(f), bufferPos(0), bufferFilled(0) {}
|
JpegContext(FsFile& f) : file(f), bufferPos(0), bufferFilled(0) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Cache buffer for storing 2-bit pixels during decode
|
|
||||||
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) {}
|
|
||||||
|
|
||||||
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 = bytesPerRow * h;
|
|
||||||
buffer = (uint8_t*)malloc(bufferSize);
|
|
||||||
if (buffer) {
|
|
||||||
memset(buffer, 0, bufferSize);
|
|
||||||
Serial.printf("[%lu] [JPG] 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] [JPG] 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] [JPG] 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 4x4 Bayer matrix for ordered dithering
|
|
||||||
static const uint8_t bayer4x4[4][4] = {
|
|
||||||
{0, 8, 2, 10},
|
|
||||||
{12, 4, 14, 6},
|
|
||||||
{3, 11, 1, 9},
|
|
||||||
{15, 7, 13, 5},
|
|
||||||
};
|
|
||||||
|
|
||||||
// Apply Bayer dithering and quantize to 4 levels (0-3)
|
|
||||||
// Stateless - works correctly with any pixel processing order (ideal for MCU-based decoding)
|
|
||||||
static uint8_t applyBayerDither4Level(uint8_t gray, int x, int y) {
|
|
||||||
int bayer = bayer4x4[y & 3][x & 3];
|
|
||||||
int dither = (bayer - 8) * 5; // Scale to ±40 (half of quantization step 85)
|
|
||||||
|
|
||||||
int adjusted = gray + dither;
|
|
||||||
if (adjusted < 0) adjusted = 0;
|
|
||||||
if (adjusted > 255) adjusted = 255;
|
|
||||||
|
|
||||||
if (adjusted < 64) return 0;
|
|
||||||
if (adjusted < 128) return 1;
|
|
||||||
if (adjusted < 192) return 2;
|
|
||||||
return 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw a pixel respecting the current render mode for grayscale support
|
|
||||||
static void drawPixelWithRenderMode(GfxRenderer& renderer, int x, int y, uint8_t pixelValue) {
|
|
||||||
GfxRenderer::RenderMode renderMode = renderer.getRenderMode();
|
|
||||||
if (renderMode == GfxRenderer::BW && pixelValue < 3) {
|
|
||||||
renderer.drawPixel(x, y, true);
|
|
||||||
} else if (renderMode == GfxRenderer::GRAYSCALE_MSB && (pixelValue == 1 || pixelValue == 2)) {
|
|
||||||
renderer.drawPixel(x, y, false);
|
|
||||||
} else if (renderMode == GfxRenderer::GRAYSCALE_LSB && pixelValue == 1) {
|
|
||||||
renderer.drawPixel(x, y, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool JpegToFramebufferConverter::getDimensionsStatic(const std::string& imagePath, ImageDimensions& out) {
|
bool JpegToFramebufferConverter::getDimensionsStatic(const std::string& imagePath, ImageDimensions& out) {
|
||||||
FsFile file;
|
FsFile file;
|
||||||
if (!SdMan.openFileForRead("JPG", imagePath, file)) {
|
if (!SdMan.openFileForRead("JPG", imagePath, file)) {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user