1 // Copyright © 2008-2021 Pioneer Developers. See AUTHORS.txt for details 2 // Licensed under the terms of the GPL v3. See licenses/GPL-3.txt 3 4 #include "RandomColor.h" 5 #include "libs.h" 6 #include "utils.h" 7 #include <algorithm> 8 9 namespace RandomColorGenerator { 10 //static 11 std::map<ColorScheme, RandomColor::DefinedColor> RandomColor::ColorDictionary; 12 static Random *pRNG = nullptr; 13 RandomColor()14 RandomColor::RandomColor() 15 { 16 // Populate the color dictionary 17 LoadColorBounds(); 18 } 19 20 /// Gets a new random color. 21 /// <param name="scheme">Which color schemed to use when generating the color.</param> 22 /// <param name="luminosity">The desired luminosity of the color.</param> 23 //static GetColor(ColorScheme scheme,Luminosity luminosity)24 Color RandomColor::GetColor(ColorScheme scheme, Luminosity luminosity) 25 { 26 int H, S, B; 27 28 // First we pick a hue (H) 29 H = PickHue(scheme); 30 31 // Then use H to determine saturation (S) 32 S = PickSaturation(H, luminosity, scheme); 33 34 // Then use S and H to determine brightness (B). 35 B = PickBrightness(H, S, luminosity); 36 37 // Then we return the HSB color in the desired format 38 return HsvToColor(H, S, B); 39 } 40 41 /// Generates multiple random colors. 42 /// <param name="scheme">Which color scheme to use when generating the color.</param> 43 /// <param name="luminosity">The desired luminosity of the color.</param> 44 /// <param name="count">How many colors to generate</param> 45 //static GetColors(Random & rand,ColorScheme scheme,Luminosity luminosity,int count)46 std::vector<Color> RandomColor::GetColors(Random &rand, ColorScheme scheme, Luminosity luminosity, int count) 47 { 48 pRNG = &rand; 49 std::vector<Color> ret; 50 ret.resize(count); 51 for (int i = 0; i < count; i++) { 52 ret[i] = GetColor(scheme, luminosity); 53 } 54 pRNG = nullptr; 55 return ret; 56 } 57 58 //static PickHue(ColorScheme scheme)59 int RandomColor::PickHue(ColorScheme scheme) 60 { 61 Range hueRange(GetHueRange(scheme)); 62 int hue = RandomWithin(hueRange); 63 64 // Instead of storing red as two separate ranges, 65 // we group them, using negative numbers 66 if (hue < 0) hue = 360 + hue; 67 68 return hue; 69 } 70 71 //static PickSaturation(int hue,Luminosity luminosity,ColorScheme scheme)72 int RandomColor::PickSaturation(int hue, Luminosity luminosity, ColorScheme scheme) 73 { 74 if (luminosity == Luminosity::LUMINOSITY_RANDOM) { 75 return RandomWithin(0, 100); 76 } 77 78 if (scheme == ColorScheme::SCHEME_MONOCHROME) { 79 return 0; 80 } 81 82 Range saturationRange = GetColorInfo(hue).SaturationRange; 83 84 int sMin = saturationRange.Lower; 85 int sMax = saturationRange.Upper; 86 87 switch (luminosity) { 88 case Luminosity::LUMINOSITY_BRIGHT: 89 sMin = 55; 90 break; 91 92 case Luminosity::LUMINOSITY_DARK: 93 sMin = sMax - 10; 94 break; 95 96 case Luminosity::LUMINOSITY_LIGHT: 97 sMax = 55; 98 break; 99 100 case Luminosity::LUMINOSITY_RANDOM: 101 // TODO: is this correct? just leave sMin/sMax? 102 break; 103 } 104 105 return RandomWithin(sMin, sMax); 106 } 107 108 //static PickBrightness(int H,int S,Luminosity luminosity)109 int RandomColor::PickBrightness(int H, int S, Luminosity luminosity) 110 { 111 auto bMin = GetMinimumBrightness(H, S); 112 auto bMax = 100; 113 114 switch (luminosity) { 115 case Luminosity::LUMINOSITY_DARK: 116 bMax = bMin + 20; 117 break; 118 119 case Luminosity::LUMINOSITY_LIGHT: 120 bMin = (bMax + bMin) / 2; 121 break; 122 123 case Luminosity::LUMINOSITY_RANDOM: 124 bMin = 0; 125 bMax = 100; 126 break; 127 128 case Luminosity::LUMINOSITY_BRIGHT: 129 // TODO: is this correct? just leave min/max? 130 break; 131 } 132 133 return RandomWithin(bMin, bMax); 134 } 135 136 //static GetMinimumBrightness(int H,int S)137 int RandomColor::GetMinimumBrightness(int H, int S) 138 { 139 auto lowerBounds = GetColorInfo(H).LowerBounds; 140 141 for (size_t i = 0; i < std::min(lowerBounds.size(), lowerBounds.size() - 1U); i++) { 142 auto s1 = lowerBounds[i].x; 143 auto v1 = lowerBounds[i].y; 144 145 auto s2 = lowerBounds[i + 1].x; 146 auto v2 = lowerBounds[i + 1].y; 147 148 if (S >= s1 && S <= s2) { 149 auto m = (v2 - v1) / (s2 - s1); 150 auto b = v1 - m * s1; 151 152 return static_cast<int>(m * S + b); 153 } 154 } 155 156 return 0; 157 } 158 159 //static GetHueRange(ColorScheme colorInput)160 Range RandomColor::GetHueRange(ColorScheme colorInput) 161 { 162 std::map<ColorScheme, DefinedColor>::iterator out = ColorDictionary.find(colorInput); 163 if (out != ColorDictionary.end()) { 164 return out->second.HueRange; 165 } 166 167 return Range(0, 360); 168 } 169 170 //static GetColorInfo(int hue)171 RandomColor::DefinedColor RandomColor::GetColorInfo(int hue) 172 { 173 // Maps red colors to make picking hue easier 174 if (hue >= 334 && hue <= 360) { 175 hue -= 360; 176 } 177 178 for (auto c : ColorDictionary) { 179 if (hue >= c.second.HueRange[0] && hue <= c.second.HueRange[1]) { 180 return c.second; 181 } 182 } 183 184 return DefinedColor(); 185 } 186 187 //static RandomWithin(Range range)188 int RandomColor::RandomWithin(Range range) 189 { 190 return RandomWithin(range.Lower, range.Upper); 191 } 192 //static RandomWithin(int lower,int upper)193 int RandomColor::RandomWithin(int lower, int upper) 194 { 195 assert(pRNG); 196 return pRNG->Int32(lower, upper + 1); 197 } 198 199 //static DefineColor(ColorScheme scheme,Point hueRange,const Point * lowerBounds,const size_t lbCount)200 void RandomColor::DefineColor(ColorScheme scheme, Point hueRange, const Point *lowerBounds, const size_t lbCount) 201 { 202 auto sMin = lowerBounds[0].x; 203 auto sMax = lowerBounds[lbCount - 1].x; 204 auto bMin = lowerBounds[lbCount - 1].y; 205 auto bMax = lowerBounds[0].y; 206 207 DefinedColor defCol; 208 defCol.HueRange = Range(hueRange.x, hueRange.y); 209 for (size_t lb = 0; lb < lbCount; lb++) { 210 defCol.LowerBounds.push_back(lowerBounds[lb]); 211 } 212 defCol.SaturationRange = Range(sMin, sMax); 213 defCol.BrightnessRange = Range(bMin, bMax); 214 215 ColorDictionary[scheme] = defCol; 216 } 217 218 //static LoadColorBounds()219 void RandomColor::LoadColorBounds() 220 { 221 const Point mono[] = { { 0, 0 }, { 100, 0 } }; 222 DefineColor( 223 ColorScheme::SCHEME_MONOCHROME, 224 Point(0, 360), 225 mono, 226 COUNTOF(mono)); 227 228 Point red[] = { { 20, 100 }, { 30, 92 }, { 40, 89 }, { 50, 85 }, { 60, 78 }, { 70, 70 }, { 80, 60 }, { 90, 55 }, { 100, 50 } }; 229 DefineColor( 230 ColorScheme::SCHEME_RED, 231 Point(-26, 18), 232 red, 233 COUNTOF(red)); 234 235 Point orange[] = { { 20, 100 }, { 30, 93 }, { 40, 88 }, { 50, 86 }, { 60, 85 }, { 70, 70 }, { 100, 70 } }; 236 DefineColor( 237 ColorScheme::SCHEME_ORANGE, 238 Point(19, 46), 239 orange, 240 COUNTOF(orange)); 241 242 Point yellow[] = { { 25, 100 }, { 40, 94 }, { 50, 89 }, { 60, 86 }, { 70, 84 }, { 80, 82 }, { 90, 80 }, { 100, 75 } }; 243 DefineColor( 244 ColorScheme::SCHEME_YELLOW, 245 Point(47, 62), 246 yellow, 247 COUNTOF(yellow)); 248 249 Point green[] = { { 30, 100 }, { 40, 90 }, { 50, 85 }, { 60, 81 }, { 70, 74 }, { 80, 64 }, { 90, 50 }, { 100, 40 } }; 250 DefineColor( 251 ColorScheme::SCHEME_GREEN, 252 Point(63, 178), 253 green, 254 COUNTOF(green)); 255 256 Point blue[] = { { 20, 100 }, { 30, 86 }, { 40, 80 }, { 50, 74 }, { 60, 60 }, { 70, 52 }, { 80, 44 }, { 90, 39 }, { 100, 35 } }; 257 DefineColor( 258 ColorScheme::SCHEME_BLUE, 259 Point(179, 257), 260 blue, 261 COUNTOF(blue)); 262 263 Point purple[] = { { 20, 100 }, { 30, 87 }, { 40, 79 }, { 50, 70 }, { 60, 65 }, { 70, 59 }, { 80, 52 }, { 90, 45 }, { 100, 42 } }; 264 DefineColor( 265 ColorScheme::SCHEME_PURPLE, 266 Point(258, 282), 267 purple, 268 COUNTOF(purple)); 269 270 Point pink[] = { { 20, 100 }, { 30, 90 }, { 40, 86 }, { 60, 84 }, { 80, 80 }, { 90, 75 }, { 100, 73 } }; 271 DefineColor( 272 ColorScheme::SCHEME_PINK, 273 Point(283, 334), 274 pink, 275 COUNTOF(pink)); 276 } 277 278 /// Converts hue, saturation, and lightness to a color. 279 //static HsvToColor(int hue,int saturation,double value)280 Color RandomColor::HsvToColor(int hue, int saturation, double value) 281 { 282 // this doesn't work for the values of 0 and 360 283 // here's the hacky fix 284 auto h = double(hue); 285 if (is_equal_exact(h, 0.0)) { 286 h = 1.0; 287 } 288 if (is_equal_exact(h, 360.0)) { 289 h = 359.0; 290 } 291 292 // Rebase the h,s,v values 293 h = h / 360.0; 294 auto s = saturation / 100.0; 295 auto v = value / 100.0; 296 297 auto hInt = static_cast<int>(floor(h * 6.0)); 298 auto f = h * 6 - hInt; 299 auto p = v * (1 - s); 300 auto q = v * (1 - f * s); 301 auto t = v * (1 - (1 - f) * s); 302 auto r = 256.0; 303 auto g = 256.0; 304 auto b = 256.0; 305 306 switch (hInt) { 307 case 0: 308 r = v; 309 g = t; 310 b = p; 311 break; 312 case 1: 313 r = q; 314 g = v; 315 b = p; 316 break; 317 case 2: 318 r = p; 319 g = v; 320 b = t; 321 break; 322 case 3: 323 r = p; 324 g = q; 325 b = v; 326 break; 327 case 4: 328 r = t; 329 g = p; 330 b = v; 331 break; 332 case 5: 333 r = v; 334 g = p; 335 b = q; 336 break; 337 } 338 auto c = Color(static_cast<Uint8>(floor(r * 255.0)), 339 static_cast<Uint8>(floor(g * 255.0)), 340 static_cast<Uint8>(floor(b * 255.0)), 341 255); 342 343 return c; 344 } 345 } // namespace RandomColorGenerator 346