Something went wrong. Try again.
A fork of https://github.com/crosspoint-reader/crosspoint-reader
Something went wrong. Try again.
11 kB · 366 lines
C
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366#pragma once
#include <cstddef>#include <cstdint>#include <cstring>#include <memory>
#include "../Memory/Memory.h"
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);
struct GrayPlanePixel { bool write; bool black;};
// level: 0=black, 1=dark, 2=light, 3=white. drawPixel(true) clears a bit.constexpr GrayPlanePixel grayPlanePixel(uint8_t level, bool msb, bool absolute) { if (absolute) return {true, !(level == 3 || level == (msb ? 2 : 1))}; return {msb ? (level == 1 || level == 2) : level == 1, false};}
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) { if (width <= 0) return; const size_t candidateRowSize = static_cast<size_t>(width) + 4; if (candidateRowSize > SIZE_MAX / (3 * sizeof(int16_t))) return; rowSize = candidateRowSize; errorRows = makeUniqueNoThrow<int16_t[]>(rowSize * 3); if (!errorRows) return; errorRow0 = errorRows.get(); errorRow1 = errorRow0 + rowSize; errorRow2 = errorRow1 + rowSize; }
// Callers must check row allocation before processing pixels. bool isValid() const { return errorRows != nullptr; }
// 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) { if (!isValid()) return adjustPixel(gray) < 128 ? 0 : 1;
// 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() { if (!isValid()) return; int16_t* temp = errorRow0; errorRow0 = errorRow1; errorRow1 = errorRow2; errorRow2 = temp; memset(errorRow2, 0, rowSize * sizeof(int16_t)); }
void reset() { if (!isValid()) return; memset(errorRow0, 0, rowSize * sizeof(int16_t)); memset(errorRow1, 0, rowSize * sizeof(int16_t)); memset(errorRow2, 0, rowSize * sizeof(int16_t)); }
private: size_t rowSize{0}; std::unique_ptr<int16_t[]> errorRows; int16_t* errorRow0 = nullptr; int16_t* errorRow1 = nullptr; int16_t* errorRow2 = nullptr;};
// 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, bool originalThresholds = false) : originalThresholds(originalThresholds) { if (width <= 0) return; const size_t candidateRowSize = static_cast<size_t>(width) + 4; if (candidateRowSize > SIZE_MAX / (3 * sizeof(int16_t))) return; rowSize = candidateRowSize; errorRows = makeUniqueNoThrow<int16_t[]>(rowSize * 3); if (!errorRows) return; errorRow0 = errorRows.get(); errorRow1 = errorRow0 + rowSize; errorRow2 = errorRow1 + rowSize; }
// Callers must check row allocation before processing pixels. bool isValid() const { return errorRows != nullptr; }
// **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 + (isValid() ? errorRow0[x + 2] : 0); if (adjusted < 0) adjusted = 0; if (adjusted > 255) adjusted = 255;
// Quantize to 4 levels uint8_t quantized; int quantizedValue; if (originalThresholds) { 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 { // Legacy panel tuning; slightly darker midtones. if (adjusted < 30) { quantized = 0; quantizedValue = 15; } else if (adjusted < 55) { quantized = 1; quantizedValue = 35; } else if (adjusted < 150) { quantized = 2; quantizedValue = 90; } else { quantized = 3; quantizedValue = 210; } }
if (!isValid()) return quantized;
// 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() { if (!isValid()) return; int16_t* temp = errorRow0; errorRow0 = errorRow1; errorRow1 = errorRow2; errorRow2 = temp; memset(errorRow2, 0, rowSize * sizeof(int16_t)); }
void reset() { if (!isValid()) return; memset(errorRow0, 0, rowSize * sizeof(int16_t)); memset(errorRow1, 0, rowSize * sizeof(int16_t)); memset(errorRow2, 0, rowSize * sizeof(int16_t)); }
private: size_t rowSize{0}; std::unique_ptr<int16_t[]> errorRows; int16_t* errorRow0 = nullptr; int16_t* errorRow1 = nullptr; int16_t* errorRow2 = nullptr; const bool originalThresholds;};
// 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, bool originalThresholds = false) : rowCount(0), originalThresholds(originalThresholds) { if (width <= 0) return; const size_t candidateRowSize = static_cast<size_t>(width) + 2; if (candidateRowSize > SIZE_MAX / (2 * sizeof(int16_t))) return; rowSize = candidateRowSize; errorRows = makeUniqueNoThrow<int16_t[]>(rowSize * 2); if (!errorRows) return; errorCurRow = errorRows.get(); errorNextRow = errorCurRow + rowSize; }
// Callers must check row allocation before processing pixels. bool isValid() const { return errorRows != nullptr; }
// **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 + (isValid() ? errorCurRow[x + 1] : 0);
// 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 (originalThresholds) { 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 { // Legacy panel tuning; slightly darker midtones. if (adjusted < 30) { quantized = 0; quantizedValue = 15; } else if (adjusted < 55) { quantized = 1; quantizedValue = 35; } else if (adjusted < 150) { quantized = 2; quantizedValue = 90; } else { quantized = 3; quantizedValue = 210; } }
if (!isValid()) return quantized;
// 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() { if (!isValid()) return; // Swap buffers int16_t* temp = errorCurRow; errorCurRow = errorNextRow; errorNextRow = temp; // Clear the next row buffer memset(errorNextRow, 0, rowSize * 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() { if (!isValid()) return; memset(errorCurRow, 0, rowSize * sizeof(int16_t)); memset(errorNextRow, 0, rowSize * sizeof(int16_t)); rowCount = 0; }
private: int rowCount; size_t rowSize{0}; std::unique_ptr<int16_t[]> errorRows; int16_t* errorCurRow = nullptr; int16_t* errorNextRow = nullptr; const bool originalThresholds;};