mirror of
https://github.com/KDE/konsole.git
synced 2025-12-23 23:38:08 -05:00
Keep perceived contrast in random background color
Summary: Randomize colors using HSLuv color space instead of HSL. It has perceptually uniform lightness, which means every color with the same lightness value is perceived as equally bright by humans. Displays quality and lack of calibration in most monitors lowers this uniformity, but even on worst color display it should be better than standard HSL. More information about HSLuv: http://www.hsluv.org/ Minor changes: * Random seed takes PID into account to prevent repeated colors in separate Konsole processes * Key names in a config were changed * Adapted "Black on random light" color scheme Breeze with random ranges on default bg and fg: hue=360° saturation=100: {F6754773} To be done in future: * Automatically convert color schemes which use old randomization method. Reviewers: #konsole, #vdg Subscribers: hindenburg, #vdg, #konsole Tags: #konsole, #vdg Differential Revision: https://phabricator.kde.org/D20263
This commit is contained in:
committed by
Kurt Hindenburg
parent
74a0121422
commit
dc29be39a6
@@ -1,12 +1,20 @@
|
||||
[Background]
|
||||
Color=247,247,214
|
||||
MaxRandomHue=340
|
||||
RandomHueRange=360
|
||||
RandomSaturationRange=25
|
||||
RandomLightnessRange=10
|
||||
|
||||
[BackgroundIntense]
|
||||
Color=255,255,221
|
||||
RandomHueRange=360
|
||||
RandomSaturationRange=25
|
||||
RandomLightnessRange=10
|
||||
|
||||
[BackgroundFaint]
|
||||
Color=247,247,214
|
||||
RandomHueRange=360
|
||||
RandomSaturationRange=25
|
||||
RandomLightnessRange=10
|
||||
|
||||
[Color0]
|
||||
Color=0,0,0
|
||||
@@ -92,4 +100,5 @@ Color=0,0,0
|
||||
|
||||
[General]
|
||||
Description=Black on Random Light
|
||||
ColorRandomization=true
|
||||
Opacity=1
|
||||
|
||||
@@ -51,6 +51,7 @@ qt5_add_dbus_adaptor(windowadaptors_SRCS
|
||||
|
||||
set(konsoleprivate_SRCS ${sessionadaptors_SRCS}
|
||||
${windowadaptors_SRCS}
|
||||
hsluv.c
|
||||
BookmarkHandler.cpp
|
||||
ColorScheme.cpp
|
||||
ColorSchemeManager.cpp
|
||||
|
||||
@@ -43,6 +43,41 @@ typedef QColor ColorEntry;
|
||||
#define INTENSITIES 3
|
||||
#define TABLE_COLORS (INTENSITIES*BASE_COLORS)
|
||||
|
||||
enum ColorTableIndex {
|
||||
ColorFgIndex,
|
||||
ColorBgIndex,
|
||||
Color0Index,
|
||||
Color1Index,
|
||||
Color2Index,
|
||||
Color3Index,
|
||||
Color4Index,
|
||||
Color5Index,
|
||||
Color6Index,
|
||||
Color7Index,
|
||||
|
||||
ColorFgIntenseIndex,
|
||||
ColorBgIntenseIndex,
|
||||
Color0IntenseIndex,
|
||||
Color1IntenseIndex,
|
||||
Color2IntenseIndex,
|
||||
Color3IntenseIndex,
|
||||
Color4IntenseIndex,
|
||||
Color5IntenseIndex,
|
||||
Color6IntenseIndex,
|
||||
Color7IntenseIndex,
|
||||
|
||||
ColorFgFaintIndex,
|
||||
ColorBgFaintIndex,
|
||||
Color0FaintIndex,
|
||||
Color1FaintIndex,
|
||||
Color2FaintIndex,
|
||||
Color3FaintIndex,
|
||||
Color4FaintIndex,
|
||||
Color5FaintIndex,
|
||||
Color6FaintIndex,
|
||||
Color7FaintIndex,
|
||||
};
|
||||
|
||||
#define DEFAULT_FORE_COLOR 0
|
||||
#define DEFAULT_BACK_COLOR 1
|
||||
|
||||
|
||||
@@ -21,9 +21,11 @@
|
||||
|
||||
// Own
|
||||
#include "ColorScheme.h"
|
||||
#include "hsluv.h"
|
||||
|
||||
// Qt
|
||||
#include <QPainter>
|
||||
#include <QVariant>
|
||||
|
||||
// KDE
|
||||
#include <KConfig>
|
||||
@@ -38,6 +40,15 @@
|
||||
namespace {
|
||||
const int FGCOLOR_INDEX = 0;
|
||||
const int BGCOLOR_INDEX = 1;
|
||||
|
||||
const char RandomHueRangeKey[] = "RandomHueRange";
|
||||
const char RandomSaturationRangeKey[] = "RandomSaturationRange";
|
||||
const char RandomLightnessRangeKey[] = "RandomLightnessRange";
|
||||
const char EnableColorRandomizationKey[] = "ColorRandomization";
|
||||
|
||||
const double MaxHue = 360.0;
|
||||
const double MaxSaturation = 100.0;
|
||||
const double MaxLightness = 100.0;
|
||||
}
|
||||
|
||||
using namespace Konsole;
|
||||
@@ -193,7 +204,7 @@ ColorScheme::ColorScheme(const ColorScheme &other) :
|
||||
if (other._randomTable != nullptr) {
|
||||
for (int i = 0; i < TABLE_COLORS; i++) {
|
||||
const RandomizationRange &range = other._randomTable[i];
|
||||
setRandomizationRange(i, range.hue, range.saturation, range.value);
|
||||
setRandomizationRange(i, range.hue, range.saturation, range.lightness);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -250,10 +261,15 @@ ColorEntry ColorScheme::colorEntry(int index, uint randomSeed) const
|
||||
|
||||
ColorEntry entry = colorTable()[index];
|
||||
|
||||
if (randomSeed == 0 || _randomTable == nullptr || _randomTable[index].isNull()) {
|
||||
if (!_colorRandomization || randomSeed == 0 || _randomTable == nullptr
|
||||
|| _randomTable[index].isNull()) {
|
||||
return entry;
|
||||
}
|
||||
|
||||
double baseHue, baseSaturation, baseLightness;
|
||||
rgb2hsluv(entry.redF(), entry.greenF(), entry.blueF(),
|
||||
&baseHue, &baseSaturation, &baseLightness);
|
||||
|
||||
const RandomizationRange &range = _randomTable[index];
|
||||
|
||||
// 32-bit Mersenne Twister
|
||||
@@ -261,33 +277,56 @@ ColorEntry ColorScheme::colorEntry(int index, uint randomSeed) const
|
||||
// minstd_rand0 which always gives us 0 on the first number.
|
||||
std::mt19937 randomEngine(randomSeed);
|
||||
|
||||
int hueDifference = 0;
|
||||
if (range.hue != 0u) {
|
||||
std::uniform_int_distribution<int> dist(0, range.hue);
|
||||
hueDifference = dist(randomEngine);
|
||||
// Use hues located around base color's hue.
|
||||
// H=0 [|= =] H=128 [ =|= ] H=360 [= =|]
|
||||
const double minHue = baseHue - range.hue / 2.0;
|
||||
const double maxHue = baseHue + range.hue / 2.0;
|
||||
std::uniform_real_distribution<> hueDistribution(minHue, maxHue);
|
||||
// Hue value is an angle, it wraps after 360°. Adding MAX_HUE
|
||||
// guarantees that the sum is not negative.
|
||||
const double hue = fmod(MaxHue + hueDistribution(randomEngine), MaxHue);
|
||||
|
||||
// Saturation is always decreased. With more saturation more
|
||||
// information about hue is preserved in RGB color space
|
||||
// (consider red with S=100 and "red" with S=0 which is gray).
|
||||
// Additionally, I think it can be easier to imagine more
|
||||
// toned color than more vivid one.
|
||||
// S=0 [|== ] S=50 [ ==| ] S=100 [ ==|]
|
||||
const double minSaturation = qMax(baseSaturation - range.saturation, 0.0);
|
||||
const double maxSaturation = qMax(range.saturation, baseSaturation);
|
||||
// Use rising linear distribution as colors with lower
|
||||
// saturation are less distinguishable.
|
||||
std::piecewise_linear_distribution<> saturationDistribution({minSaturation, maxSaturation},
|
||||
[](double v) { return v; });
|
||||
const double saturation = qFuzzyCompare(minSaturation, maxSaturation)
|
||||
? baseSaturation
|
||||
: saturationDistribution(randomEngine);
|
||||
|
||||
// Lightness range has base value at its center. The base
|
||||
// value is clamped to prevent the range from shrinking.
|
||||
// L=0 [=|= ] L=50 [ =|= ] L=100 [ =|=]
|
||||
baseLightness = qBound(range.lightness / 2.0, baseLightness , MaxLightness - range.lightness);
|
||||
const double minLightness = qMax(baseLightness - range.lightness / 2.0, 0.0);
|
||||
const double maxLightness = qMin(baseLightness + range.lightness / 2.0, MaxLightness);
|
||||
// Use triangular distribution with peak at L=50.0.
|
||||
// Dark and very light colors are less distinguishable.
|
||||
std::initializer_list<double> lightnessIntervals;
|
||||
if (minLightness < 50.0 && 50.0 < maxLightness) {
|
||||
lightnessIntervals = {minLightness, 50.0, maxLightness};
|
||||
} else {
|
||||
lightnessIntervals = {minLightness, maxLightness};
|
||||
}
|
||||
static const auto lightnessWeightsFunc = [](double v) { return 50.0 - qAbs(v - 50.0); };
|
||||
std::piecewise_linear_distribution<> lightnessDistribution(lightnessIntervals,
|
||||
lightnessWeightsFunc);
|
||||
const double lightness = qFuzzyCompare(minLightness, maxLightness)
|
||||
? baseLightness
|
||||
: lightnessDistribution(randomEngine);
|
||||
|
||||
int saturationDifference = 0;
|
||||
if (range.saturation != 0u) {
|
||||
std::uniform_int_distribution<int> dist(0, range.saturation);
|
||||
saturationDifference = dist(randomEngine) - range.saturation / 2;
|
||||
}
|
||||
double red, green, blue;
|
||||
hsluv2rgb(hue, saturation, lightness, &red, &green, &blue);
|
||||
|
||||
int valueDifference = 0;
|
||||
if (range.value != 0u) {
|
||||
std::uniform_int_distribution<int> dist(0, range.value);
|
||||
valueDifference = dist(randomEngine) - range.value / 2;
|
||||
}
|
||||
|
||||
QColor &color = entry;
|
||||
|
||||
int newHue = qAbs((color.hue() + hueDifference) % MAX_HUE);
|
||||
int newValue = qMin(qAbs(color.value() + valueDifference), 255);
|
||||
int newSaturation = qMin(qAbs(color.saturation() + saturationDifference), 255);
|
||||
|
||||
color.setHsv(newHue, newSaturation, newValue);
|
||||
|
||||
return entry;
|
||||
return QColor(qRound(red * 255), qRound(green * 255), qRound(blue * 255));
|
||||
}
|
||||
|
||||
void ColorScheme::getColorTable(ColorEntry *table, uint randomSeed) const
|
||||
@@ -297,35 +336,38 @@ void ColorScheme::getColorTable(ColorEntry *table, uint randomSeed) const
|
||||
}
|
||||
}
|
||||
|
||||
bool ColorScheme::randomizedBackgroundColor() const
|
||||
bool ColorScheme::isColorRandomizationEnabled() const
|
||||
{
|
||||
return _randomTable == nullptr ? false : !_randomTable[BGCOLOR_INDEX].isNull();
|
||||
return (_colorRandomization && _randomTable != nullptr);
|
||||
}
|
||||
|
||||
void ColorScheme::setRandomizedBackgroundColor(bool randomize)
|
||||
void ColorScheme::setColorRandomization(bool randomize)
|
||||
{
|
||||
// the hue of the background color is allowed to be randomly
|
||||
// adjusted as much as possible.
|
||||
//
|
||||
// the value and saturation are left alone to maintain read-ability
|
||||
// except for dark background schemes which allow a change in the
|
||||
// colour value (one less than the dark background threshold)
|
||||
_colorRandomization = randomize;
|
||||
if (randomize) {
|
||||
quint8 maxValue = 0;
|
||||
|
||||
if (hasDarkBackground()) {
|
||||
maxValue = 126;
|
||||
bool hasAnyRandomizationEntries = false;
|
||||
if (_randomTable != nullptr) {
|
||||
for (int i = 0; !hasAnyRandomizationEntries && i < TABLE_COLORS; i++) {
|
||||
hasAnyRandomizationEntries = !_randomTable[i].isNull();
|
||||
}
|
||||
}
|
||||
// Set default randomization settings
|
||||
if (!hasAnyRandomizationEntries) {
|
||||
static const int ColorIndexesForRandomization[] = {
|
||||
ColorFgIndex, ColorBgIndex,
|
||||
ColorFgIntenseIndex, ColorBgIntenseIndex,
|
||||
ColorFgFaintIndex, ColorBgFaintIndex,
|
||||
};
|
||||
for (int index: ColorIndexesForRandomization) {
|
||||
setRandomizationRange(index, MaxHue, MaxSaturation, 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
setRandomizationRange(BGCOLOR_INDEX, MAX_HUE, 255, maxValue);
|
||||
} else if (_randomTable != nullptr) {
|
||||
setRandomizationRange(BGCOLOR_INDEX, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void ColorScheme::setRandomizationRange(int index, quint16 hue, quint8 saturation, quint8 value)
|
||||
void ColorScheme::setRandomizationRange(int index, double hue, double saturation, double lightness)
|
||||
{
|
||||
Q_ASSERT(hue <= MAX_HUE);
|
||||
Q_ASSERT(hue <= MaxHue);
|
||||
Q_ASSERT(index >= 0 && index < TABLE_COLORS);
|
||||
|
||||
if (_randomTable == nullptr) {
|
||||
@@ -333,8 +375,8 @@ void ColorScheme::setRandomizationRange(int index, quint16 hue, quint8 saturatio
|
||||
}
|
||||
|
||||
_randomTable[index].hue = hue;
|
||||
_randomTable[index].value = value;
|
||||
_randomTable[index].saturation = saturation;
|
||||
_randomTable[index].lightness = lightness;
|
||||
}
|
||||
|
||||
const ColorEntry *ColorScheme::colorTable() const
|
||||
@@ -357,9 +399,12 @@ QColor ColorScheme::backgroundColor() const
|
||||
|
||||
bool ColorScheme::hasDarkBackground() const
|
||||
{
|
||||
// value can range from 0 - 255, with larger values indicating higher brightness.
|
||||
// so 127 is in the middle, anything less is deemed 'dark'
|
||||
return backgroundColor().value() < 127;
|
||||
double h, s, l;
|
||||
const double r = backgroundColor().redF();
|
||||
const double g = backgroundColor().greenF();
|
||||
const double b = backgroundColor().blueF();
|
||||
rgb2hsluv(r, g, b, &h, &s, &l);
|
||||
return l < 0.5;
|
||||
}
|
||||
|
||||
void ColorScheme::setOpacity(qreal opacity)
|
||||
@@ -396,6 +441,7 @@ void ColorScheme::read(const KConfig &config)
|
||||
setOpacity(configGroup.readEntry("Opacity", 1.0));
|
||||
_blur = configGroup.readEntry("Blur", false);
|
||||
setWallpaper(configGroup.readEntry("Wallpaper", QString()));
|
||||
_colorRandomization = configGroup.readEntry(EnableColorRandomizationKey, false);
|
||||
|
||||
for (int i = 0; i < TABLE_COLORS; i++) {
|
||||
readColorEntry(config, i);
|
||||
@@ -416,17 +462,25 @@ void ColorScheme::readColorEntry(const KConfig &config, int index)
|
||||
entry = configGroup.readEntry("Color", QColor());
|
||||
setColorTableEntry(index, entry);
|
||||
|
||||
quint16 hue = static_cast<quint16>(configGroup.readEntry("MaxRandomHue", 0));
|
||||
const quint8 value = static_cast<quint8>(configGroup.readEntry("MaxRandomValue", 0));
|
||||
const quint8 saturation = static_cast<quint8>(configGroup.readEntry("MaxRandomSaturation", 0));
|
||||
const auto readAndCheckConfigEntry = [&](const char *key, double min, double max) -> double {
|
||||
const double value = configGroup.readEntry(key, min);
|
||||
if (min > value || value > max) {
|
||||
qCDebug(KonsoleDebug) << QStringLiteral(
|
||||
"Color scheme \"%1\": color index 2 has an invalid value: %3 = %4. "
|
||||
"Allowed value range: %5 - %6. Using %7.")
|
||||
.arg(name()).arg(index).arg(QLatin1String(key)).arg(value, 0, 'g', 1)
|
||||
.arg(min, 0, 'g', 1).arg(max, 0, 'g', 1).arg(min, 0, 'g', 1);
|
||||
return min;
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
if (hue > MAX_HUE) {
|
||||
qCDebug(KonsoleDebug)<<"ColorScheme"<<name()<<"has an invalid MaxRandomHue"<<hue<<"for index"<< index<<", using"<<MAX_HUE;
|
||||
hue = MAX_HUE;
|
||||
}
|
||||
double hue = readAndCheckConfigEntry(RandomHueRangeKey, 0.0, MaxHue);
|
||||
double saturation = readAndCheckConfigEntry(RandomSaturationRangeKey, 0.0, MaxSaturation);
|
||||
double lightness = readAndCheckConfigEntry(RandomLightnessRangeKey, 0.0, MaxLightness);
|
||||
|
||||
if (hue != 0 || value != 0 || saturation != 0) {
|
||||
setRandomizationRange(index, hue, saturation, value);
|
||||
if (!qFuzzyIsNull(hue) || !qFuzzyIsNull(saturation) || !qFuzzyIsNull(lightness)) {
|
||||
setRandomizationRange(index, hue, saturation, lightness);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -438,6 +492,7 @@ void ColorScheme::write(KConfig &config) const
|
||||
configGroup.writeEntry("Opacity", _opacity);
|
||||
configGroup.writeEntry("Blur", _blur);
|
||||
configGroup.writeEntry("Wallpaper", _wallpaper->path());
|
||||
configGroup.writeEntry(EnableColorRandomizationKey, _colorRandomization);
|
||||
|
||||
for (int i = 0; i < TABLE_COLORS; i++) {
|
||||
writeColorEntry(config, i);
|
||||
@@ -453,25 +508,36 @@ void ColorScheme::writeColorEntry(KConfig &config, int index) const
|
||||
configGroup.writeEntry("Color", entry);
|
||||
|
||||
// Remove unused keys
|
||||
if (configGroup.hasKey("Transparent")) {
|
||||
configGroup.deleteEntry("Transparent");
|
||||
}
|
||||
if (configGroup.hasKey("Transparency")) {
|
||||
configGroup.deleteEntry("Transparency");
|
||||
}
|
||||
if (configGroup.hasKey("Bold")) {
|
||||
configGroup.deleteEntry("Bold");
|
||||
static const char *obsoleteKeys[] = {
|
||||
"Transparent",
|
||||
"Transparency",
|
||||
"Bold",
|
||||
// Uncomment when people stop using Konsole from 2019:
|
||||
// "MaxRandomHue",
|
||||
// "MaxRandomValue",
|
||||
// "MaxRandomSaturation"
|
||||
};
|
||||
for (const auto key: obsoleteKeys) {
|
||||
if (configGroup.hasKey(key)) {
|
||||
configGroup.deleteEntry(key);
|
||||
}
|
||||
}
|
||||
|
||||
RandomizationRange random = _randomTable != nullptr ? _randomTable[index] : RandomizationRange();
|
||||
|
||||
// record randomization if this color has randomization or
|
||||
// if one of the keys already exists
|
||||
if (!random.isNull() || configGroup.hasKey("MaxRandomHue")) {
|
||||
configGroup.writeEntry("MaxRandomHue", static_cast<int>(random.hue));
|
||||
configGroup.writeEntry("MaxRandomValue", static_cast<int>(random.value));
|
||||
configGroup.writeEntry("MaxRandomSaturation", static_cast<int>(random.saturation));
|
||||
}
|
||||
const auto checkAndMaybeSaveValue = [&](const char *key, double value) {
|
||||
const bool valueIsNull = qFuzzyCompare(value, 0.0);
|
||||
const bool keyExists = configGroup.hasKey(key);
|
||||
const bool keyExistsAndHasDifferentValue = !qFuzzyCompare(configGroup.readEntry(key, value),
|
||||
value);
|
||||
if ((!valueIsNull && !keyExists) || keyExistsAndHasDifferentValue) {
|
||||
configGroup.writeEntry(key, value);
|
||||
}
|
||||
};
|
||||
|
||||
checkAndMaybeSaveValue(RandomHueRangeKey, random.hue);
|
||||
checkAndMaybeSaveValue(RandomSaturationRangeKey, random.saturation);
|
||||
checkAndMaybeSaveValue(RandomLightnessRangeKey, random.lightness);
|
||||
}
|
||||
|
||||
void ColorScheme::setWallpaper(const QString &path)
|
||||
|
||||
@@ -132,8 +132,8 @@ public:
|
||||
|
||||
/**
|
||||
* Returns true if this color scheme has a dark background.
|
||||
* The background color is said to be dark if it has a value of less than 127
|
||||
* in the HSV color space.
|
||||
* The background color is said to be dark if it has a lightness
|
||||
* of less than 50% in the HSLuv color space.
|
||||
*/
|
||||
bool hasDarkBackground() const;
|
||||
|
||||
@@ -169,15 +169,15 @@ public:
|
||||
ColorSchemeWallpaper::Ptr wallpaper() const;
|
||||
|
||||
/**
|
||||
* Enables randomization of the background color. This will cause
|
||||
* the palette returned by getColorTable() and colorEntry() to
|
||||
* be adjusted depending on the value of the random seed argument
|
||||
* to them.
|
||||
* Enables colors randomization. This will cause the palette
|
||||
* returned by getColorTable() and colorEntry() to be adjusted
|
||||
* depending on the parameters of color randomization and the
|
||||
* random seed parameter passed to them.
|
||||
*/
|
||||
void setRandomizedBackgroundColor(bool randomize);
|
||||
void setColorRandomization(bool randomize);
|
||||
|
||||
/** Returns true if the background color is randomized. */
|
||||
bool randomizedBackgroundColor() const;
|
||||
/** Returns true if color randomization is enabled. */
|
||||
bool isColorRandomizationEnabled() const;
|
||||
|
||||
static const ColorEntry defaultTable[]; // table of default color entries
|
||||
|
||||
@@ -189,20 +189,20 @@ private:
|
||||
class RandomizationRange
|
||||
{
|
||||
public:
|
||||
RandomizationRange() : hue(0),
|
||||
saturation(0),
|
||||
value(0)
|
||||
RandomizationRange() : hue(0.0),
|
||||
saturation(0.0),
|
||||
lightness(0.0)
|
||||
{
|
||||
}
|
||||
|
||||
bool isNull() const
|
||||
{
|
||||
return hue == 0 && saturation == 0 && value == 0;
|
||||
return qFuzzyIsNull(hue) && qFuzzyIsNull(saturation) && qFuzzyIsNull(lightness);
|
||||
}
|
||||
|
||||
quint16 hue;
|
||||
quint8 saturation;
|
||||
quint8 value;
|
||||
double hue;
|
||||
double saturation;
|
||||
double lightness;
|
||||
};
|
||||
|
||||
// returns the active color table. if none has been set specifically,
|
||||
@@ -218,7 +218,7 @@ private:
|
||||
// sets the amount of randomization allowed for a particular color
|
||||
// in the palette. creates the randomization table if
|
||||
// it does not already exist
|
||||
void setRandomizationRange(int index, quint16 hue, quint8 saturation, quint8 value);
|
||||
void setRandomizationRange(int index, double hue, double saturation, double lightness);
|
||||
|
||||
QString _description;
|
||||
QString _name;
|
||||
@@ -236,9 +236,9 @@ private:
|
||||
// enables blur behind the terminal window
|
||||
bool _blur;
|
||||
|
||||
ColorSchemeWallpaper::Ptr _wallpaper;
|
||||
bool _colorRandomization;
|
||||
|
||||
static const quint16 MAX_HUE = 340;
|
||||
ColorSchemeWallpaper::Ptr _wallpaper;
|
||||
|
||||
static const char * const colorNames[TABLE_COLORS];
|
||||
static const char * const translatedColorNames[TABLE_COLORS];
|
||||
|
||||
@@ -258,7 +258,7 @@ void ColorSchemeEditor::setBlur(bool blur)
|
||||
|
||||
void ColorSchemeEditor::setRandomizedBackgroundColor(bool randomized)
|
||||
{
|
||||
_colors->setRandomizedBackgroundColor(randomized);
|
||||
_colors->setColorRandomization(randomized);
|
||||
}
|
||||
|
||||
void ColorSchemeEditor::setup(const ColorScheme *scheme, bool isNewScheme)
|
||||
@@ -291,7 +291,7 @@ void ColorSchemeEditor::setup(const ColorScheme *scheme, bool isNewScheme)
|
||||
_ui->blurCheckBox->setChecked(scheme->blur());
|
||||
|
||||
// randomized background color checkbox
|
||||
_ui->randomizedBackgroundCheck->setChecked(scheme->randomizedBackgroundColor());
|
||||
_ui->randomizedBackgroundCheck->setChecked(scheme->isColorRandomizationEnabled());
|
||||
|
||||
// wallpaper stuff
|
||||
_ui->wallpaperPath->setText(scheme->wallpaper()->path());
|
||||
|
||||
@@ -45,8 +45,12 @@
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="randomizedBackgroundCheck">
|
||||
<property name="toolTip">
|
||||
<string>Hue and saturation values of default foreground and background colors are randomized by default. Some color schemes might use different randomization settings.
|
||||
To see any effect, set colors with saturation value greater than 0.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Vary the background color for each tab</string>
|
||||
<string>Randomly adjust colors for each session</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
||||
@@ -774,9 +774,10 @@ void ViewManager::viewDestroyed(QWidget *view)
|
||||
TerminalDisplay *ViewManager::createTerminalDisplay(Session *session)
|
||||
{
|
||||
auto display = new TerminalDisplay(nullptr);
|
||||
display->setRandomSeed(session->sessionId() * 31);
|
||||
display->setRandomSeed(session->sessionId() | (qApp->applicationPid() << 10));
|
||||
connect(display, &TerminalDisplay::requestToggleExpansion,
|
||||
_viewContainer, &TabbedViewContainer::toggleMaximizeCurrentTerminal);
|
||||
|
||||
return display;
|
||||
}
|
||||
|
||||
|
||||
453
src/hsluv.c
Normal file
453
src/hsluv.c
Normal file
@@ -0,0 +1,453 @@
|
||||
/*
|
||||
* HSLuv-C: Human-friendly HSL
|
||||
* <http://github.com/hsluv/hsluv-c>
|
||||
* <http://www.hsluv.org/>
|
||||
*
|
||||
* Copyright (c) 2015 Alexei Boronine (original idea, JavaScript implementation)
|
||||
* Copyright (c) 2015 Roger Tallada (Obj-C implementation)
|
||||
* Copyright (c) 2017 Martin Mitas (C implementation, based on Obj-C implementation)
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation
|
||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
* and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
* IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "hsluv.h"
|
||||
|
||||
#include <float.h>
|
||||
#include <math.h>
|
||||
|
||||
|
||||
typedef struct Triplet_tag Triplet;
|
||||
struct Triplet_tag {
|
||||
double a;
|
||||
double b;
|
||||
double c;
|
||||
};
|
||||
|
||||
/* for RGB */
|
||||
static const Triplet m[3] = {
|
||||
{ 3.24096994190452134377, -1.53738317757009345794, -0.49861076029300328366 },
|
||||
{ -0.96924363628087982613, 1.87596750150772066772, 0.04155505740717561247 },
|
||||
{ 0.05563007969699360846, -0.20397695888897656435, 1.05697151424287856072 }
|
||||
};
|
||||
|
||||
/* for XYZ */
|
||||
static const Triplet m_inv[3] = {
|
||||
{ 0.41239079926595948129, 0.35758433938387796373, 0.18048078840183428751 },
|
||||
{ 0.21263900587151035754, 0.71516867876775592746, 0.07219231536073371500 },
|
||||
{ 0.01933081871559185069, 0.11919477979462598791, 0.95053215224966058086 }
|
||||
};
|
||||
|
||||
static const double ref_u = 0.19783000664283680764;
|
||||
static const double ref_v = 0.46831999493879100370;
|
||||
|
||||
static const double kappa = 903.29629629629629629630;
|
||||
static const double epsilon = 0.00885645167903563082;
|
||||
|
||||
|
||||
typedef struct Bounds_tag Bounds;
|
||||
struct Bounds_tag {
|
||||
double a;
|
||||
double b;
|
||||
};
|
||||
|
||||
|
||||
static void
|
||||
get_bounds(double l, Bounds bounds[6])
|
||||
{
|
||||
double tl = l + 16.0;
|
||||
double sub1 = (tl * tl * tl) / 1560896.0;
|
||||
double sub2 = (sub1 > epsilon ? sub1 : (l / kappa));
|
||||
int channel;
|
||||
int t;
|
||||
|
||||
for(channel = 0; channel < 3; channel++) {
|
||||
double m1 = m[channel].a;
|
||||
double m2 = m[channel].b;
|
||||
double m3 = m[channel].c;
|
||||
|
||||
for (t = 0; t < 2; t++) {
|
||||
double top1 = (284517.0 * m1 - 94839.0 * m3) * sub2;
|
||||
double top2 = (838422.0 * m3 + 769860.0 * m2 + 731718.0 * m1) * l * sub2 - 769860.0 * t * l;
|
||||
double bottom = (632260.0 * m3 - 126452.0 * m2) * sub2 + 126452.0 * t;
|
||||
|
||||
bounds[channel * 2 + t].a = top1 / bottom;
|
||||
bounds[channel * 2 + t].b = top2 / bottom;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static double
|
||||
intersect_line_line(const Bounds* line1, const Bounds* line2)
|
||||
{
|
||||
return (line1->b - line2->b) / (line2->a - line1->a);
|
||||
}
|
||||
|
||||
static double
|
||||
dist_from_pole_squared(double x, double y)
|
||||
{
|
||||
return x * x + y * y;
|
||||
}
|
||||
|
||||
static double
|
||||
ray_length_until_intersect(double theta, const Bounds* line)
|
||||
{
|
||||
return line->b / (sin(theta) - line->a * cos(theta));
|
||||
}
|
||||
|
||||
static double
|
||||
max_safe_chroma_for_l(double l)
|
||||
{
|
||||
double min_len_squared = DBL_MAX;
|
||||
Bounds bounds[6];
|
||||
int i;
|
||||
|
||||
get_bounds(l, bounds);
|
||||
for(i = 0; i < 6; i++) {
|
||||
double m1 = bounds[i].a;
|
||||
double b1 = bounds[i].b;
|
||||
/* x where line intersects with perpendicular running though (0, 0) */
|
||||
Bounds line2 = { -1.0 / m1, 0.0 };
|
||||
double x = intersect_line_line(&bounds[i], &line2);
|
||||
double distance = dist_from_pole_squared(x, b1 + x * m1);
|
||||
|
||||
if(distance < min_len_squared)
|
||||
min_len_squared = distance;
|
||||
}
|
||||
|
||||
return sqrt(min_len_squared);
|
||||
}
|
||||
|
||||
static double
|
||||
max_chroma_for_lh(double l, double h)
|
||||
{
|
||||
double min_len = DBL_MAX;
|
||||
double hrad = h * 0.01745329251994329577; /* (2 * pi / 360) */
|
||||
Bounds bounds[6];
|
||||
int i;
|
||||
|
||||
get_bounds(l, bounds);
|
||||
for(i = 0; i < 6; i++) {
|
||||
double len = ray_length_until_intersect(hrad, &bounds[i]);
|
||||
|
||||
if(len >= 0 && len < min_len)
|
||||
min_len = len;
|
||||
}
|
||||
return min_len;
|
||||
}
|
||||
|
||||
static double
|
||||
dot_product(const Triplet* t1, const Triplet* t2)
|
||||
{
|
||||
return (t1->a * t2->a + t1->b * t2->b + t1->c * t2->c);
|
||||
}
|
||||
|
||||
/* Used for rgb conversions */
|
||||
static double
|
||||
from_linear(double c)
|
||||
{
|
||||
if(c <= 0.0031308)
|
||||
return 12.92 * c;
|
||||
else
|
||||
return 1.055 * pow(c, 1.0 / 2.4) - 0.055;
|
||||
}
|
||||
|
||||
static double
|
||||
to_linear(double c)
|
||||
{
|
||||
if (c > 0.04045)
|
||||
return pow((c + 0.055) / 1.055, 2.4);
|
||||
else
|
||||
return c / 12.92;
|
||||
}
|
||||
|
||||
static void
|
||||
xyz2rgb(Triplet* in_out)
|
||||
{
|
||||
double r = from_linear(dot_product(&m[0], in_out));
|
||||
double g = from_linear(dot_product(&m[1], in_out));
|
||||
double b = from_linear(dot_product(&m[2], in_out));
|
||||
in_out->a = r;
|
||||
in_out->b = g;
|
||||
in_out->c = b;
|
||||
}
|
||||
|
||||
static void
|
||||
rgb2xyz(Triplet* in_out)
|
||||
{
|
||||
Triplet rgbl = { to_linear(in_out->a), to_linear(in_out->b), to_linear(in_out->c) };
|
||||
double x = dot_product(&m_inv[0], &rgbl);
|
||||
double y = dot_product(&m_inv[1], &rgbl);
|
||||
double z = dot_product(&m_inv[2], &rgbl);
|
||||
in_out->a = x;
|
||||
in_out->b = y;
|
||||
in_out->c = z;
|
||||
}
|
||||
|
||||
/* http://en.wikipedia.org/wiki/CIELUV
|
||||
* In these formulas, Yn refers to the reference white point. We are using
|
||||
* illuminant D65, so Yn (see refY in Maxima file) equals 1. The formula is
|
||||
* simplified accordingly.
|
||||
*/
|
||||
static double
|
||||
y2l(double y)
|
||||
{
|
||||
if(y <= epsilon)
|
||||
return y * kappa;
|
||||
else
|
||||
return 116.0 * cbrt(y) - 16.0;
|
||||
}
|
||||
|
||||
static double
|
||||
l2y(double l)
|
||||
{
|
||||
if(l <= 8.0) {
|
||||
return l / kappa;
|
||||
} else {
|
||||
double x = (l + 16.0) / 116.0;
|
||||
return (x * x * x);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
xyz2luv(Triplet* in_out)
|
||||
{
|
||||
double var_u = (4.0 * in_out->a) / (in_out->a + (15.0 * in_out->b) + (3.0 * in_out->c));
|
||||
double var_v = (9.0 * in_out->b) / (in_out->a + (15.0 * in_out->b) + (3.0 * in_out->c));
|
||||
double l = y2l(in_out->b);
|
||||
double u = 13.0 * l * (var_u - ref_u);
|
||||
double v = 13.0 * l * (var_v - ref_v);
|
||||
|
||||
in_out->a = l;
|
||||
if(l < 0.00000001) {
|
||||
in_out->b = 0.0;
|
||||
in_out->c = 0.0;
|
||||
} else {
|
||||
in_out->b = u;
|
||||
in_out->c = v;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
luv2xyz(Triplet* in_out)
|
||||
{
|
||||
if(in_out->a <= 0.00000001) {
|
||||
/* Black will create a divide-by-zero error. */
|
||||
in_out->a = 0.0;
|
||||
in_out->b = 0.0;
|
||||
in_out->c = 0.0;
|
||||
return;
|
||||
}
|
||||
|
||||
double var_u = in_out->b / (13.0 * in_out->a) + ref_u;
|
||||
double var_v = in_out->c / (13.0 * in_out->a) + ref_v;
|
||||
double y = l2y(in_out->a);
|
||||
double x = -(9.0 * y * var_u) / ((var_u - 4.0) * var_v - var_u * var_v);
|
||||
double z = (9.0 * y - (15.0 * var_v * y) - (var_v * x)) / (3.0 * var_v);
|
||||
in_out->a = x;
|
||||
in_out->b = y;
|
||||
in_out->c = z;
|
||||
}
|
||||
|
||||
static void
|
||||
luv2lch(Triplet* in_out)
|
||||
{
|
||||
double l = in_out->a;
|
||||
double u = in_out->b;
|
||||
double v = in_out->c;
|
||||
double h;
|
||||
double c = sqrt(u * u + v * v);
|
||||
|
||||
/* Grays: disambiguate hue */
|
||||
if(c < 0.00000001) {
|
||||
h = 0;
|
||||
} else {
|
||||
h = atan2(v, u) * 57.29577951308232087680; /* (180 / pi) */
|
||||
if(h < 0.0)
|
||||
h += 360.0;
|
||||
}
|
||||
|
||||
in_out->a = l;
|
||||
in_out->b = c;
|
||||
in_out->c = h;
|
||||
}
|
||||
|
||||
static void
|
||||
lch2luv(Triplet* in_out)
|
||||
{
|
||||
double hrad = in_out->c * 0.01745329251994329577; /* (pi / 180.0) */
|
||||
double u = cos(hrad) * in_out->b;
|
||||
double v = sin(hrad) * in_out->b;
|
||||
|
||||
in_out->b = u;
|
||||
in_out->c = v;
|
||||
}
|
||||
|
||||
static void
|
||||
hsluv2lch(Triplet* in_out)
|
||||
{
|
||||
double h = in_out->a;
|
||||
double s = in_out->b;
|
||||
double l = in_out->c;
|
||||
double c;
|
||||
|
||||
/* White and black: disambiguate chroma */
|
||||
if(l > 99.9999999 || l < 0.00000001)
|
||||
c = 0.0;
|
||||
else
|
||||
c = max_chroma_for_lh(l, h) / 100.0 * s;
|
||||
|
||||
/* Grays: disambiguate hue */
|
||||
if (s < 0.00000001)
|
||||
h = 0.0;
|
||||
|
||||
in_out->a = l;
|
||||
in_out->b = c;
|
||||
in_out->c = h;
|
||||
}
|
||||
|
||||
static void
|
||||
lch2hsluv(Triplet* in_out)
|
||||
{
|
||||
double l = in_out->a;
|
||||
double c = in_out->b;
|
||||
double h = in_out->c;
|
||||
double s;
|
||||
|
||||
/* White and black: disambiguate saturation */
|
||||
if(l > 99.9999999 || l < 0.00000001)
|
||||
s = 0.0;
|
||||
else
|
||||
s = c / max_chroma_for_lh(l, h) * 100.0;
|
||||
|
||||
/* Grays: disambiguate hue */
|
||||
if (c < 0.00000001)
|
||||
h = 0.0;
|
||||
|
||||
in_out->a = h;
|
||||
in_out->b = s;
|
||||
in_out->c = l;
|
||||
}
|
||||
|
||||
static void
|
||||
hpluv2lch(Triplet* in_out)
|
||||
{
|
||||
double h = in_out->a;
|
||||
double s = in_out->b;
|
||||
double l = in_out->c;
|
||||
double c;
|
||||
|
||||
/* White and black: disambiguate chroma */
|
||||
if(l > 99.9999999 || l < 0.00000001)
|
||||
c = 0.0;
|
||||
else
|
||||
c = max_safe_chroma_for_l(l) / 100.0 * s;
|
||||
|
||||
/* Grays: disambiguate hue */
|
||||
if (s < 0.00000001)
|
||||
h = 0.0;
|
||||
|
||||
in_out->a = l;
|
||||
in_out->b = c;
|
||||
in_out->c = h;
|
||||
}
|
||||
|
||||
static void
|
||||
lch2hpluv(Triplet* in_out)
|
||||
{
|
||||
double l = in_out->a;
|
||||
double c = in_out->b;
|
||||
double h = in_out->c;
|
||||
double s;
|
||||
|
||||
/* White and black: disambiguate saturation */
|
||||
if (l > 99.9999999 || l < 0.00000001)
|
||||
s = 0.0;
|
||||
else
|
||||
s = c / max_safe_chroma_for_l(l) * 100.0;
|
||||
|
||||
/* Grays: disambiguate hue */
|
||||
if (c < 0.00000001)
|
||||
h = 0.0;
|
||||
|
||||
in_out->a = h;
|
||||
in_out->b = s;
|
||||
in_out->c = l;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void
|
||||
hsluv2rgb(double h, double s, double l, double* pr, double* pg, double* pb)
|
||||
{
|
||||
Triplet tmp = { h, s, l };
|
||||
|
||||
hsluv2lch(&tmp);
|
||||
lch2luv(&tmp);
|
||||
luv2xyz(&tmp);
|
||||
xyz2rgb(&tmp);
|
||||
|
||||
*pr = tmp.a;
|
||||
*pg = tmp.b;
|
||||
*pb = tmp.c;
|
||||
}
|
||||
|
||||
void
|
||||
hpluv2rgb(double h, double s, double l, double* pr, double* pg, double* pb)
|
||||
{
|
||||
Triplet tmp = { h, s, l };
|
||||
|
||||
hpluv2lch(&tmp);
|
||||
lch2luv(&tmp);
|
||||
luv2xyz(&tmp);
|
||||
xyz2rgb(&tmp);
|
||||
|
||||
*pr = tmp.a;
|
||||
*pg = tmp.b;
|
||||
*pb = tmp.c;
|
||||
}
|
||||
|
||||
void
|
||||
rgb2hsluv(double r, double g, double b, double* ph, double* ps, double* pl)
|
||||
{
|
||||
Triplet tmp = { r, g, b };
|
||||
|
||||
rgb2xyz(&tmp);
|
||||
xyz2luv(&tmp);
|
||||
luv2lch(&tmp);
|
||||
lch2hsluv(&tmp);
|
||||
|
||||
*ph = tmp.a;
|
||||
*ps = tmp.b;
|
||||
*pl = tmp.c;
|
||||
}
|
||||
|
||||
void
|
||||
rgb2hpluv(double r, double g, double b, double* ph, double* ps, double* pl)
|
||||
{
|
||||
Triplet tmp = { r, g, b };
|
||||
|
||||
rgb2xyz(&tmp);
|
||||
xyz2luv(&tmp);
|
||||
luv2lch(&tmp);
|
||||
lch2hpluv(&tmp);
|
||||
|
||||
*ph = tmp.a;
|
||||
*ps = tmp.b;
|
||||
*pl = tmp.c;
|
||||
}
|
||||
90
src/hsluv.h
Normal file
90
src/hsluv.h
Normal file
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* HSLuv-C: Human-friendly HSL
|
||||
* <http://github.com/hsluv/hsluv-c>
|
||||
* <http://www.hsluv.org/>
|
||||
*
|
||||
* Copyright (c) 2015 Alexei Boronine (original idea, JavaScript implementation)
|
||||
* Copyright (c) 2015 Roger Tallada (Obj-C implementation)
|
||||
* Copyright (c) 2017 Martin Mitas (C implementation, based on Obj-C implementation)
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation
|
||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
* and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
* IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef HSLUV_H
|
||||
#define HSLUV_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
|
||||
/**
|
||||
* Convert HSLuv to RGB.
|
||||
*
|
||||
* @param h Hue. Between 0.0 and 360.0.
|
||||
* @param s Saturation. Between 0.0 and 100.0.
|
||||
* @param l Lightness. Between 0.0 and 100.0.
|
||||
* @param[out] pr Red component. Between 0.0 and 1.0.
|
||||
* @param[out] pr Green component. Between 0.0 and 1.0.
|
||||
* @param[out] pr Blue component. Between 0.0 and 1.0.
|
||||
*/
|
||||
void hsluv2rgb(double h, double s, double l, double* pr, double* pg, double* pb);
|
||||
|
||||
/**
|
||||
* Convert RGB to HSLuv.
|
||||
*
|
||||
* @param r Red component. Between 0.0 and 1.0.
|
||||
* @param g Green component. Between 0.0 and 1.0.
|
||||
* @param b Blue component. Between 0.0 and 1.0.
|
||||
* @param[out] ph Hue. Between 0.0 and 360.0.
|
||||
* @param[out] ps Saturation. Between 0.0 and 100.0.
|
||||
* @param[out] pl Lightness. Between 0.0 and 100.0.
|
||||
*/
|
||||
void rgb2hsluv(double r, double g, double b, double* ph, double* ps, double* pl);
|
||||
|
||||
/**
|
||||
* Convert HPLuv to RGB.
|
||||
*
|
||||
* @param h Hue. Between 0.0 and 360.0.
|
||||
* @param s Saturation. Between 0.0 and 100.0.
|
||||
* @param l Lightness. Between 0.0 and 100.0.
|
||||
* @param[out] pr Red component. Between 0.0 and 1.0.
|
||||
* @param[out] pg Green component. Between 0.0 and 1.0.
|
||||
* @param[out] pb Blue component. Between 0.0 and 1.0.
|
||||
*/
|
||||
void hpluv2rgb(double h, double s, double l, double* pr, double* pg, double* pb);
|
||||
|
||||
/**
|
||||
* Convert RGB to HPLuv.
|
||||
*
|
||||
* @param r Red component. Between 0.0 and 1.0.
|
||||
* @param g Green component. Between 0.0 and 1.0.
|
||||
* @param b Blue component. Between 0.0 and 1.0.
|
||||
* @param[out] ph Hue. Between 0.0 and 360.0.
|
||||
* @param[out] ps Saturation. Between 0.0 and 100.0.
|
||||
* @param[out] pl Lightness. Between 0.0 and 100.0.
|
||||
*/
|
||||
void rgb2hpluv(double r, double g, double b, double* ph, double* ps, double* pl);
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* HSLUV_H */
|
||||
Reference in New Issue
Block a user