Something went wrong. Try again.
A fork of https://github.com/crosspoint-reader/crosspoint-reader
Something went wrong. Try again.
9.2 kB · 322 lines
C
at master
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323#pragma once
#include <cstdint>#include <cstring>
struct BmpHeader;
// Helper functionsuint8_t quantize(int gray, int x, int y);uint8_t quantizeSimple(int gray);uint8_t quantize1bit(int gray, int x, int y);int adjustPixel(int gray);
enum class BmpRowOrder { BottomUp, TopDown };
// Populates a 1-bit BMP header in the provided memory.void createBmpHeader(BmpHeader* bmpHeader, int width, int height, BmpRowOrder rowOrder);
// 1-bit Atkinson dithering - better quality than noise dithering for thumbnails// Error distribution pattern (same as 2-bit but quantizes to 2 levels):// X 1/8 1/8// 1/8 1/8 1/8// 1/8class Atkinson1BitDitherer { public: explicit Atkinson1BitDitherer(int width) : width(width) { errorRow0 = new int16_t[width + 4](); // Current row errorRow1 = new int16_t[width + 4](); // Next row errorRow2 = new int16_t[width + 4](); // Row after next }
~Atkinson1BitDitherer() { delete[] errorRow0; delete[] errorRow1; delete[] errorRow2; }
// EXPLICITLY DELETE THE COPY CONSTRUCTOR Atkinson1BitDitherer(const Atkinson1BitDitherer& other) = delete;
// EXPLICITLY DELETE THE COPY ASSIGNMENT OPERATOR Atkinson1BitDitherer& operator=(const Atkinson1BitDitherer& other) = delete;
uint8_t processPixel(int gray, int x) { // Apply brightness/contrast/gamma adjustments gray = adjustPixel(gray);
// Add accumulated error int adjusted = gray + errorRow0[x + 2]; if (adjusted < 0) adjusted = 0; if (adjusted > 255) adjusted = 255;
// Quantize to 2 levels (1-bit): 0 = black, 1 = white uint8_t quantized; int quantizedValue; if (adjusted < 128) { quantized = 0; quantizedValue = 0; } else { quantized = 1; quantizedValue = 255; }
// Calculate error (only distribute 6/8 = 75%) int error = (adjusted - quantizedValue) >> 3; // error/8
// Distribute 1/8 to each of 6 neighbors errorRow0[x + 3] += error; // Right errorRow0[x + 4] += error; // Right+1 errorRow1[x + 1] += error; // Bottom-left errorRow1[x + 2] += error; // Bottom errorRow1[x + 3] += error; // Bottom-right errorRow2[x + 2] += error; // Two rows down
return quantized; }
void nextRow() { int16_t* temp = errorRow0; errorRow0 = errorRow1; errorRow1 = errorRow2; errorRow2 = temp; memset(errorRow2, 0, (width + 4) * sizeof(int16_t)); }
void reset() { memset(errorRow0, 0, (width + 4) * sizeof(int16_t)); memset(errorRow1, 0, (width + 4) * sizeof(int16_t)); memset(errorRow2, 0, (width + 4) * sizeof(int16_t)); }
private: int width; int16_t* errorRow0; int16_t* errorRow1; int16_t* errorRow2;};
// Atkinson dithering - distributes only 6/8 (75%) of error for cleaner results// Error distribution pattern:// X 1/8 1/8// 1/8 1/8 1/8// 1/8// Less error buildup = fewer artifacts than Floyd-Steinbergclass AtkinsonDitherer { public: explicit AtkinsonDitherer(int width) : width(width) { errorRow0 = new int16_t[width + 4](); // Current row errorRow1 = new int16_t[width + 4](); // Next row errorRow2 = new int16_t[width + 4](); // Row after next }
~AtkinsonDitherer() { delete[] errorRow0; delete[] errorRow1; delete[] errorRow2; } // **1. EXPLICITLY DELETE THE COPY CONSTRUCTOR** AtkinsonDitherer(const AtkinsonDitherer& other) = delete;
// **2. EXPLICITLY DELETE THE COPY ASSIGNMENT OPERATOR** AtkinsonDitherer& operator=(const AtkinsonDitherer& other) = delete;
uint8_t processPixel(int gray, int x) { // Add accumulated error int adjusted = gray + errorRow0[x + 2]; if (adjusted < 0) adjusted = 0; if (adjusted > 255) adjusted = 255;
// Quantize to 4 levels uint8_t quantized; int quantizedValue; if (false) { // original thresholds if (adjusted < 43) { quantized = 0; quantizedValue = 0; } else if (adjusted < 128) { quantized = 1; quantizedValue = 85; } else if (adjusted < 213) { quantized = 2; quantizedValue = 170; } else { quantized = 3; quantizedValue = 255; } } else { // fine-tuned to X4 eink display if (adjusted < 30) { quantized = 0; quantizedValue = 15; } else if (adjusted < 50) { quantized = 1; quantizedValue = 30; } else if (adjusted < 140) { quantized = 2; quantizedValue = 80; } else { quantized = 3; quantizedValue = 210; } }
// Calculate error (only distribute 6/8 = 75%) int error = (adjusted - quantizedValue) >> 3; // error/8
// Distribute 1/8 to each of 6 neighbors errorRow0[x + 3] += error; // Right errorRow0[x + 4] += error; // Right+1 errorRow1[x + 1] += error; // Bottom-left errorRow1[x + 2] += error; // Bottom errorRow1[x + 3] += error; // Bottom-right errorRow2[x + 2] += error; // Two rows down
return quantized; }
void nextRow() { int16_t* temp = errorRow0; errorRow0 = errorRow1; errorRow1 = errorRow2; errorRow2 = temp; memset(errorRow2, 0, (width + 4) * sizeof(int16_t)); }
void reset() { memset(errorRow0, 0, (width + 4) * sizeof(int16_t)); memset(errorRow1, 0, (width + 4) * sizeof(int16_t)); memset(errorRow2, 0, (width + 4) * sizeof(int16_t)); }
private: int width; int16_t* errorRow0; int16_t* errorRow1; int16_t* errorRow2;};
// Floyd-Steinberg error diffusion dithering with serpentine scanning// Serpentine scanning alternates direction each row to reduce "worm" artifacts// Error distribution pattern (left-to-right):// X 7/16// 3/16 5/16 1/16// Error distribution pattern (right-to-left, mirrored):// 1/16 5/16 3/16// 7/16 Xclass FloydSteinbergDitherer { public: explicit FloydSteinbergDitherer(int width) : width(width), rowCount(0) { errorCurRow = new int16_t[width + 2](); // +2 for boundary handling errorNextRow = new int16_t[width + 2](); }
~FloydSteinbergDitherer() { delete[] errorCurRow; delete[] errorNextRow; }
// **1. EXPLICITLY DELETE THE COPY CONSTRUCTOR** FloydSteinbergDitherer(const FloydSteinbergDitherer& other) = delete;
// **2. EXPLICITLY DELETE THE COPY ASSIGNMENT OPERATOR** FloydSteinbergDitherer& operator=(const FloydSteinbergDitherer& other) = delete;
// Process a single pixel and return quantized 2-bit value // x is the logical x position (0 to width-1), direction handled internally uint8_t processPixel(int gray, int x) { // Add accumulated error to this pixel int adjusted = gray + errorCurRow[x + 1];
// Clamp to valid range if (adjusted < 0) adjusted = 0; if (adjusted > 255) adjusted = 255;
// Quantize to 4 levels (0, 85, 170, 255) uint8_t quantized; int quantizedValue; if (false) { // original thresholds if (adjusted < 43) { quantized = 0; quantizedValue = 0; } else if (adjusted < 128) { quantized = 1; quantizedValue = 85; } else if (adjusted < 213) { quantized = 2; quantizedValue = 170; } else { quantized = 3; quantizedValue = 255; } } else { // fine-tuned to X4 eink display if (adjusted < 30) { quantized = 0; quantizedValue = 15; } else if (adjusted < 50) { quantized = 1; quantizedValue = 30; } else if (adjusted < 140) { quantized = 2; quantizedValue = 80; } else { quantized = 3; quantizedValue = 210; } }
// Calculate error int error = adjusted - quantizedValue;
// Distribute error to neighbors (serpentine: direction-aware) if (!isReverseRow()) { // Left to right: standard distribution // Right: 7/16 errorCurRow[x + 2] += (error * 7) >> 4; // Bottom-left: 3/16 errorNextRow[x] += (error * 3) >> 4; // Bottom: 5/16 errorNextRow[x + 1] += (error * 5) >> 4; // Bottom-right: 1/16 errorNextRow[x + 2] += (error) >> 4; } else { // Right to left: mirrored distribution // Left: 7/16 errorCurRow[x] += (error * 7) >> 4; // Bottom-right: 3/16 errorNextRow[x + 2] += (error * 3) >> 4; // Bottom: 5/16 errorNextRow[x + 1] += (error * 5) >> 4; // Bottom-left: 1/16 errorNextRow[x] += (error) >> 4; }
return quantized; }
// Call at the end of each row to swap buffers void nextRow() { // Swap buffers int16_t* temp = errorCurRow; errorCurRow = errorNextRow; errorNextRow = temp; // Clear the next row buffer memset(errorNextRow, 0, (width + 2) * sizeof(int16_t)); rowCount++; }
// Check if current row should be processed in reverse bool isReverseRow() const { return (rowCount & 1) != 0; }
// Reset for a new image or MCU block void reset() { memset(errorCurRow, 0, (width + 2) * sizeof(int16_t)); memset(errorNextRow, 0, (width + 2) * sizeof(int16_t)); rowCount = 0; }
private: int width; int rowCount; int16_t* errorCurRow; int16_t* errorNextRow;};