pioneer/src/RandomColor.cpp

346 lines
7.8 KiB
C++

// Copyright © 2008-2020 Pioneer Developers. See AUTHORS.txt for details
// Licensed under the terms of the GPL v3. See licenses/GPL-3.txt
#include "RandomColor.h"
#include "libs.h"
#include "utils.h"
#include <algorithm>
namespace RandomColorGenerator {
//static
std::map<ColorScheme, RandomColor::DefinedColor> RandomColor::ColorDictionary;
static Random *pRNG = nullptr;
RandomColor::RandomColor()
{
// Populate the color dictionary
LoadColorBounds();
}
/// Gets a new random color.
/// <param name="scheme">Which color schemed to use when generating the color.</param>
/// <param name="luminosity">The desired luminosity of the color.</param>
//static
Color RandomColor::GetColor(ColorScheme scheme, Luminosity luminosity)
{
int H, S, B;
// First we pick a hue (H)
H = PickHue(scheme);
// Then use H to determine saturation (S)
S = PickSaturation(H, luminosity, scheme);
// Then use S and H to determine brightness (B).
B = PickBrightness(H, S, luminosity);
// Then we return the HSB color in the desired format
return HsvToColor(H, S, B);
}
/// Generates multiple random colors.
/// <param name="scheme">Which color scheme to use when generating the color.</param>
/// <param name="luminosity">The desired luminosity of the color.</param>
/// <param name="count">How many colors to generate</param>
//static
std::vector<Color> RandomColor::GetColors(Random &rand, ColorScheme scheme, Luminosity luminosity, int count)
{
pRNG = &rand;
std::vector<Color> ret;
ret.resize(count);
for (int i = 0; i < count; i++) {
ret[i] = GetColor(scheme, luminosity);
}
pRNG = nullptr;
return ret;
}
//static
int RandomColor::PickHue(ColorScheme scheme)
{
Range hueRange(GetHueRange(scheme));
int hue = RandomWithin(hueRange);
// Instead of storing red as two separate ranges,
// we group them, using negative numbers
if (hue < 0) hue = 360 + hue;
return hue;
}
//static
int RandomColor::PickSaturation(int hue, Luminosity luminosity, ColorScheme scheme)
{
if (luminosity == Luminosity::LUMINOSITY_RANDOM) {
return RandomWithin(0, 100);
}
if (scheme == ColorScheme::SCHEME_MONOCHROME) {
return 0;
}
Range saturationRange = GetColorInfo(hue).SaturationRange;
int sMin = saturationRange.Lower;
int sMax = saturationRange.Upper;
switch (luminosity) {
case Luminosity::LUMINOSITY_BRIGHT:
sMin = 55;
break;
case Luminosity::LUMINOSITY_DARK:
sMin = sMax - 10;
break;
case Luminosity::LUMINOSITY_LIGHT:
sMax = 55;
break;
case Luminosity::LUMINOSITY_RANDOM:
// TODO: is this correct? just leave sMin/sMax?
break;
}
return RandomWithin(sMin, sMax);
}
//static
int RandomColor::PickBrightness(int H, int S, Luminosity luminosity)
{
auto bMin = GetMinimumBrightness(H, S);
auto bMax = 100;
switch (luminosity) {
case Luminosity::LUMINOSITY_DARK:
bMax = bMin + 20;
break;
case Luminosity::LUMINOSITY_LIGHT:
bMin = (bMax + bMin) / 2;
break;
case Luminosity::LUMINOSITY_RANDOM:
bMin = 0;
bMax = 100;
break;
case Luminosity::LUMINOSITY_BRIGHT:
// TODO: is this correct? just leave min/max?
break;
}
return RandomWithin(bMin, bMax);
}
//static
int RandomColor::GetMinimumBrightness(int H, int S)
{
auto lowerBounds = GetColorInfo(H).LowerBounds;
for (size_t i = 0; i < std::min(lowerBounds.size(), lowerBounds.size() - 1U); i++) {
auto s1 = lowerBounds[i].x;
auto v1 = lowerBounds[i].y;
auto s2 = lowerBounds[i + 1].x;
auto v2 = lowerBounds[i + 1].y;
if (S >= s1 && S <= s2) {
auto m = (v2 - v1) / (s2 - s1);
auto b = v1 - m * s1;
return static_cast<int>(m * S + b);
}
}
return 0;
}
//static
Range RandomColor::GetHueRange(ColorScheme colorInput)
{
std::map<ColorScheme, DefinedColor>::iterator out = ColorDictionary.find(colorInput);
if (out != ColorDictionary.end()) {
return out->second.HueRange;
}
return Range(0, 360);
}
//static
RandomColor::DefinedColor RandomColor::GetColorInfo(int hue)
{
// Maps red colors to make picking hue easier
if (hue >= 334 && hue <= 360) {
hue -= 360;
}
for (auto c : ColorDictionary) {
if (hue >= c.second.HueRange[0] && hue <= c.second.HueRange[1]) {
return c.second;
}
}
return DefinedColor();
}
//static
int RandomColor::RandomWithin(Range range)
{
return RandomWithin(range.Lower, range.Upper);
}
//static
int RandomColor::RandomWithin(int lower, int upper)
{
assert(pRNG);
return pRNG->Int32(lower, upper + 1);
}
//static
void RandomColor::DefineColor(ColorScheme scheme, Point hueRange, const Point *lowerBounds, const size_t lbCount)
{
auto sMin = lowerBounds[0].x;
auto sMax = lowerBounds[lbCount - 1].x;
auto bMin = lowerBounds[lbCount - 1].y;
auto bMax = lowerBounds[0].y;
DefinedColor defCol;
defCol.HueRange = Range(hueRange.x, hueRange.y);
for (size_t lb = 0; lb < lbCount; lb++) {
defCol.LowerBounds.push_back(lowerBounds[lb]);
}
defCol.SaturationRange = Range(sMin, sMax);
defCol.BrightnessRange = Range(bMin, bMax);
ColorDictionary[scheme] = defCol;
}
//static
void RandomColor::LoadColorBounds()
{
const Point mono[] = { { 0, 0 }, { 100, 0 } };
DefineColor(
ColorScheme::SCHEME_MONOCHROME,
Point(0, 360),
mono,
COUNTOF(mono));
Point red[] = { { 20, 100 }, { 30, 92 }, { 40, 89 }, { 50, 85 }, { 60, 78 }, { 70, 70 }, { 80, 60 }, { 90, 55 }, { 100, 50 } };
DefineColor(
ColorScheme::SCHEME_RED,
Point(-26, 18),
red,
COUNTOF(red));
Point orange[] = { { 20, 100 }, { 30, 93 }, { 40, 88 }, { 50, 86 }, { 60, 85 }, { 70, 70 }, { 100, 70 } };
DefineColor(
ColorScheme::SCHEME_ORANGE,
Point(19, 46),
orange,
COUNTOF(orange));
Point yellow[] = { { 25, 100 }, { 40, 94 }, { 50, 89 }, { 60, 86 }, { 70, 84 }, { 80, 82 }, { 90, 80 }, { 100, 75 } };
DefineColor(
ColorScheme::SCHEME_YELLOW,
Point(47, 62),
yellow,
COUNTOF(yellow));
Point green[] = { { 30, 100 }, { 40, 90 }, { 50, 85 }, { 60, 81 }, { 70, 74 }, { 80, 64 }, { 90, 50 }, { 100, 40 } };
DefineColor(
ColorScheme::SCHEME_GREEN,
Point(63, 178),
green,
COUNTOF(green));
Point blue[] = { { 20, 100 }, { 30, 86 }, { 40, 80 }, { 50, 74 }, { 60, 60 }, { 70, 52 }, { 80, 44 }, { 90, 39 }, { 100, 35 } };
DefineColor(
ColorScheme::SCHEME_BLUE,
Point(179, 257),
blue,
COUNTOF(blue));
Point purple[] = { { 20, 100 }, { 30, 87 }, { 40, 79 }, { 50, 70 }, { 60, 65 }, { 70, 59 }, { 80, 52 }, { 90, 45 }, { 100, 42 } };
DefineColor(
ColorScheme::SCHEME_PURPLE,
Point(258, 282),
purple,
COUNTOF(purple));
Point pink[] = { { 20, 100 }, { 30, 90 }, { 40, 86 }, { 60, 84 }, { 80, 80 }, { 90, 75 }, { 100, 73 } };
DefineColor(
ColorScheme::SCHEME_PINK,
Point(283, 334),
pink,
COUNTOF(pink));
}
/// Converts hue, saturation, and lightness to a color.
//static
Color RandomColor::HsvToColor(int hue, int saturation, double value)
{
// this doesn't work for the values of 0 and 360
// here's the hacky fix
auto h = double(hue);
if (is_equal_exact(h, 0.0)) {
h = 1.0;
}
if (is_equal_exact(h, 360.0)) {
h = 359.0;
}
// Rebase the h,s,v values
h = h / 360.0;
auto s = saturation / 100.0;
auto v = value / 100.0;
auto hInt = static_cast<int>(floor(h * 6.0));
auto f = h * 6 - hInt;
auto p = v * (1 - s);
auto q = v * (1 - f * s);
auto t = v * (1 - (1 - f) * s);
auto r = 256.0;
auto g = 256.0;
auto b = 256.0;
switch (hInt) {
case 0:
r = v;
g = t;
b = p;
break;
case 1:
r = q;
g = v;
b = p;
break;
case 2:
r = p;
g = v;
b = t;
break;
case 3:
r = p;
g = q;
b = v;
break;
case 4:
r = t;
g = p;
b = v;
break;
case 5:
r = v;
g = p;
b = q;
break;
}
auto c = Color(static_cast<Uint8>(floor(r * 255.0)),
static_cast<Uint8>(floor(g * 255.0)),
static_cast<Uint8>(floor(b * 255.0)),
255);
return c;
}
}; // namespace RandomColorGenerator