File indexing completed on 2024-05-19 05:42:06
0001 // ct_lvtgclr_colormanagement.cpp -*-C++-*- 0002 0003 /* 0004 // Copyright 2023 Codethink Ltd <codethink@codethink.co.uk> 0005 // SPDX-License-Identifier: Apache-2.0 0006 // 0007 // Licensed under the Apache License, Version 2.0 (the "License"); 0008 // you may not use this file except in compliance with the License. 0009 // You may obtain a copy of the License at 0010 // 0011 // http://www.apache.org/licenses/LICENSE-2.0 0012 // 0013 // Unless required by applicable law or agreed to in writing, software 0014 // distributed under the License is distributed on an "AS IS" BASIS, 0015 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 0016 // See the License for the specific language governing permissions and 0017 // limitations under the License. 0018 */ 0019 0020 #include <ct_lvtclr_colormanagement.h> 0021 0022 #include <QCryptographicHash> 0023 0024 #include <QDebug> 0025 #include <preferences.h> 0026 0027 #include <unordered_map> 0028 #include <vector> 0029 0030 // clazy:excludeall=qcolor-from-literal 0031 0032 namespace Codethink::lvtclr { 0033 0034 struct ColorManagement::Private { 0035 QColor baseColor; 0036 std::unordered_map<std::string, QColor> colorMap; 0037 std::unordered_map<std::string, QColor> userDefinedColorMap; 0038 0039 std::unordered_map<std::string, Qt::BrushStyle> fillMap; 0040 0041 /* 'v used the data here: http://mkweb.bcgsc.ca/colorblind/ and did 0042 * a few iterations of software that 0043 * can simulate color blindness over kwin. 0044 * - https://www.qt.io/blog/2017/09/15/testing-applications-color-blindness 0045 */ 0046 const std::vector<QColor> colorBlindPalette = { 0047 "#E8384F", // Red 0048 "#FD817D", // orange 0049 "#FDAE33", // yellow-orange 0050 "#EECC16", // yellow 0051 "#A4C61A", // yellow-green 0052 "#62BB35", // "green" 0053 "#37A862", // "blue-green" 0054 "#8D9F9B", // "cool-gray" 0055 "#208EA3", // "aqua" 0056 "#4178BC", // "blue" 0057 "#7A71F6", // "indigo" 0058 "#AA71FF", // "purple" 0059 "#E37CFF", // "magenta" 0060 "#EA4E90", // "hot-pink" 0061 "#FCA7E4" // "pink" 0062 }; 0063 0064 const std::vector<Qt::BrushStyle> brushPatterns = {Qt::Dense1Pattern, 0065 Qt::Dense2Pattern, 0066 Qt::Dense3Pattern, 0067 Qt::Dense4Pattern, 0068 Qt::Dense5Pattern, 0069 Qt::Dense6Pattern, 0070 Qt::Dense7Pattern, 0071 Qt::HorPattern, 0072 Qt::VerPattern, 0073 Qt::CrossPattern, 0074 Qt::BDiagPattern, 0075 Qt::FDiagPattern, 0076 Qt::DiagCrossPattern}; 0077 }; 0078 0079 ColorManagement::ColorManagement(bool doConnectSignals): d(std::make_unique<ColorManagement::Private>()) 0080 { 0081 // Start the ColorManagement with a white base color. 0082 d->baseColor = QColor(255, 255, 255); 0083 0084 if (doConnectSignals) { 0085 connect(Preferences::self(), &Preferences::useColorBlindFillChanged, this, &ColorManagement::colorBlindChanged); 0086 connect(Preferences::self(), &Preferences::colorBlindModeChanged, this, &ColorManagement::colorBlindChanged); 0087 } 0088 } 0089 0090 ColorManagement::~ColorManagement() = default; 0091 0092 void ColorManagement::setBaseColor(const QColor& color) 0093 { 0094 d->baseColor = color; 0095 resetCaches(); 0096 } 0097 0098 void ColorManagement::colorBlindChanged() 0099 { 0100 resetCaches(); 0101 Q_EMIT requestNewColor(); 0102 } 0103 0104 Qt::BrushStyle ColorManagement::fillPattern(const std::string& id) 0105 { 0106 if (!isUsingColorBlindFill()) { 0107 return Qt::BrushStyle::SolidPattern; 0108 } 0109 0110 auto it = d->fillMap.find(id); 0111 if (it != d->fillMap.end()) { 0112 return it->second; 0113 } 0114 0115 static size_t currentPattern = 0; 0116 Qt::BrushStyle retValue = d->brushPatterns[currentPattern]; 0117 currentPattern = (currentPattern + 1) % d->brushPatterns.size(); 0118 d->fillMap[id] = retValue; 0119 0120 return retValue; 0121 } 0122 0123 QColor ColorManagement::getColorFor(const std::string& id) 0124 { 0125 // User defined colors are always preferred 0126 if (d->userDefinedColorMap.find(id) != d->userDefinedColorMap.end()) { 0127 return d->userDefinedColorMap[id]; 0128 } 0129 0130 // If used doesn't have any preferences, generate 0131 if (d->colorMap.find(id) == d->colorMap.end()) { 0132 // Could not find cached color. Calculate a new one. 0133 if (isColorBlindModeActive()) { 0134 d->colorMap[id] = generateColorblindColor(); 0135 } else { 0136 d->colorMap[id] = generateUniqueColor(id); 0137 } 0138 } 0139 return d->colorMap[id]; 0140 } 0141 0142 void ColorManagement::setColorFor(const std::string& id, const QColor& color) 0143 { 0144 d->userDefinedColorMap[id] = color; 0145 } 0146 0147 QColor ColorManagement::generateUniqueColor(const std::string& id) const 0148 { 0149 // given the fact that a single byte will change completely 0150 // the results of a md5 hash, we can use that to use the first 0151 // three letters as unsigned chars (so, range 0 - 255) to get the 0152 // color value for red, green and blue. This removes the randomness 0153 // of the colors on the view. 0154 auto md5Algorithm = QCryptographicHash(QCryptographicHash::Algorithm::Md5); 0155 md5Algorithm.addData(QString::fromStdString(id + "salt").toLocal8Bit()); 0156 auto md5 = md5Algorithm.result().toStdString(); 0157 0158 int red = (unsigned char) md5[0]; 0159 int green = (unsigned char) md5[1]; 0160 int blue = (unsigned char) md5[2]; 0161 0162 red = (red + d->baseColor.red()) / 2; 0163 green = (green + d->baseColor.green()) / 2; 0164 blue = (blue + d->baseColor.blue()) / 2; 0165 0166 return {red, green, blue}; 0167 } 0168 0169 QColor ColorManagement::generateColorblindColor() const 0170 { 0171 int idx = 0; 0172 bool found = false; 0173 bool brigthen = false; 0174 bool darken = false; 0175 0176 QColor currColor; 0177 do { 0178 currColor = d->colorBlindPalette[idx]; 0179 if (brigthen) { 0180 currColor = currColor.lighter(); 0181 } else if (darken) { 0182 currColor = currColor.darker(); 0183 } 0184 0185 found = std::find_if(std::begin(d->colorMap), 0186 std::end(d->colorMap), 0187 [currColor](const std::pair<std::string, QColor>& color) { 0188 return color.second == currColor; 0189 }) 0190 != std::end(d->colorMap); 0191 0192 idx += 1; 0193 if (idx == (int) d->colorBlindPalette.size()) { 0194 idx = 0; 0195 // too many colors. return the currently tested color. we tested 0196 // more than 45 colors here, it's unlikely that the amount of classes will 0197 // trigger this. 0198 if (brigthen && darken) { 0199 break; 0200 } 0201 if (!darken) { 0202 darken = true; 0203 continue; 0204 } 0205 if (!brigthen) { 0206 brigthen = true; 0207 continue; 0208 } 0209 } 0210 } while (found); 0211 0212 return currColor; 0213 } 0214 0215 void ColorManagement::resetCaches() 0216 { 0217 d->colorMap.clear(); 0218 d->fillMap.clear(); 0219 } 0220 0221 bool ColorManagement::isColorBlindModeActive() const 0222 { 0223 return Preferences::colorBlindMode(); 0224 } 0225 0226 bool Codethink::lvtclr::ColorManagement::isUsingColorBlindFill() const 0227 { 0228 return Preferences::useColorBlindFill(); 0229 } 0230 0231 } // namespace Codethink::lvtclr