A fork of https://github.com/crosspoint-reader/crosspoint-reader
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at records-reader 150 lines 5.4 kB view raw
1#include "BitmapHelpers.h" 2 3#include <cstdint> 4#include <cstring> // Added for memset 5 6#include "Bitmap.h" 7 8// Brightness/Contrast adjustments: 9constexpr bool USE_BRIGHTNESS = false; // true: apply brightness/gamma adjustments 10constexpr int BRIGHTNESS_BOOST = 10; // Brightness offset (0-50) 11constexpr bool GAMMA_CORRECTION = false; // Gamma curve (brightens midtones) 12constexpr float CONTRAST_FACTOR = 1.15f; // Contrast multiplier (1.0 = no change, >1 = more contrast) 13constexpr bool USE_NOISE_DITHERING = false; // Hash-based noise dithering 14 15// Integer approximation of gamma correction (brightens midtones) 16// Uses a simple curve: out = 255 * sqrt(in/255) ≈ sqrt(in * 255) 17static inline int applyGamma(int gray) { 18 if (!GAMMA_CORRECTION) return gray; 19 // Fast integer square root approximation for gamma ~0.5 (brightening) 20 // This brightens dark/mid tones while preserving highlights 21 const int product = gray * 255; 22 // Newton-Raphson integer sqrt (2 iterations for good accuracy) 23 int x = gray; 24 if (x > 0) { 25 x = (x + product / x) >> 1; 26 x = (x + product / x) >> 1; 27 } 28 return x > 255 ? 255 : x; 29} 30 31// Apply contrast adjustment around midpoint (128) 32// factor > 1.0 increases contrast, < 1.0 decreases 33static inline int applyContrast(int gray) { 34 // Integer-based contrast: (gray - 128) * factor + 128 35 // Using fixed-point: factor 1.15 ≈ 115/100 36 constexpr int factorNum = static_cast<int>(CONTRAST_FACTOR * 100); 37 int adjusted = ((gray - 128) * factorNum) / 100 + 128; 38 if (adjusted < 0) adjusted = 0; 39 if (adjusted > 255) adjusted = 255; 40 return adjusted; 41} 42// Combined brightness/contrast/gamma adjustment 43int adjustPixel(int gray) { 44 if (!USE_BRIGHTNESS) return gray; 45 46 // Order: contrast first, then brightness, then gamma 47 gray = applyContrast(gray); 48 gray += BRIGHTNESS_BOOST; 49 if (gray > 255) gray = 255; 50 if (gray < 0) gray = 0; 51 gray = applyGamma(gray); 52 53 return gray; 54} 55// Simple quantization without dithering - divide into 4 levels 56// The thresholds are fine-tuned to the X4 display 57uint8_t quantizeSimple(int gray) { 58 if (gray < 45) { 59 return 0; 60 } else if (gray < 70) { 61 return 1; 62 } else if (gray < 140) { 63 return 2; 64 } else { 65 return 3; 66 } 67} 68 69// Hash-based noise dithering - survives downsampling without moiré artifacts 70// Uses integer hash to generate pseudo-random threshold per pixel 71static inline uint8_t quantizeNoise(int gray, int x, int y) { 72 uint32_t hash = static_cast<uint32_t>(x) * 374761393u + static_cast<uint32_t>(y) * 668265263u; 73 hash = (hash ^ (hash >> 13)) * 1274126177u; 74 const int threshold = static_cast<int>(hash >> 24); 75 76 const int scaled = gray * 3; 77 if (scaled < 255) { 78 return (scaled + threshold >= 255) ? 1 : 0; 79 } else if (scaled < 510) { 80 return ((scaled - 255) + threshold >= 255) ? 2 : 1; 81 } else { 82 return ((scaled - 510) + threshold >= 255) ? 3 : 2; 83 } 84} 85 86// Main quantization function - selects between methods based on config 87uint8_t quantize(int gray, int x, int y) { 88 if (USE_NOISE_DITHERING) { 89 return quantizeNoise(gray, x, y); 90 } else { 91 return quantizeSimple(gray); 92 } 93} 94 95// 1-bit noise dithering for fast home screen rendering 96// Uses hash-based noise for consistent dithering that works well at small sizes 97uint8_t quantize1bit(int gray, int x, int y) { 98 gray = adjustPixel(gray); 99 100 // Generate noise threshold using integer hash (no regular pattern to alias) 101 uint32_t hash = static_cast<uint32_t>(x) * 374761393u + static_cast<uint32_t>(y) * 668265263u; 102 hash = (hash ^ (hash >> 13)) * 1274126177u; 103 const int threshold = static_cast<int>(hash >> 24); // 0-255 104 105 // Simple threshold with noise: gray >= (128 + noise offset) -> white 106 // The noise adds variation around the 128 midpoint 107 const int adjustedThreshold = 128 + ((threshold - 128) / 2); // Range: 64-192 108 return (gray >= adjustedThreshold) ? 1 : 0; 109} 110 111void createBmpHeader(BmpHeader* bmpHeader, int width, int height, BmpRowOrder rowOrder) { 112 if (!bmpHeader) return; 113 114 // Zero out the memory to ensure no garbage data if called on uninitialized stack memory 115 std::memset(bmpHeader, 0, sizeof(BmpHeader)); 116 117 uint32_t rowSize = (width + 31) / 32 * 4; 118 uint32_t imageSize = rowSize * height; 119 uint32_t fileSize = sizeof(BmpHeader) + imageSize; 120 121 bmpHeader->fileHeader.bfType = 0x4D42; 122 bmpHeader->fileHeader.bfSize = fileSize; 123 bmpHeader->fileHeader.bfReserved1 = 0; 124 bmpHeader->fileHeader.bfReserved2 = 0; 125 bmpHeader->fileHeader.bfOffBits = sizeof(BmpHeader); 126 127 bmpHeader->infoHeader.biSize = sizeof(bmpHeader->infoHeader); 128 bmpHeader->infoHeader.biWidth = width; 129 bmpHeader->infoHeader.biHeight = (rowOrder == BmpRowOrder::TopDown) ? -height : height; 130 bmpHeader->infoHeader.biPlanes = 1; 131 bmpHeader->infoHeader.biBitCount = 1; 132 bmpHeader->infoHeader.biCompression = 0; 133 bmpHeader->infoHeader.biSizeImage = imageSize; 134 bmpHeader->infoHeader.biXPelsPerMeter = 2835; // 72 DPI 135 bmpHeader->infoHeader.biYPelsPerMeter = 2835; // 72 DPI 136 bmpHeader->infoHeader.biClrUsed = 2; 137 bmpHeader->infoHeader.biClrImportant = 2; 138 139 // Color 0 (black) 140 bmpHeader->colors[0].rgbBlue = 0; 141 bmpHeader->colors[0].rgbGreen = 0; 142 bmpHeader->colors[0].rgbRed = 0; 143 bmpHeader->colors[0].rgbReserved = 0; 144 145 // Color 1 (white) 146 bmpHeader->colors[1].rgbBlue = 255; 147 bmpHeader->colors[1].rgbGreen = 255; 148 bmpHeader->colors[1].rgbRed = 255; 149 bmpHeader->colors[1].rgbReserved = 0; 150}