Changeset View
Changeset View
Standalone View
Standalone View
src/ColorScheme.cpp
Show All 15 Lines | 1 | /* | |||
---|---|---|---|---|---|
16 | You should have received a copy of the GNU General Public License | 16 | You should have received a copy of the GNU General Public License | ||
17 | along with this program; if not, write to the Free Software | 17 | along with this program; if not, write to the Free Software | ||
18 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | 18 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | ||
19 | 02110-1301 USA. | 19 | 02110-1301 USA. | ||
20 | */ | 20 | */ | ||
21 | 21 | | |||
22 | // Own | 22 | // Own | ||
23 | #include "ColorScheme.h" | 23 | #include "ColorScheme.h" | ||
24 | #include "hsluv.h" | ||||
24 | 25 | | |||
25 | // Qt | 26 | // Qt | ||
26 | #include <QPainter> | 27 | #include <QPainter> | ||
28 | #include <QVariant> | ||||
27 | 29 | | |||
28 | // KDE | 30 | // KDE | ||
29 | #include <KConfig> | 31 | #include <KConfig> | ||
30 | #include <KLocalizedString> | 32 | #include <KLocalizedString> | ||
31 | #include <KConfigGroup> | 33 | #include <KConfigGroup> | ||
32 | 34 | | |||
33 | // STL | 35 | // STL | ||
34 | #include <random> | 36 | #include <random> | ||
35 | 37 | | |||
36 | #include "konsoledebug.h" | 38 | #include "konsoledebug.h" | ||
37 | 39 | | |||
38 | namespace { | 40 | namespace { | ||
39 | const int FGCOLOR_INDEX = 0; | 41 | static const int FGCOLOR_INDEX = 0; | ||
40 | const int BGCOLOR_INDEX = 1; | 42 | static const int BGCOLOR_INDEX = 1; | ||
43 | | ||||
44 | static const char RandomHueRangeKey[] = "RandomHueRange"; | ||||
45 | static const char RandomSaturationRangeKey[] = "RandomSaturationRange"; | ||||
46 | static const char RandomLightnessRangeKey[] = "RandomLightnessRange"; | ||||
47 | | ||||
48 | static const double MaxHue = 360.0; | ||||
49 | static const double MaxSaturation = 100.0; | ||||
50 | static const double MaxLightness = 100.0; | ||||
hindenburg: nitpick - you don't need to fix these now - "static definition in anonymous namespace; static… | |||||
41 | } | 51 | } | ||
42 | 52 | | |||
43 | using namespace Konsole; | 53 | using namespace Konsole; | ||
44 | 54 | | |||
45 | // The following are almost IBM standard color codes, with some slight | 55 | // The following are almost IBM standard color codes, with some slight | ||
46 | // gamma correction for the dim colors to compensate for bright X screens. | 56 | // gamma correction for the dim colors to compensate for bright X screens. | ||
47 | // It contains the 8 ansiterm/xterm colors in 2 intensities. | 57 | // It contains the 8 ansiterm/xterm colors in 2 intensities. | ||
48 | const ColorEntry ColorScheme::defaultTable[TABLE_COLORS] = { | 58 | const ColorEntry ColorScheme::defaultTable[TABLE_COLORS] = { | ||
▲ Show 20 Lines • Show All 139 Lines • ▼ Show 20 Line(s) | 197 | if (other._table != nullptr) { | |||
188 | for (int i = 0; i < TABLE_COLORS; i++) { | 198 | for (int i = 0; i < TABLE_COLORS; i++) { | ||
189 | setColorTableEntry(i, other._table[i]); | 199 | setColorTableEntry(i, other._table[i]); | ||
190 | } | 200 | } | ||
191 | } | 201 | } | ||
192 | 202 | | |||
193 | if (other._randomTable != nullptr) { | 203 | if (other._randomTable != nullptr) { | ||
194 | for (int i = 0; i < TABLE_COLORS; i++) { | 204 | for (int i = 0; i < TABLE_COLORS; i++) { | ||
195 | const RandomizationRange &range = other._randomTable[i]; | 205 | const RandomizationRange &range = other._randomTable[i]; | ||
196 | setRandomizationRange(i, range.hue, range.saturation, range.value); | 206 | setRandomizationRange(i, range.hue, range.saturation, range.lightness); | ||
197 | } | 207 | } | ||
198 | } | 208 | } | ||
199 | } | 209 | } | ||
200 | 210 | | |||
201 | ColorScheme::~ColorScheme() | 211 | ColorScheme::~ColorScheme() | ||
202 | { | 212 | { | ||
203 | delete[] _table; | 213 | delete[] _table; | ||
204 | delete[] _randomTable; | 214 | delete[] _randomTable; | ||
▲ Show 20 Lines • Show All 44 Lines • ▼ Show 20 Line(s) | 258 | { | |||
249 | Q_ASSERT(index >= 0 && index < TABLE_COLORS); | 259 | Q_ASSERT(index >= 0 && index < TABLE_COLORS); | ||
250 | 260 | | |||
251 | ColorEntry entry = colorTable()[index]; | 261 | ColorEntry entry = colorTable()[index]; | ||
252 | 262 | | |||
253 | if (randomSeed == 0 || _randomTable == nullptr || _randomTable[index].isNull()) { | 263 | if (randomSeed == 0 || _randomTable == nullptr || _randomTable[index].isNull()) { | ||
254 | return entry; | 264 | return entry; | ||
255 | } | 265 | } | ||
256 | 266 | | |||
267 | double baseHue, baseSaturation, baseLightness; | ||||
268 | rgb2hsluv(entry.redF(), entry.greenF(), entry.blueF(), | ||||
269 | &baseHue, &baseSaturation, &baseLightness); | ||||
270 | | ||||
257 | const RandomizationRange &range = _randomTable[index]; | 271 | const RandomizationRange &range = _randomTable[index]; | ||
258 | 272 | | |||
259 | // 32-bit Mersenne Twister | 273 | // 32-bit Mersenne Twister | ||
260 | // Can't use default_random_engine, because in GCC this maps to | 274 | // Can't use default_random_engine, because in GCC this maps to | ||
261 | // minstd_rand0 which always gives us 0 on the first number. | 275 | // minstd_rand0 which always gives us 0 on the first number. | ||
262 | std::mt19937 randomEngine(randomSeed); | 276 | std::mt19937 randomEngine(randomSeed); | ||
263 | 277 | | |||
264 | int hueDifference = 0; | 278 | // Use hues located around base color's hue. | ||
265 | if (range.hue != 0u) { | 279 | // H=0 [|= =] H=128 [ =|= ] H=360 [= =|] | ||
266 | std::uniform_int_distribution<int> dist(0, range.hue); | 280 | const double minHue = baseHue - range.hue / 2.0; | ||
267 | hueDifference = dist(randomEngine); | 281 | const double maxHue = baseHue + range.hue / 2.0; | ||
268 | } | 282 | std::uniform_real_distribution<> hueDistribution(minHue, maxHue); | ||
269 | 283 | // Hue value is an angle, it wraps after 360°. Adding MAX_HUE | |||
270 | int saturationDifference = 0; | 284 | // guarantees that the sum is not negative. | ||
271 | if (range.saturation != 0u) { | 285 | const double hue = fmod(MaxHue + hueDistribution(randomEngine), MaxHue); | ||
272 | std::uniform_int_distribution<int> dist(0, range.saturation); | 286 | | ||
273 | saturationDifference = dist(randomEngine) - range.saturation / 2; | 287 | // Saturation is always decreased. With more saturation more | ||
274 | } | 288 | // information about hue is preserved in RGB color space | ||
275 | 289 | // (consider red with S=100 and "red" with S=0 which is gray). | |||
276 | int valueDifference = 0; | 290 | // Additionally, I think it can be easier to imagine more | ||
277 | if (range.value != 0u) { | 291 | // toned color than more vivid one. | ||
278 | std::uniform_int_distribution<int> dist(0, range.value); | 292 | // S=0 [|== ] S=50 [ ==| ] S=100 [ ==|] | ||
279 | valueDifference = dist(randomEngine) - range.value / 2; | 293 | const double minSaturation = qMax(baseSaturation - range.saturation, 0.0); | ||
280 | } | 294 | const double maxSaturation = qMax(range.saturation, baseSaturation); | ||
281 | 295 | // Linear distribution as less saturation means less | |||
282 | QColor &color = entry; | 296 | // distinguishable hues. | ||
283 | 297 | std::piecewise_linear_distribution<> saturationDistribution({minSaturation, maxSaturation}, | |||
284 | int newHue = qAbs((color.hue() + hueDifference) % MAX_HUE); | 298 | [](double v) { return v; }); | ||
285 | int newValue = qMin(qAbs(color.value() + valueDifference), 255); | 299 | const double saturation = qFuzzyCompare(minSaturation, maxSaturation) | ||
286 | int newSaturation = qMin(qAbs(color.saturation() + saturationDifference), 255); | 300 | ? baseSaturation | ||
301 | : saturationDistribution(randomEngine); | ||||
302 | | ||||
303 | // Lightness range has base value at its center. The base | ||||
304 | // value is clamped to prevent the range from shrinking. | ||||
305 | // L=0 [=|= ] L=50 [ =|= ] L=100 [ =|=] | ||||
306 | baseLightness = qBound(range.lightness / 2.0, baseLightness , MaxLightness - range.lightness); | ||||
307 | const double minLightness = qMax(baseLightness - range.lightness / 2.0, 0.0); | ||||
308 | const double maxLightness = qMin(baseLightness + range.lightness / 2.0, MaxLightness); | ||||
309 | std::uniform_real_distribution<> lightnessDistribution(minLightness, maxLightness); | ||||
310 | const double lightness = lightnessDistribution(randomEngine); | ||||
287 | 311 | | |||
288 | color.setHsv(newHue, newSaturation, newValue); | 312 | double red, green, blue; | ||
313 | hsluv2rgb(hue, saturation, lightness, &red, &green, &blue); | ||||
289 | 314 | | |||
290 | return entry; | 315 | qDebug("Color %2d: %6.2f, %6.2f, %6.2f, #%02x%02x%02x", index, hue, saturation, lightness, qRound(red * 255), qRound(green * 255), qRound(blue * 255)); | ||
316 | return QColor(qRound(red * 255), qRound(green * 255), qRound(blue * 255)); | ||||
291 | } | 317 | } | ||
292 | 318 | | |||
293 | void ColorScheme::getColorTable(ColorEntry *table, uint randomSeed) const | 319 | void ColorScheme::getColorTable(ColorEntry *table, uint randomSeed) const | ||
294 | { | 320 | { | ||
295 | for (int i = 0; i < TABLE_COLORS; i++) { | 321 | for (int i = 0; i < TABLE_COLORS; i++) { | ||
296 | table[i] = colorEntry(i, randomSeed); | 322 | table[i] = colorEntry(i, randomSeed); | ||
297 | } | 323 | } | ||
298 | } | 324 | } | ||
299 | 325 | | |||
300 | bool ColorScheme::randomizedBackgroundColor() const | 326 | bool ColorScheme::randomizedBackgroundColor() const | ||
301 | { | 327 | { | ||
302 | return _randomTable == nullptr ? false : !_randomTable[BGCOLOR_INDEX].isNull(); | 328 | return _randomTable == nullptr ? false : !_randomTable[BGCOLOR_INDEX].isNull(); | ||
303 | } | 329 | } | ||
304 | 330 | | |||
305 | void ColorScheme::setRandomizedBackgroundColor(bool randomize) | 331 | void ColorScheme::setRandomizedBackgroundColor(bool randomize) | ||
306 | { | 332 | { | ||
307 | // the hue of the background color is allowed to be randomly | | |||
308 | // adjusted as much as possible. | | |||
309 | // | | |||
310 | // the value and saturation are left alone to maintain read-ability | | |||
311 | // except for dark background schemes which allow a change in the | | |||
312 | // colour value (one less than the dark background threshold) | | |||
313 | if (randomize) { | 333 | if (randomize) { | ||
314 | int maxValue = 0; | 334 | setRandomizationRange(BGCOLOR_INDEX, MaxHue, MaxSaturation, 0.0); | ||
315 | | ||||
316 | if (hasDarkBackground()) { | | |||
317 | maxValue = 126; | | |||
318 | } | | |||
319 | | ||||
320 | setRandomizationRange(BGCOLOR_INDEX, MAX_HUE, 255, maxValue); | | |||
321 | } else { | 335 | } else { | ||
322 | if (_randomTable != nullptr) { | 336 | if (_randomTable != nullptr) { | ||
323 | setRandomizationRange(BGCOLOR_INDEX, 0, 0, 0); | 337 | setRandomizationRange(BGCOLOR_INDEX, 0, 0, 0); | ||
324 | } | 338 | } | ||
325 | } | 339 | } | ||
326 | } | 340 | } | ||
327 | 341 | | |||
328 | void ColorScheme::setRandomizationRange(int index, quint16 hue, quint8 saturation, quint8 value) | 342 | void ColorScheme::setRandomizationRange(int index, double hue, double saturation, double lightness) | ||
329 | { | 343 | { | ||
330 | Q_ASSERT(hue <= MAX_HUE); | 344 | Q_ASSERT(hue <= MaxHue); | ||
331 | Q_ASSERT(index >= 0 && index < TABLE_COLORS); | 345 | Q_ASSERT(index >= 0 && index < TABLE_COLORS); | ||
332 | 346 | | |||
333 | if (_randomTable == nullptr) { | 347 | if (_randomTable == nullptr) { | ||
334 | _randomTable = new RandomizationRange[TABLE_COLORS]; | 348 | _randomTable = new RandomizationRange[TABLE_COLORS]; | ||
335 | } | 349 | } | ||
336 | 350 | | |||
337 | _randomTable[index].hue = hue; | 351 | _randomTable[index].hue = hue; | ||
338 | _randomTable[index].value = value; | | |||
339 | _randomTable[index].saturation = saturation; | 352 | _randomTable[index].saturation = saturation; | ||
353 | _randomTable[index].lightness = lightness; | ||||
340 | } | 354 | } | ||
341 | 355 | | |||
342 | const ColorEntry *ColorScheme::colorTable() const | 356 | const ColorEntry *ColorScheme::colorTable() const | ||
343 | { | 357 | { | ||
344 | if (_table != nullptr) { | 358 | if (_table != nullptr) { | ||
345 | return _table; | 359 | return _table; | ||
346 | } else { | 360 | } else { | ||
347 | return defaultTable; | 361 | return defaultTable; | ||
▲ Show 20 Lines • Show All 65 Lines • ▼ Show 20 Line(s) | 425 | if (!configGroup.hasKey("Color") && _table != nullptr) { | |||
413 | return; | 427 | return; | ||
414 | } | 428 | } | ||
415 | 429 | | |||
416 | ColorEntry entry; | 430 | ColorEntry entry; | ||
417 | 431 | | |||
418 | entry = configGroup.readEntry("Color", QColor()); | 432 | entry = configGroup.readEntry("Color", QColor()); | ||
419 | setColorTableEntry(index, entry); | 433 | setColorTableEntry(index, entry); | ||
420 | 434 | | |||
421 | quint16 hue = static_cast<quint16>(configGroup.readEntry("MaxRandomHue", 0)); | 435 | double hue = configGroup.readEntry(RandomHueRangeKey, 0.0); | ||
422 | const quint8 value = static_cast<quint8>(configGroup.readEntry("MaxRandomValue", 0)); | 436 | double saturation = configGroup.readEntry(RandomSaturationRangeKey, 0.0); | ||
423 | const quint8 saturation = static_cast<quint8>(configGroup.readEntry("MaxRandomSaturation", 0)); | 437 | double lightness = configGroup.readEntry(RandomLightnessRangeKey, 0.0); | ||
424 | 438 | | |||
425 | if (hue > MAX_HUE) { | 439 | qDebug("Color %2d: read: %6.2f, %6.2f, %6.2f, #%02x%02x%02x", index, hue, saturation, lightness, entry.red(), entry.green(), entry.blue()); | ||
426 | qCDebug(KonsoleDebug)<<"ColorScheme"<<name()<<"has an invalid MaxRandomHue"<<hue<<"for index"<< index<<", using"<<MAX_HUE; | 440 | | ||
427 | hue = MAX_HUE; | 441 | if (0 > hue || hue > MaxHue) { | ||
442 | qCDebug(KonsoleDebug) << QStringLiteral("Color scheme \"%1\": invalid value for index %2: %3 = %4. Allowed value range: 0.0 - 360.0. Using 0.0.") | ||||
443 | .arg(name()).arg(index).arg(QLatin1String(RandomHueRangeKey)).arg(hue, 0, 'g', 1); | ||||
444 | hue = 0.0; | ||||
445 | } | ||||
446 | if (0 > saturation || saturation > MaxSaturation) { | ||||
447 | qCDebug(KonsoleDebug) << QStringLiteral("Color scheme \"%1\": invalid value for index %2: %3 = %4. Allowed value range: 0.0 - 100.0. Using 0.0.") | ||||
448 | .arg(name()).arg(index).arg(QLatin1String(RandomSaturationRangeKey)).arg(saturation, 0, 'g', 1); | ||||
449 | saturation = 0.0; | ||||
450 | } | ||||
451 | if (0 > lightness || lightness > MaxLightness) { | ||||
452 | qCDebug(KonsoleDebug) << QStringLiteral("Color scheme \"%1\": invalid value for index %2: %3 = %4. Allowed value range: 0.0 - 100.0. Using 0.0.") | ||||
453 | .arg(name()).arg(index).arg(QLatin1String(RandomLightnessRangeKey)).arg(lightness, 0, 'g', 1); | ||||
454 | lightness = 0.0; | ||||
428 | } | 455 | } | ||
429 | 456 | | |||
430 | if (hue != 0 || value != 0 || saturation != 0) { | 457 | if (!qFuzzyIsNull(hue) || !qFuzzyIsNull(saturation) || !qFuzzyIsNull(lightness)) { | ||
431 | setRandomizationRange(index, hue, saturation, value); | 458 | setRandomizationRange(index, hue, saturation, lightness); | ||
432 | } | 459 | } | ||
433 | } | 460 | } | ||
434 | 461 | | |||
435 | void ColorScheme::write(KConfig &config) const | 462 | void ColorScheme::write(KConfig &config) const | ||
436 | { | 463 | { | ||
437 | KConfigGroup configGroup = config.group("General"); | 464 | KConfigGroup configGroup = config.group("General"); | ||
438 | 465 | | |||
439 | configGroup.writeEntry("Description", _description); | 466 | configGroup.writeEntry("Description", _description); | ||
Show All 24 Lines | 477 | { | |||
464 | if (configGroup.hasKey("Bold")) { | 491 | if (configGroup.hasKey("Bold")) { | ||
465 | configGroup.deleteEntry("Bold"); | 492 | configGroup.deleteEntry("Bold"); | ||
466 | } | 493 | } | ||
467 | 494 | | |||
468 | RandomizationRange random = _randomTable != nullptr ? _randomTable[index] : RandomizationRange(); | 495 | RandomizationRange random = _randomTable != nullptr ? _randomTable[index] : RandomizationRange(); | ||
469 | 496 | | |||
470 | // record randomization if this color has randomization or | 497 | // record randomization if this color has randomization or | ||
471 | // if one of the keys already exists | 498 | // if one of the keys already exists | ||
472 | if (!random.isNull() || configGroup.hasKey("MaxRandomHue")) { | 499 | if (!random.isNull() || configGroup.hasKey(RandomHueRangeKey)) { | ||
473 | configGroup.writeEntry("MaxRandomHue", static_cast<int>(random.hue)); | 500 | configGroup.writeEntry(RandomHueRangeKey, random.hue); | ||
474 | configGroup.writeEntry("MaxRandomValue", static_cast<int>(random.value)); | 501 | configGroup.writeEntry(RandomSaturationRangeKey, random.saturation); | ||
475 | configGroup.writeEntry("MaxRandomSaturation", static_cast<int>(random.saturation)); | 502 | configGroup.writeEntry(RandomLightnessRangeKey, random.lightness); | ||
476 | } | 503 | } | ||
477 | } | 504 | } | ||
478 | 505 | | |||
479 | void ColorScheme::setWallpaper(const QString &path) | 506 | void ColorScheme::setWallpaper(const QString &path) | ||
480 | { | 507 | { | ||
481 | _wallpaper = new ColorSchemeWallpaper(path); | 508 | _wallpaper = new ColorSchemeWallpaper(path); | ||
482 | } | 509 | } | ||
483 | 510 | | |||
▲ Show 20 Lines • Show All 60 Lines • Show Last 20 Lines |
nitpick - you don't need to fix these now - "static definition in anonymous namespace; static is redundant here"