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