feat: Extend high contrast mode to entire UI

- Add global high contrast mode flag in BitmapHelpers
- Modify quantization thresholds for high contrast rendering
- Update ditherer classes (Atkinson, Floyd-Steinberg) for contrast mode
- Add displayContrast setting with persistence
- Add "High Contrast" toggle in display settings
- Apply high contrast mode on startup from saved settings
This commit is contained in:
cottongin 2026-01-27 20:34:44 -05:00
parent 1496ce68a6
commit 158caacfe0
No known key found for this signature in database
GPG Key ID: 0ECC91FE4655C262
7 changed files with 80 additions and 27 deletions

View File

@ -2,6 +2,17 @@
#include <cstdint> #include <cstdint>
// Global high contrast mode flag
static bool g_highContrastMode = false;
void setHighContrastMode(bool enabled) {
g_highContrastMode = enabled;
}
bool isHighContrastMode() {
return g_highContrastMode;
}
// Brightness/Contrast adjustments: // Brightness/Contrast adjustments:
constexpr bool USE_BRIGHTNESS = false; // true: apply brightness/gamma adjustments constexpr bool USE_BRIGHTNESS = false; // true: apply brightness/gamma adjustments
constexpr int BRIGHTNESS_BOOST = 10; // Brightness offset (0-50) constexpr int BRIGHTNESS_BOOST = 10; // Brightness offset (0-50)
@ -52,6 +63,19 @@ int adjustPixel(int gray) {
// Simple quantization without dithering - divide into 4 levels // Simple quantization without dithering - divide into 4 levels
// The thresholds are fine-tuned to the X4 display // The thresholds are fine-tuned to the X4 display
uint8_t quantizeSimple(int gray) { uint8_t quantizeSimple(int gray) {
if (g_highContrastMode) {
// High contrast: push mid-grays toward black/white
if (gray < 60) {
return 0;
} else if (gray < 100) {
return 1;
} else if (gray < 180) {
return 2;
} else {
return 3;
}
} else {
// Normal thresholds
if (gray < 45) { if (gray < 45) {
return 0; return 0;
} else if (gray < 70) { } else if (gray < 70) {
@ -62,6 +86,7 @@ uint8_t quantizeSimple(int gray) {
return 3; return 3;
} }
} }
}
// Hash-based noise dithering - survives downsampling without moiré artifacts // Hash-based noise dithering - survives downsampling without moiré artifacts
// Uses integer hash to generate pseudo-random threshold per pixel // Uses integer hash to generate pseudo-random threshold per pixel

View File

@ -2,6 +2,11 @@
#include <cstring> #include <cstring>
// Global high contrast mode control
// When enabled, quantization thresholds are adjusted to push mid-grays toward black/white
void setHighContrastMode(bool enabled);
bool isHighContrastMode();
// Helper functions // Helper functions
uint8_t quantize(int gray, int x, int y); uint8_t quantize(int gray, int x, int y);
uint8_t quantizeSimple(int gray); uint8_t quantizeSimple(int gray);
@ -122,21 +127,23 @@ class AtkinsonDitherer {
// Quantize to 4 levels // Quantize to 4 levels
uint8_t quantized; uint8_t quantized;
int quantizedValue; int quantizedValue;
if (false) { // original thresholds if (isHighContrastMode()) {
if (adjusted < 43) { // High contrast thresholds - push mid-grays toward extremes
if (adjusted < 50) {
quantized = 0; quantized = 0;
quantizedValue = 0; quantizedValue = 15;
} else if (adjusted < 128) { } else if (adjusted < 90) {
quantized = 1; quantized = 1;
quantizedValue = 85; quantizedValue = 50;
} else if (adjusted < 213) { } else if (adjusted < 170) {
quantized = 2; quantized = 2;
quantizedValue = 170; quantizedValue = 130;
} else { } else {
quantized = 3; quantized = 3;
quantizedValue = 255; quantizedValue = 230;
} }
} else { // fine-tuned to X4 eink display } else {
// Normal thresholds - fine-tuned to X4 eink display
if (adjusted < 30) { if (adjusted < 30) {
quantized = 0; quantized = 0;
quantizedValue = 15; quantizedValue = 15;
@ -223,24 +230,26 @@ class FloydSteinbergDitherer {
if (adjusted < 0) adjusted = 0; if (adjusted < 0) adjusted = 0;
if (adjusted > 255) adjusted = 255; if (adjusted > 255) adjusted = 255;
// Quantize to 4 levels (0, 85, 170, 255) // Quantize to 4 levels
uint8_t quantized; uint8_t quantized;
int quantizedValue; int quantizedValue;
if (false) { // original thresholds if (isHighContrastMode()) {
if (adjusted < 43) { // High contrast thresholds - push mid-grays toward extremes
if (adjusted < 50) {
quantized = 0; quantized = 0;
quantizedValue = 0; quantizedValue = 15;
} else if (adjusted < 128) { } else if (adjusted < 90) {
quantized = 1; quantized = 1;
quantizedValue = 85; quantizedValue = 50;
} else if (adjusted < 213) { } else if (adjusted < 170) {
quantized = 2; quantized = 2;
quantizedValue = 170; quantizedValue = 130;
} else { } else {
quantized = 3; quantized = 3;
quantizedValue = 255; quantizedValue = 230;
} }
} else { // fine-tuned to X4 eink display } else {
// Normal thresholds - fine-tuned to X4 eink display
if (adjusted < 30) { if (adjusted < 30) {
quantized = 0; quantized = 0;
quantizedValue = 15; quantizedValue = 15;

View File

@ -23,7 +23,7 @@ void readAndValidate(FsFile& file, uint8_t& member, const uint8_t maxValue) {
namespace { namespace {
constexpr uint8_t SETTINGS_FILE_VERSION = 1; constexpr uint8_t SETTINGS_FILE_VERSION = 1;
// Increment this when adding new persisted settings fields // Increment this when adding new persisted settings fields
constexpr uint8_t SETTINGS_COUNT = 26; // 25 + sleepScreenCoverFilter constexpr uint8_t SETTINGS_COUNT = 27; // 26 + displayContrast
constexpr char SETTINGS_FILE[] = "/.crosspoint/settings.bin"; constexpr char SETTINGS_FILE[] = "/.crosspoint/settings.bin";
} // namespace } // namespace
@ -66,6 +66,8 @@ bool CrossPointSettings::saveToFile() const {
serialization::writeString(outputFile, std::string(opdsPassword)); serialization::writeString(outputFile, std::string(opdsPassword));
// PR #476: B&W filter for cover images // PR #476: B&W filter for cover images
serialization::writePod(outputFile, sleepScreenCoverFilter); serialization::writePod(outputFile, sleepScreenCoverFilter);
// System-wide display contrast
serialization::writePod(outputFile, displayContrast);
// New fields added at end for backward compatibility // New fields added at end for backward compatibility
outputFile.close(); outputFile.close();
@ -167,6 +169,9 @@ bool CrossPointSettings::loadFromFile() {
// PR #476: B&W filter for cover images // PR #476: B&W filter for cover images
readAndValidate(inputFile, sleepScreenCoverFilter, SLEEP_SCREEN_COVER_FILTER_COUNT); readAndValidate(inputFile, sleepScreenCoverFilter, SLEEP_SCREEN_COVER_FILTER_COUNT);
if (++settingsRead >= fileSettingsCount) break; if (++settingsRead >= fileSettingsCount) break;
// System-wide display contrast (0 = normal, 1 = high)
serialization::readPod(inputFile, displayContrast);
if (++settingsRead >= fileSettingsCount) break;
// New fields added at end for backward compatibility // New fields added at end for backward compatibility
} while (false); } while (false);

View File

@ -133,6 +133,8 @@ class CrossPointSettings {
uint8_t hideBatteryPercentage = HIDE_NEVER; uint8_t hideBatteryPercentage = HIDE_NEVER;
// Long-press chapter skip on side buttons // Long-press chapter skip on side buttons
uint8_t longPressChapterSkip = 1; uint8_t longPressChapterSkip = 1;
// System-wide display contrast (0 = normal, 1 = high)
uint8_t displayContrast = 0;
// Pinned list name (empty = none pinned) // Pinned list name (empty = none pinned)
char pinnedListName[64] = ""; char pinnedListName[64] = "";

View File

@ -1,5 +1,6 @@
#include "CategorySettingsActivity.h" #include "CategorySettingsActivity.h"
#include <BitmapHelpers.h>
#include <GfxRenderer.h> #include <GfxRenderer.h>
#include <HardwareSerial.h> #include <HardwareSerial.h>
@ -115,6 +116,11 @@ void CategorySettingsActivity::toggleCurrentSetting() {
// Toggle the boolean value using the member pointer // Toggle the boolean value using the member pointer
const bool currentValue = SETTINGS.*(setting.valuePtr); const bool currentValue = SETTINGS.*(setting.valuePtr);
SETTINGS.*(setting.valuePtr) = !currentValue; SETTINGS.*(setting.valuePtr) = !currentValue;
// Handle side effects for specific settings
if (setting.valuePtr == &CrossPointSettings::displayContrast) {
setHighContrastMode(SETTINGS.displayContrast == 1);
}
} else if (setting.type == SettingType::ENUM && setting.valuePtr != nullptr) { } else if (setting.type == SettingType::ENUM && setting.valuePtr != nullptr) {
const uint8_t currentValue = SETTINGS.*(setting.valuePtr); const uint8_t currentValue = SETTINGS.*(setting.valuePtr);
SETTINGS.*(setting.valuePtr) = (currentValue + 1) % static_cast<uint8_t>(setting.enumValues.size()); SETTINGS.*(setting.valuePtr) = (currentValue + 1) % static_cast<uint8_t>(setting.enumValues.size());

View File

@ -19,6 +19,7 @@ const SettingInfo displaySettings[displaySettingsCount] = {
SettingInfo::Enum("Sleep Screen Cover Mode", &CrossPointSettings::sleepScreenCoverMode, {"Fit", "Crop", "Actual"}), SettingInfo::Enum("Sleep Screen Cover Mode", &CrossPointSettings::sleepScreenCoverMode, {"Fit", "Crop", "Actual"}),
SettingInfo::Enum("Sleep Screen Cover Filter", &CrossPointSettings::sleepScreenCoverFilter, SettingInfo::Enum("Sleep Screen Cover Filter", &CrossPointSettings::sleepScreenCoverFilter,
{"None", "Contrast", "Inverted"}), {"None", "Contrast", "Inverted"}),
SettingInfo::Toggle("High Contrast", &CrossPointSettings::displayContrast),
SettingInfo::Enum("Status Bar", &CrossPointSettings::statusBar, SettingInfo::Enum("Status Bar", &CrossPointSettings::statusBar,
{"None", "No Progress", "Full w/ Percentage", "Full w/ Progress Bar", "Progress Bar"}), {"None", "No Progress", "Full w/ Percentage", "Full w/ Progress Bar", "Progress Bar"}),
SettingInfo::Enum("Hide Battery %", &CrossPointSettings::hideBatteryPercentage, {"Never", "In Reader", "Always"}), SettingInfo::Enum("Hide Battery %", &CrossPointSettings::hideBatteryPercentage, {"Never", "In Reader", "Always"}),

View File

@ -6,9 +6,12 @@
#include <SDCardManager.h> #include <SDCardManager.h>
#include <SPI.h> #include <SPI.h>
#include <builtinFonts/all.h> #include <builtinFonts/all.h>
#include <esp_heap_caps.h>
#include <cstring> #include <cstring>
#include <BitmapHelpers.h>
#include "Battery.h" #include "Battery.h"
#include "BookListStore.h" #include "BookListStore.h"
#include "CrossPointSettings.h" #include "CrossPointSettings.h"
@ -470,6 +473,8 @@ void setup() {
} }
SETTINGS.loadFromFile(); SETTINGS.loadFromFile();
// Apply high contrast mode from settings
setHighContrastMode(SETTINGS.displayContrast == 1);
if (isWakeupByPowerButton()) { if (isWakeupByPowerButton()) {
// For normal wakeups, verify power button press duration // For normal wakeups, verify power button press duration