From e481a03053f9ccd70f4dff881f3345e68e788daa Mon Sep 17 00:00:00 2001 From: Dan Keenan Date: Sat, 28 Feb 2026 15:05:56 -0500 Subject: [PATCH 1/5] Calculate contrast rations. --- CMakeLists.txt | 2 ++ src/color_helpers.cpp | 17 +++++++++++++++ src/color_helpers.h | 51 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 70 insertions(+) create mode 100644 src/color_helpers.cpp create mode 100644 src/color_helpers.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 5cdaaff..14b06e5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -101,10 +101,12 @@ set(SACNVIEW_HEADERS src/widgets/checkableheader.h src/models/sacnlistenermodel.h src/delegates/resettablecounterdelegate.h + src/color_helpers.h ) set(SACNVIEW_SOURCES sacnview.natvis + src/color_helpers.cpp src/commandline.cpp src/firewallcheck.cpp src/ipc.cpp diff --git a/src/color_helpers.cpp b/src/color_helpers.cpp new file mode 100644 index 0000000..a4d4af3 --- /dev/null +++ b/src/color_helpers.cpp @@ -0,0 +1,17 @@ +#include "color_helpers.h" + +double calcLuminance(const QColor & color) +{ + // https://w3c.github.io/wcag/guidelines/22/#dfn-relative-luminance + return 0.2126 * color.redF() + 0.7152 * color.greenF() + 0.0722 * color.blueF(); +} + +double calcContrastRatio(const QColor & color1, const QColor & color2) +{ + const double luminance1 = calcLuminance(color1); + const double luminance2 = calcLuminance(color2); + const double lighter = std::max(luminance1, luminance2); + const double darker = std::min(luminance1, luminance2); + // https://w3c.github.io/wcag/guidelines/22/#dfn-contrast-ratio + return (lighter + 0.05) / (darker + 0.05); +} diff --git a/src/color_helpers.h b/src/color_helpers.h new file mode 100644 index 0000000..ac0cbdd --- /dev/null +++ b/src/color_helpers.h @@ -0,0 +1,51 @@ +#ifndef SRC_COLOR_HELPERS_H +#define SRC_COLOR_HELPERS_H + +#include + +/** + * Calculate luminance in the sRGB color space. + * + * @see https://w3c.github.io/wcag/guidelines/22/#dfn-relative-luminance + */ +double calcLuminance(const QColor & color); + +/** + * Calculate contrast ratio between two colors. + * + * @see https://w3c.github.io/wcag/guidelines/22/#dfn-contrast-ratio + */ +double calcContrastRatio(const QColor & color1, const QColor & color2); + +/** + * Find the color in the iterator @p begin .. @p end that contrasts best with @p bgColor. + * + * @param bgColor Background color + * @param begin Start QColor iterator of possible foreground colors. + * @param end End QColor iterator. + */ +template +QColor findBestContrastingColor(const QColor & bgColor, FgColorIt begin, FgColorIt end) +{ + using ColorContrastRatio = std::tuple; + std::vector contrastRatios; + + // Calculate ratios. + for (FgColorIt it = begin; it != end; ++it) + { + contrastRatios.emplace_back(*it, calcContrastRatio(bgColor, *it)); + } + + // Find the highest contrast ratio. + ColorContrastRatio best = contrastRatios.front(); + for (const auto & contrastRatio : contrastRatios) + { + if (std::get<1>(best) < std::get<1>(contrastRatio)) + { + best = contrastRatio; + } + } + return std::get<0>(best); +} + +#endif //SRC_COLOR_HELPERS_H From 3e5ad2ac728e6036889811c15349c3ecf85493ee Mon Sep 17 00:00:00 2001 From: Dan Keenan Date: Sat, 28 Feb 2026 15:17:18 -0500 Subject: [PATCH 2/5] Change text color in grid view. --- src/widgets/gridwidget.cpp | 27 +++++++++++++++++++++++---- src/widgets/gridwidget.h | 4 +++- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/src/widgets/gridwidget.cpp b/src/widgets/gridwidget.cpp index 3a7aa00..4be42ec 100644 --- a/src/widgets/gridwidget.cpp +++ b/src/widgets/gridwidget.cpp @@ -14,6 +14,8 @@ // limitations under the License. #include "gridwidget.h" +#include "color_helpers.h" + #include #include #include @@ -29,7 +31,11 @@ #define CELL_COUNT 512 GridWidget::GridWidget(QWidget * parent) - : QWidget(parent), m_colors(CELL_COUNT, this->palette().color(QPalette::Base)), m_cellHeight(CELL_HEIGHT) + : QWidget(parent) + , m_bgColors(CELL_COUNT, this->palette().color(QPalette::Base)) + , m_fgColors(CELL_COUNT, this->palette().color(QPalette::Text)) + , m_possibleFgColors({this->palette().color(QPalette::Text), this->palette().color(QPalette::BrightText)}) + , m_cellHeight(CELL_HEIGHT) { for (int i = 0; i < CELL_COUNT; i++) { @@ -117,10 +123,11 @@ void GridWidget::paintEvent(QPaintEvent * event) if (!value.isEmpty()) { - QColor fillColor = m_colors[address]; + QColor fillColor = m_bgColors[address]; QString rowLabel = value; painter.fillRect(textRect, fillColor); + painter.setPen(m_fgColors[address]); painter.drawText(textRect, Qt::AlignHCenter | Qt::AlignVCenter, rowLabel); } } @@ -333,17 +340,29 @@ void GridWidget::mouseDoubleClickEvent(QMouseEvent * event) void GridWidget::setAllCellColor(const QColor & color) { if (color.isValid()) - m_colors.fill(color); + { + m_bgColors.fill(color); + const QColor fgColor = findBestContrastingColor(color, m_possibleFgColors.cbegin(), m_possibleFgColors.cend()); + m_fgColors.fill(fgColor); + } else + { setAllCellColor(palette().color(QPalette::Base)); + } } void GridWidget::setCellColor(int cell, const QColor & color) { if (color.isValid()) - m_colors[cell] = color; + { + m_bgColors[cell] = color; + const QColor fgColor = findBestContrastingColor(color, m_possibleFgColors.cbegin(), m_possibleFgColors.cend()); + m_fgColors[cell] = fgColor; + } else + { setCellColor(cell, palette().color(QPalette::Base)); + } } void GridWidget::setCellValue(int cell, const QString & value) diff --git a/src/widgets/gridwidget.h b/src/widgets/gridwidget.h index 9a54a74..fd7d971 100644 --- a/src/widgets/gridwidget.h +++ b/src/widgets/gridwidget.h @@ -76,7 +76,9 @@ class GridWidget : public QWidget private: QList m_selectedAddresses; - QVector m_colors; + QVector m_bgColors; + QVector m_fgColors; + std::array m_possibleFgColors; QStringList m_values; bool m_allowMultiSelect = false; QPoint m_lastClickPoint; From a61b40d4bad20aa1b436cc2196df70c68e9f01d2 Mon Sep 17 00:00:00 2001 From: Dan Keenan Date: Sat, 28 Feb 2026 15:39:39 -0500 Subject: [PATCH 3/5] Change text color in source list. --- src/models/sacnsourcetablemodel.cpp | 34 ++++++++++++++++++++++++++++- src/models/sacnsourcetablemodel.h | 3 +++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/src/models/sacnsourcetablemodel.cpp b/src/models/sacnsourcetablemodel.cpp index 0ec7817..b87cfd9 100644 --- a/src/models/sacnsourcetablemodel.cpp +++ b/src/models/sacnsourcetablemodel.cpp @@ -14,13 +14,26 @@ #include "sacnsourcetablemodel.h" +#include + #include "preferences.h" #include "sacn/sacnlistener.h" +#include "color_helpers.h" + +std::array SACNSourceTableModel::POSSIBLE_FG_COLORS{}; + SACNSourceTableModel::SACNSourceTableModel(QObject * parent) : QAbstractTableModel(parent) -{} +{ + // Init possible foreground colors from the application palette. + if (!POSSIBLE_FG_COLORS.front().isValid()) + { + const QPalette palette = qApp->palette(); + POSSIBLE_FG_COLORS = {palette.color(QPalette::Text), palette.color(QPalette::BrightText)}; + } +} SACNSourceTableModel::~SACNSourceTableModel() {} @@ -68,6 +81,8 @@ QVariant SACNSourceTableModel::data(const QModelIndex & index, int role) const case Qt::BackgroundRole: return getBackgroundData(rowData, index.column()); + case Qt::ForegroundRole: return getForegroundData(rowData, index.column()); + case Qt::TextAlignmentRole: switch (index.column()) { @@ -218,6 +233,23 @@ QVariant SACNSourceTableModel::getBackgroundData(const RowData & rowData, int co return QVariant(); } +QVariant SACNSourceTableModel::getForegroundData(const RowData & rowData, int column) const +{ + const QVariant bgData = getBackgroundData(rowData, column); + if (!bgData.isValid()) + { + return QVariant(); + } + + const QColor bgColor = bgData.value(); + if (bgColor.isValid()) + { + return findBestContrastingColor(bgColor, POSSIBLE_FG_COLORS.cbegin(), POSSIBLE_FG_COLORS.cend()); + } + + return QVariant(); +} + QVariant SACNSourceTableModel::getTimingSummary(const RowData & rowData, int column) const { const FpsCounter::Histogram & histogram = rowData.histogram; diff --git a/src/models/sacnsourcetablemodel.h b/src/models/sacnsourcetablemodel.h index 8e5d596..82022fb 100644 --- a/src/models/sacnsourcetablemodel.h +++ b/src/models/sacnsourcetablemodel.h @@ -161,6 +161,8 @@ private Q_SLOTS: void Update(const sACNSource * source); }; + static std::array POSSIBLE_FG_COLORS; + std::vector m_rows; std::vector m_listeners; // Notes by CID as provided by SACNView user @@ -176,6 +178,7 @@ private Q_SLOTS: // Data QVariant getDisplayData(const RowData & rowData, int column) const; QVariant getBackgroundData(const RowData & rowData, int column) const; + QVariant getForegroundData(const RowData & rowData, int column) const; QVariant getTimingSummary(const RowData & rowData, int column) const; void RefreshAllTimingData(); From cc36ed88a6c41747e06d70abe76f2bcb712a1fff Mon Sep 17 00:00:00 2001 From: Dan Keenan Date: Sat, 28 Feb 2026 15:56:09 -0500 Subject: [PATCH 4/5] Change text color in scope view. --- src/color_helpers.cpp | 8 ++++++++ src/color_helpers.h | 6 ++++++ src/models/sacnsourcetablemodel.cpp | 10 +--------- src/models/sacnsourcetablemodel.h | 2 -- src/widgets/glscopewidget.cpp | 4 ++++ src/widgets/glscopewidget.h | 2 +- 6 files changed, 20 insertions(+), 12 deletions(-) diff --git a/src/color_helpers.cpp b/src/color_helpers.cpp index a4d4af3..e7932f3 100644 --- a/src/color_helpers.cpp +++ b/src/color_helpers.cpp @@ -1,4 +1,6 @@ #include "color_helpers.h" +#include +#include double calcLuminance(const QColor & color) { @@ -15,3 +17,9 @@ double calcContrastRatio(const QColor & color1, const QColor & color2) // https://w3c.github.io/wcag/guidelines/22/#dfn-contrast-ratio return (lighter + 0.05) / (darker + 0.05); } +QColor findBestContrastingColor(const QColor & bgColor) +{ + const QPalette palette = qApp->palette(); + const std::array fgColors = {palette.color(QPalette::Text), palette.color(QPalette::BrightText)}; + return findBestContrastingColor(bgColor, fgColors.cbegin(), fgColors.cend()); +} diff --git a/src/color_helpers.h b/src/color_helpers.h index ac0cbdd..c896e5c 100644 --- a/src/color_helpers.h +++ b/src/color_helpers.h @@ -48,4 +48,10 @@ QColor findBestContrastingColor(const QColor & bgColor, FgColorIt begin, FgColor return std::get<0>(best); } +/** + * Find the which of the application palette's text colors best contrasts with @p bgColor. + * @param bgColor Background color + */ +QColor findBestContrastingColor(const QColor & bgColor); + #endif //SRC_COLOR_HELPERS_H diff --git a/src/models/sacnsourcetablemodel.cpp b/src/models/sacnsourcetablemodel.cpp index b87cfd9..eeea8a0 100644 --- a/src/models/sacnsourcetablemodel.cpp +++ b/src/models/sacnsourcetablemodel.cpp @@ -22,17 +22,9 @@ #include "color_helpers.h" -std::array SACNSourceTableModel::POSSIBLE_FG_COLORS{}; - SACNSourceTableModel::SACNSourceTableModel(QObject * parent) : QAbstractTableModel(parent) { - // Init possible foreground colors from the application palette. - if (!POSSIBLE_FG_COLORS.front().isValid()) - { - const QPalette palette = qApp->palette(); - POSSIBLE_FG_COLORS = {palette.color(QPalette::Text), palette.color(QPalette::BrightText)}; - } } SACNSourceTableModel::~SACNSourceTableModel() {} @@ -244,7 +236,7 @@ QVariant SACNSourceTableModel::getForegroundData(const RowData & rowData, int co const QColor bgColor = bgData.value(); if (bgColor.isValid()) { - return findBestContrastingColor(bgColor, POSSIBLE_FG_COLORS.cbegin(), POSSIBLE_FG_COLORS.cend()); + return findBestContrastingColor(bgColor); } return QVariant(); diff --git a/src/models/sacnsourcetablemodel.h b/src/models/sacnsourcetablemodel.h index 82022fb..459417f 100644 --- a/src/models/sacnsourcetablemodel.h +++ b/src/models/sacnsourcetablemodel.h @@ -161,8 +161,6 @@ private Q_SLOTS: void Update(const sACNSource * source); }; - static std::array POSSIBLE_FG_COLORS; - std::vector m_rows; std::vector m_listeners; // Notes by CID as provided by SACNView user diff --git a/src/widgets/glscopewidget.cpp b/src/widgets/glscopewidget.cpp index 2e81ad5..9cdb8b8 100644 --- a/src/widgets/glscopewidget.cpp +++ b/src/widgets/glscopewidget.cpp @@ -22,6 +22,8 @@ #include +#include "color_helpers.h" + static constexpr qreal AXIS_LABEL_WIDTH = 45.0; static constexpr qreal AXIS_LABEL_HEIGHT = 20.0; static constexpr qreal TOP_GAP = 10.0; @@ -716,6 +718,8 @@ QVariant ScopeModel::data(const QModelIndex & index, int role) const case COL_COLOUR: if (role == Qt::BackgroundRole || role == Qt::DisplayRole || role == Qt::EditRole) return trace->color(); + if (role == Qt::ForegroundRole) + return findBestContrastingColor(trace->color()); if (role == DataSortRole) return static_cast(trace->color().rgba()); if (role == Qt::ToolTipRole) return tr("Trace color (#RRGGBB)"); break; diff --git a/src/widgets/glscopewidget.h b/src/widgets/glscopewidget.h index f1995ee..e1e3a82 100644 --- a/src/widgets/glscopewidget.h +++ b/src/widgets/glscopewidget.h @@ -684,4 +684,4 @@ class GlScopeWidget : public QOpenGLWidget, protected QOpenGLFunctions std::vector makeTriggerLine(ScopeModel::Trigger type); void updateCursor(const QPoint & widgetPos); -}; \ No newline at end of file +}; From 0ecb5a8ee98caf9a630d0032aea578c4e2938ebb Mon Sep 17 00:00:00 2001 From: Dan Keenan Date: Sat, 28 Feb 2026 16:08:23 -0500 Subject: [PATCH 5/5] Cleanup. --- src/color_helpers.cpp | 1 + src/color_helpers.h | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/color_helpers.cpp b/src/color_helpers.cpp index e7932f3..b0ae32f 100644 --- a/src/color_helpers.cpp +++ b/src/color_helpers.cpp @@ -17,6 +17,7 @@ double calcContrastRatio(const QColor & color1, const QColor & color2) // https://w3c.github.io/wcag/guidelines/22/#dfn-contrast-ratio return (lighter + 0.05) / (darker + 0.05); } + QColor findBestContrastingColor(const QColor & bgColor) { const QPalette palette = qApp->palette(); diff --git a/src/color_helpers.h b/src/color_helpers.h index c896e5c..90f7a4c 100644 --- a/src/color_helpers.h +++ b/src/color_helpers.h @@ -1,5 +1,5 @@ -#ifndef SRC_COLOR_HELPERS_H -#define SRC_COLOR_HELPERS_H +#ifndef COLOR_HELPERS_H +#define COLOR_HELPERS_H #include @@ -54,4 +54,4 @@ QColor findBestContrastingColor(const QColor & bgColor, FgColorIt begin, FgColor */ QColor findBestContrastingColor(const QColor & bgColor); -#endif //SRC_COLOR_HELPERS_H +#endif //COLOR_HELPERS_H