From 158caacfe087278cdab1ab70e839862039c8c7d1 Mon Sep 17 00:00:00 2001 From: cottongin Date: Tue, 27 Jan 2026 20:34:44 -0500 Subject: [PATCH] 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 --- lib/GfxRenderer/BitmapHelpers.cpp | 39 ++++++++++++--- lib/GfxRenderer/BitmapHelpers.h | 47 +++++++++++-------- src/CrossPointSettings.cpp | 7 ++- src/CrossPointSettings.h | 2 + .../settings/CategorySettingsActivity.cpp | 6 +++ src/activities/settings/SettingsActivity.cpp | 1 + src/main.cpp | 5 ++ 7 files changed, 80 insertions(+), 27 deletions(-) diff --git a/lib/GfxRenderer/BitmapHelpers.cpp b/lib/GfxRenderer/BitmapHelpers.cpp index 465593e..e3a45be 100644 --- a/lib/GfxRenderer/BitmapHelpers.cpp +++ b/lib/GfxRenderer/BitmapHelpers.cpp @@ -2,6 +2,17 @@ #include +// 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: constexpr bool USE_BRIGHTNESS = false; // true: apply brightness/gamma adjustments constexpr int BRIGHTNESS_BOOST = 10; // Brightness offset (0-50) @@ -52,14 +63,28 @@ int adjustPixel(int gray) { // Simple quantization without dithering - divide into 4 levels // The thresholds are fine-tuned to the X4 display uint8_t quantizeSimple(int gray) { - if (gray < 45) { - return 0; - } else if (gray < 70) { - return 1; - } else if (gray < 140) { - return 2; + 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 { - return 3; + // Normal thresholds + if (gray < 45) { + return 0; + } else if (gray < 70) { + return 1; + } else if (gray < 140) { + return 2; + } else { + return 3; + } } } diff --git a/lib/GfxRenderer/BitmapHelpers.h b/lib/GfxRenderer/BitmapHelpers.h index 791e70b..cfca34b 100644 --- a/lib/GfxRenderer/BitmapHelpers.h +++ b/lib/GfxRenderer/BitmapHelpers.h @@ -2,6 +2,11 @@ #include +// 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 uint8_t quantize(int gray, int x, int y); uint8_t quantizeSimple(int gray); @@ -122,21 +127,23 @@ class AtkinsonDitherer { // Quantize to 4 levels uint8_t quantized; int quantizedValue; - if (false) { // original thresholds - if (adjusted < 43) { + if (isHighContrastMode()) { + // High contrast thresholds - push mid-grays toward extremes + if (adjusted < 50) { quantized = 0; - quantizedValue = 0; - } else if (adjusted < 128) { + quantizedValue = 15; + } else if (adjusted < 90) { quantized = 1; - quantizedValue = 85; - } else if (adjusted < 213) { + quantizedValue = 50; + } else if (adjusted < 170) { quantized = 2; - quantizedValue = 170; + quantizedValue = 130; } else { 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) { quantized = 0; quantizedValue = 15; @@ -223,24 +230,26 @@ class FloydSteinbergDitherer { if (adjusted < 0) adjusted = 0; if (adjusted > 255) adjusted = 255; - // Quantize to 4 levels (0, 85, 170, 255) + // Quantize to 4 levels uint8_t quantized; int quantizedValue; - if (false) { // original thresholds - if (adjusted < 43) { + if (isHighContrastMode()) { + // High contrast thresholds - push mid-grays toward extremes + if (adjusted < 50) { quantized = 0; - quantizedValue = 0; - } else if (adjusted < 128) { + quantizedValue = 15; + } else if (adjusted < 90) { quantized = 1; - quantizedValue = 85; - } else if (adjusted < 213) { + quantizedValue = 50; + } else if (adjusted < 170) { quantized = 2; - quantizedValue = 170; + quantizedValue = 130; } else { 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) { quantized = 0; quantizedValue = 15; diff --git a/src/CrossPointSettings.cpp b/src/CrossPointSettings.cpp index dafccb5..de1f03b 100644 --- a/src/CrossPointSettings.cpp +++ b/src/CrossPointSettings.cpp @@ -23,7 +23,7 @@ void readAndValidate(FsFile& file, uint8_t& member, const uint8_t maxValue) { namespace { constexpr uint8_t SETTINGS_FILE_VERSION = 1; // 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"; } // namespace @@ -66,6 +66,8 @@ bool CrossPointSettings::saveToFile() const { serialization::writeString(outputFile, std::string(opdsPassword)); // PR #476: B&W filter for cover images serialization::writePod(outputFile, sleepScreenCoverFilter); + // System-wide display contrast + serialization::writePod(outputFile, displayContrast); // New fields added at end for backward compatibility outputFile.close(); @@ -167,6 +169,9 @@ bool CrossPointSettings::loadFromFile() { // PR #476: B&W filter for cover images readAndValidate(inputFile, sleepScreenCoverFilter, SLEEP_SCREEN_COVER_FILTER_COUNT); 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 } while (false); diff --git a/src/CrossPointSettings.h b/src/CrossPointSettings.h index 79a42d8..a26e9a8 100644 --- a/src/CrossPointSettings.h +++ b/src/CrossPointSettings.h @@ -133,6 +133,8 @@ class CrossPointSettings { uint8_t hideBatteryPercentage = HIDE_NEVER; // Long-press chapter skip on side buttons uint8_t longPressChapterSkip = 1; + // System-wide display contrast (0 = normal, 1 = high) + uint8_t displayContrast = 0; // Pinned list name (empty = none pinned) char pinnedListName[64] = ""; diff --git a/src/activities/settings/CategorySettingsActivity.cpp b/src/activities/settings/CategorySettingsActivity.cpp index a805885..b95dbc2 100644 --- a/src/activities/settings/CategorySettingsActivity.cpp +++ b/src/activities/settings/CategorySettingsActivity.cpp @@ -1,5 +1,6 @@ #include "CategorySettingsActivity.h" +#include #include #include @@ -115,6 +116,11 @@ void CategorySettingsActivity::toggleCurrentSetting() { // Toggle the boolean value using the member pointer const bool currentValue = SETTINGS.*(setting.valuePtr); 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) { const uint8_t currentValue = SETTINGS.*(setting.valuePtr); SETTINGS.*(setting.valuePtr) = (currentValue + 1) % static_cast(setting.enumValues.size()); diff --git a/src/activities/settings/SettingsActivity.cpp b/src/activities/settings/SettingsActivity.cpp index ddfd2d7..3ae4d8f 100644 --- a/src/activities/settings/SettingsActivity.cpp +++ b/src/activities/settings/SettingsActivity.cpp @@ -19,6 +19,7 @@ const SettingInfo displaySettings[displaySettingsCount] = { SettingInfo::Enum("Sleep Screen Cover Mode", &CrossPointSettings::sleepScreenCoverMode, {"Fit", "Crop", "Actual"}), SettingInfo::Enum("Sleep Screen Cover Filter", &CrossPointSettings::sleepScreenCoverFilter, {"None", "Contrast", "Inverted"}), + SettingInfo::Toggle("High Contrast", &CrossPointSettings::displayContrast), SettingInfo::Enum("Status Bar", &CrossPointSettings::statusBar, {"None", "No Progress", "Full w/ Percentage", "Full w/ Progress Bar", "Progress Bar"}), SettingInfo::Enum("Hide Battery %", &CrossPointSettings::hideBatteryPercentage, {"Never", "In Reader", "Always"}), diff --git a/src/main.cpp b/src/main.cpp index 8a23725..93f6cf2 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -6,9 +6,12 @@ #include #include #include +#include #include +#include + #include "Battery.h" #include "BookListStore.h" #include "CrossPointSettings.h" @@ -470,6 +473,8 @@ void setup() { } SETTINGS.loadFromFile(); + // Apply high contrast mode from settings + setHighContrastMode(SETTINGS.displayContrast == 1); if (isWakeupByPowerButton()) { // For normal wakeups, verify power button press duration