Modified the code that generates user's colors so that it will

work regardless of the theme choices the user makes.  The code
now incorporates the contrast between the background color and the
color generated by the user_name when picking colors.  It currently
has two 'big' issues:
1. Colors are not cached.  I am planning on adding a QHash for this
a little later.  This should improve performance by not calculating
the color for the same users over and over and over again.
2. Theme changes do not trigger the colors to get refreshed.
Currently, you will have to switch to a different room and back
to get the colors to refresh.
This commit is contained in:
redsky17 2019-01-18 17:17:25 +00:00
parent 1882198e4b
commit 50e382f554
7 changed files with 152 additions and 10 deletions

View File

@ -3,6 +3,10 @@ QLabel {
color: #caccd1;
}
TimelineItem {
qproperty-backgroundColor: #202228;
}
#chatPage,
#chatPage > * {
background-color: #202228;

View File

@ -3,6 +3,10 @@ QLabel {
color: #333;
}
TimelineItem {
qproperty-backgroundColor: white;
}
#chatPage,
#chatPage > * {
background-color: white;

View File

@ -3,6 +3,10 @@ TypingDisplay {
qproperty-backgroundColor: palette(window);
}
TimelineItem {
qproperty-backgroundColor: palette(window);
}
TimelineView,
TimelineView > * {
border: none;

View File

@ -383,20 +383,120 @@ utils::linkColor()
}
QString
utils::generateHexColor(const QString &input)
utils::generateHexColor(const int hash)
{
QString colour("#");
for (int i = 0; i < 3; i++) {
int value = (hash >> (i * 8)) & 0xFF;
colour.append(("00" + QString::number(value, 16)).right(2));
}
// nhlog::ui()->debug("Hex Generated {} -> {}", QString::number(hash).toStdString(),
// colour.toStdString());
return colour.toUpper();
}
int
utils::hashQString(const QString &input)
{
auto hash = 0;
for (int i = 0; i < input.length(); i++) {
hash = input.at(i).digitValue() + ((hash << 5) - hash);
}
hash *= 13;
QString colour("#");
for (int i = 0; i < 3; i++) {
int value = (hash >> (i * 8)) & 0xFF;
colour.append(("00" + QString::number(value, 16)).right(2));
return hash;
}
QString
utils::generateContrastingHexColor(const QString &input, const QString &background)
{
nhlog::ui()->debug("Background hex {}", background.toStdString());
const QColor backgroundCol(background);
const qreal backgroundLum = luminance(background);
// Create a color for the input
auto hash = hashQString(input);
auto colorHex = generateHexColor(hash);
// converting to a QColor makes the luminance calc easier.
QColor inputColor = QColor(colorHex);
// attempt to score both the luminance and the contrast.
// contrast should have a higher precedence, but luminance
// helps dictate how exciting the colors are.
auto colorLum = luminance(inputColor);
auto contrast = computeContrast(colorLum, backgroundLum);
// If the contrast or luminance don't meet our criteria,
// try again and again until they do. After 10 tries,
// the best-scoring color will be chosen.
int att = 0;
while ((contrast < 5 || (colorLum < 0.05 || colorLum > 0.95)) && ++att < 10) {
hash = hashQString(input) + ((hash << 2) * 13);
auto newHex = generateHexColor(hash);
inputColor.setNamedColor(newHex);
auto tmpLum = luminance(inputColor);
auto tmpContrast = computeContrast(tmpLum, backgroundLum);
// Prioritize contrast over luminance
// If both values are better, it's a no brainer.
if (tmpContrast > contrast && (tmpLum > 0.05 && tmpLum < 0.95)) {
contrast = tmpContrast;
colorHex = newHex;
colorLum = tmpLum;
}
// Otherwise, if we still can get a more
// vibrant color and have met our contrast
// threshold, pick the more vibrant color,
// even if contrast will drop somewhat.
// choosing 50% luminance as ideal.
else if ((qAbs(tmpLum - 0.50) < qAbs(colorLum - 0.50)) && tmpContrast >= 5) {
contrast = tmpContrast;
colorHex = newHex;
colorLum = tmpLum;
}
// Otherwise, just take the better contrast.
else if (tmpContrast > contrast) {
contrast = tmpContrast;
colorHex = newHex;
colorLum = tmpLum;
}
}
return colour;
nhlog::ui()->debug("Hex Generated for {}: [hex: {}, contrast: {}, luminance: {}]",
input.toStdString(),
colorHex.toStdString(),
QString::number(contrast).toStdString(),
QString::number(colorLum).toStdString());
return colorHex;
}
qreal
utils::computeContrast(const qreal &one, const qreal &two)
{
auto ratio = (one + 0.05) / (two + 0.05);
if (two > one) {
ratio = 1 / ratio;
}
return ratio;
}
qreal
utils::luminance(const QColor &col)
{
int colRgb[3] = {col.red(), col.green(), col.blue()};
qreal lumRgb[3];
for (int i = 0; i < 3; i++) {
qreal v = colRgb[i] / 255.0;
v <= 0.03928 ? lumRgb[i] = v / 12.92 : lumRgb[i] = qPow((v + 0.055) / 1.055, 2.4);
}
return lumRgb[0] * 0.2126 + lumRgb[1] * 0.7152 + lumRgb[2] * 0.0722;
}
void

View File

@ -14,6 +14,8 @@
#include <mtx/events/collections.hpp>
#include <mtx/events/common.hpp>
#include <qmath.h>
class QComboBox;
namespace utils {
@ -227,9 +229,26 @@ markdownToHtml(const QString &text);
QString
linkColor();
//! Given an input string, create a color string
//! Given an input integer, create a color string in #RRGGBB format
QString
generateHexColor(const QString &string);
generateHexColor(const int hash);
//! Returns the hash code of the input QString
int
hashQString(const QString &input);
//! Generate a color (matching #RRGGBB) that has an acceptable contrast to background that is based
//! on the input string.
QString
generateContrastingHexColor(const QString &input, const QString &background);
//! Given two luminance values, compute the contrast ratio between them.
qreal
computeContrast(const qreal &one, const qreal &two);
//! Compute the luminance of a single color. Based on https://stackoverflow.com/a/9733420
qreal
luminance(const QColor &col);
//! Center a widget in relation to another widget.
void

View File

@ -622,8 +622,6 @@ TimelineItem::generateUserName(const QString &user_id, const QString &displaynam
sender = displayname.split(":")[0].split("@")[1];
}
auto userColor = utils::generateHexColor(user_id);
QFont usernameFont;
usernameFont.setPointSizeF(usernameFont.pointSizeF() * 1.1);
usernameFont.setWeight(QFont::Medium);
@ -639,6 +637,12 @@ TimelineItem::generateUserName(const QString &user_id, const QString &displaynam
userName_->setAlignment(Qt::AlignLeft | Qt::AlignTop);
userName_->setFixedWidth(QFontMetrics(userName_->font()).width(userName_->text()));
// TimelineItem isn't displayed. This forces the QSS to get
// loaded.
qApp->style()->polish(this);
// generate user's unique color.
auto backCol = backgroundColor().name();
auto userColor = utils::generateContrastingHexColor(user_id, backCol);
userName_->setStyleSheet("QLabel { color : " + userColor + "; }");
auto filter = new UserProfileFilter(user_id, userName_);

View File

@ -132,6 +132,8 @@ private:
class TimelineItem : public QWidget
{
Q_OBJECT
Q_PROPERTY(QColor backgroundColor READ backgroundColor WRITE setBackgroundColor)
public:
TimelineItem(const mtx::events::RoomEvent<mtx::events::msg::Notice> &e,
bool with_sender,
@ -202,6 +204,9 @@ public:
const QString &room_id,
QWidget *parent);
void setBackgroundColor(const QColor &color) { backgroundColor_ = color; }
QColor backgroundColor() const { return backgroundColor_; }
void setUserAvatar(const QImage &pixmap);
DescInfo descriptionMessage() const { return descriptionMsg_; }
QString eventId() const { return event_id_; }
@ -282,6 +287,8 @@ private:
QLabel *timestamp_;
QLabel *userName_;
TextLabel *body_;
QColor backgroundColor_;
};
template<class Widget>