1 | /**************************************************************************/ |
2 | /* color.cpp */ |
3 | /**************************************************************************/ |
4 | /* This file is part of: */ |
5 | /* GODOT ENGINE */ |
6 | /* https://godotengine.org */ |
7 | /**************************************************************************/ |
8 | /* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ |
9 | /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ |
10 | /* */ |
11 | /* Permission is hereby granted, free of charge, to any person obtaining */ |
12 | /* a copy of this software and associated documentation files (the */ |
13 | /* "Software"), to deal in the Software without restriction, including */ |
14 | /* without limitation the rights to use, copy, modify, merge, publish, */ |
15 | /* distribute, sublicense, and/or sell copies of the Software, and to */ |
16 | /* permit persons to whom the Software is furnished to do so, subject to */ |
17 | /* the following conditions: */ |
18 | /* */ |
19 | /* The above copyright notice and this permission notice shall be */ |
20 | /* included in all copies or substantial portions of the Software. */ |
21 | /* */ |
22 | /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ |
23 | /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ |
24 | /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ |
25 | /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ |
26 | /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ |
27 | /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ |
28 | /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ |
29 | /**************************************************************************/ |
30 | |
31 | #include "color.h" |
32 | |
33 | #include "color_names.inc" |
34 | #include "core/math/math_funcs.h" |
35 | #include "core/string/ustring.h" |
36 | #include "core/templates/rb_map.h" |
37 | |
38 | #include "thirdparty/misc/ok_color.h" |
39 | |
40 | uint32_t Color::to_argb32() const { |
41 | uint32_t c = (uint8_t)Math::round(a * 255.0f); |
42 | c <<= 8; |
43 | c |= (uint8_t)Math::round(r * 255.0f); |
44 | c <<= 8; |
45 | c |= (uint8_t)Math::round(g * 255.0f); |
46 | c <<= 8; |
47 | c |= (uint8_t)Math::round(b * 255.0f); |
48 | |
49 | return c; |
50 | } |
51 | |
52 | uint32_t Color::to_abgr32() const { |
53 | uint32_t c = (uint8_t)Math::round(a * 255.0f); |
54 | c <<= 8; |
55 | c |= (uint8_t)Math::round(b * 255.0f); |
56 | c <<= 8; |
57 | c |= (uint8_t)Math::round(g * 255.0f); |
58 | c <<= 8; |
59 | c |= (uint8_t)Math::round(r * 255.0f); |
60 | |
61 | return c; |
62 | } |
63 | |
64 | uint32_t Color::to_rgba32() const { |
65 | uint32_t c = (uint8_t)Math::round(r * 255.0f); |
66 | c <<= 8; |
67 | c |= (uint8_t)Math::round(g * 255.0f); |
68 | c <<= 8; |
69 | c |= (uint8_t)Math::round(b * 255.0f); |
70 | c <<= 8; |
71 | c |= (uint8_t)Math::round(a * 255.0f); |
72 | |
73 | return c; |
74 | } |
75 | |
76 | uint64_t Color::to_abgr64() const { |
77 | uint64_t c = (uint16_t)Math::round(a * 65535.0f); |
78 | c <<= 16; |
79 | c |= (uint16_t)Math::round(b * 65535.0f); |
80 | c <<= 16; |
81 | c |= (uint16_t)Math::round(g * 65535.0f); |
82 | c <<= 16; |
83 | c |= (uint16_t)Math::round(r * 65535.0f); |
84 | |
85 | return c; |
86 | } |
87 | |
88 | uint64_t Color::to_argb64() const { |
89 | uint64_t c = (uint16_t)Math::round(a * 65535.0f); |
90 | c <<= 16; |
91 | c |= (uint16_t)Math::round(r * 65535.0f); |
92 | c <<= 16; |
93 | c |= (uint16_t)Math::round(g * 65535.0f); |
94 | c <<= 16; |
95 | c |= (uint16_t)Math::round(b * 65535.0f); |
96 | |
97 | return c; |
98 | } |
99 | |
100 | uint64_t Color::to_rgba64() const { |
101 | uint64_t c = (uint16_t)Math::round(r * 65535.0f); |
102 | c <<= 16; |
103 | c |= (uint16_t)Math::round(g * 65535.0f); |
104 | c <<= 16; |
105 | c |= (uint16_t)Math::round(b * 65535.0f); |
106 | c <<= 16; |
107 | c |= (uint16_t)Math::round(a * 65535.0f); |
108 | |
109 | return c; |
110 | } |
111 | |
112 | String _to_hex(float p_val) { |
113 | int v = Math::round(p_val * 255.0f); |
114 | v = CLAMP(v, 0, 255); |
115 | String ret; |
116 | |
117 | for (int i = 0; i < 2; i++) { |
118 | char32_t c[2] = { 0, 0 }; |
119 | int lv = v & 0xF; |
120 | if (lv < 10) { |
121 | c[0] = '0' + lv; |
122 | } else { |
123 | c[0] = 'a' + lv - 10; |
124 | } |
125 | |
126 | v >>= 4; |
127 | String cs = (const char32_t *)c; |
128 | ret = cs + ret; |
129 | } |
130 | |
131 | return ret; |
132 | } |
133 | |
134 | String Color::to_html(bool p_alpha) const { |
135 | String txt; |
136 | txt += _to_hex(r); |
137 | txt += _to_hex(g); |
138 | txt += _to_hex(b); |
139 | if (p_alpha) { |
140 | txt += _to_hex(a); |
141 | } |
142 | return txt; |
143 | } |
144 | |
145 | float Color::get_h() const { |
146 | float min = MIN(r, g); |
147 | min = MIN(min, b); |
148 | float max = MAX(r, g); |
149 | max = MAX(max, b); |
150 | |
151 | float delta = max - min; |
152 | |
153 | if (delta == 0.0f) { |
154 | return 0.0f; |
155 | } |
156 | |
157 | float h; |
158 | if (r == max) { |
159 | h = (g - b) / delta; // between yellow & magenta |
160 | } else if (g == max) { |
161 | h = 2 + (b - r) / delta; // between cyan & yellow |
162 | } else { |
163 | h = 4 + (r - g) / delta; // between magenta & cyan |
164 | } |
165 | |
166 | h /= 6.0f; |
167 | if (h < 0.0f) { |
168 | h += 1.0f; |
169 | } |
170 | |
171 | return h; |
172 | } |
173 | |
174 | float Color::get_s() const { |
175 | float min = MIN(r, g); |
176 | min = MIN(min, b); |
177 | float max = MAX(r, g); |
178 | max = MAX(max, b); |
179 | |
180 | float delta = max - min; |
181 | |
182 | return (max != 0.0f) ? (delta / max) : 0.0f; |
183 | } |
184 | |
185 | float Color::get_v() const { |
186 | float max = MAX(r, g); |
187 | max = MAX(max, b); |
188 | return max; |
189 | } |
190 | |
191 | void Color::set_hsv(float p_h, float p_s, float p_v, float p_alpha) { |
192 | int i; |
193 | float f, p, q, t; |
194 | a = p_alpha; |
195 | |
196 | if (p_s == 0.0f) { |
197 | // Achromatic (gray) |
198 | r = g = b = p_v; |
199 | return; |
200 | } |
201 | |
202 | p_h *= 6.0f; |
203 | p_h = Math::fmod(p_h, 6); |
204 | i = Math::floor(p_h); |
205 | |
206 | f = p_h - i; |
207 | p = p_v * (1.0f - p_s); |
208 | q = p_v * (1.0f - p_s * f); |
209 | t = p_v * (1.0f - p_s * (1.0f - f)); |
210 | |
211 | switch (i) { |
212 | case 0: // Red is the dominant color |
213 | r = p_v; |
214 | g = t; |
215 | b = p; |
216 | break; |
217 | case 1: // Green is the dominant color |
218 | r = q; |
219 | g = p_v; |
220 | b = p; |
221 | break; |
222 | case 2: |
223 | r = p; |
224 | g = p_v; |
225 | b = t; |
226 | break; |
227 | case 3: // Blue is the dominant color |
228 | r = p; |
229 | g = q; |
230 | b = p_v; |
231 | break; |
232 | case 4: |
233 | r = t; |
234 | g = p; |
235 | b = p_v; |
236 | break; |
237 | default: // (5) Red is the dominant color |
238 | r = p_v; |
239 | g = p; |
240 | b = q; |
241 | break; |
242 | } |
243 | } |
244 | |
245 | void Color::set_ok_hsl(float p_h, float p_s, float p_l, float p_alpha) { |
246 | ok_color::HSL hsl; |
247 | hsl.h = p_h; |
248 | hsl.s = p_s; |
249 | hsl.l = p_l; |
250 | ok_color::RGB rgb = ok_color::okhsl_to_srgb(hsl); |
251 | Color c = Color(rgb.r, rgb.g, rgb.b, p_alpha).clamp(); |
252 | r = c.r; |
253 | g = c.g; |
254 | b = c.b; |
255 | a = c.a; |
256 | } |
257 | |
258 | bool Color::is_equal_approx(const Color &p_color) const { |
259 | return Math::is_equal_approx(r, p_color.r) && Math::is_equal_approx(g, p_color.g) && Math::is_equal_approx(b, p_color.b) && Math::is_equal_approx(a, p_color.a); |
260 | } |
261 | |
262 | Color Color::clamp(const Color &p_min, const Color &p_max) const { |
263 | return Color( |
264 | CLAMP(r, p_min.r, p_max.r), |
265 | CLAMP(g, p_min.g, p_max.g), |
266 | CLAMP(b, p_min.b, p_max.b), |
267 | CLAMP(a, p_min.a, p_max.a)); |
268 | } |
269 | |
270 | void Color::invert() { |
271 | r = 1.0f - r; |
272 | g = 1.0f - g; |
273 | b = 1.0f - b; |
274 | } |
275 | |
276 | Color Color::hex(uint32_t p_hex) { |
277 | float a = (p_hex & 0xFF) / 255.0f; |
278 | p_hex >>= 8; |
279 | float b = (p_hex & 0xFF) / 255.0f; |
280 | p_hex >>= 8; |
281 | float g = (p_hex & 0xFF) / 255.0f; |
282 | p_hex >>= 8; |
283 | float r = (p_hex & 0xFF) / 255.0f; |
284 | |
285 | return Color(r, g, b, a); |
286 | } |
287 | |
288 | Color Color::hex64(uint64_t p_hex) { |
289 | float a = (p_hex & 0xFFFF) / 65535.0f; |
290 | p_hex >>= 16; |
291 | float b = (p_hex & 0xFFFF) / 65535.0f; |
292 | p_hex >>= 16; |
293 | float g = (p_hex & 0xFFFF) / 65535.0f; |
294 | p_hex >>= 16; |
295 | float r = (p_hex & 0xFFFF) / 65535.0f; |
296 | |
297 | return Color(r, g, b, a); |
298 | } |
299 | |
300 | static int _parse_col4(const String &p_str, int p_ofs) { |
301 | char character = p_str[p_ofs]; |
302 | |
303 | if (character >= '0' && character <= '9') { |
304 | return character - '0'; |
305 | } else if (character >= 'a' && character <= 'f') { |
306 | return character + (10 - 'a'); |
307 | } else if (character >= 'A' && character <= 'F') { |
308 | return character + (10 - 'A'); |
309 | } |
310 | return -1; |
311 | } |
312 | |
313 | static int _parse_col8(const String &p_str, int p_ofs) { |
314 | return _parse_col4(p_str, p_ofs) * 16 + _parse_col4(p_str, p_ofs + 1); |
315 | } |
316 | |
317 | Color Color::inverted() const { |
318 | Color c = *this; |
319 | c.invert(); |
320 | return c; |
321 | } |
322 | |
323 | Color Color::html(const String &p_rgba) { |
324 | String color = p_rgba; |
325 | if (color.length() == 0) { |
326 | return Color(); |
327 | } |
328 | if (color[0] == '#') { |
329 | color = color.substr(1); |
330 | } |
331 | |
332 | // If enabled, use 1 hex digit per channel instead of 2. |
333 | // Other sizes aren't in the HTML/CSS spec but we could add them if desired. |
334 | bool is_shorthand = color.length() < 5; |
335 | bool alpha = false; |
336 | |
337 | if (color.length() == 8) { |
338 | alpha = true; |
339 | } else if (color.length() == 6) { |
340 | alpha = false; |
341 | } else if (color.length() == 4) { |
342 | alpha = true; |
343 | } else if (color.length() == 3) { |
344 | alpha = false; |
345 | } else { |
346 | ERR_FAIL_V_MSG(Color(), "Invalid color code: " + p_rgba + "." ); |
347 | } |
348 | |
349 | float r, g, b, a = 1.0f; |
350 | if (is_shorthand) { |
351 | r = _parse_col4(color, 0) / 15.0f; |
352 | g = _parse_col4(color, 1) / 15.0f; |
353 | b = _parse_col4(color, 2) / 15.0f; |
354 | if (alpha) { |
355 | a = _parse_col4(color, 3) / 15.0f; |
356 | } |
357 | } else { |
358 | r = _parse_col8(color, 0) / 255.0f; |
359 | g = _parse_col8(color, 2) / 255.0f; |
360 | b = _parse_col8(color, 4) / 255.0f; |
361 | if (alpha) { |
362 | a = _parse_col8(color, 6) / 255.0f; |
363 | } |
364 | } |
365 | ERR_FAIL_COND_V_MSG(r < 0.0f, Color(), "Invalid color code: " + p_rgba + "." ); |
366 | ERR_FAIL_COND_V_MSG(g < 0.0f, Color(), "Invalid color code: " + p_rgba + "." ); |
367 | ERR_FAIL_COND_V_MSG(b < 0.0f, Color(), "Invalid color code: " + p_rgba + "." ); |
368 | ERR_FAIL_COND_V_MSG(a < 0.0f, Color(), "Invalid color code: " + p_rgba + "." ); |
369 | |
370 | return Color(r, g, b, a); |
371 | } |
372 | |
373 | bool Color::html_is_valid(const String &p_color) { |
374 | String color = p_color; |
375 | |
376 | if (color.length() == 0) { |
377 | return false; |
378 | } |
379 | if (color[0] == '#') { |
380 | color = color.substr(1); |
381 | } |
382 | |
383 | // Check if the amount of hex digits is valid. |
384 | int len = color.length(); |
385 | if (!(len == 3 || len == 4 || len == 6 || len == 8)) { |
386 | return false; |
387 | } |
388 | |
389 | // Check if each hex digit is valid. |
390 | for (int i = 0; i < len; i++) { |
391 | if (_parse_col4(color, i) == -1) { |
392 | return false; |
393 | } |
394 | } |
395 | |
396 | return true; |
397 | } |
398 | |
399 | Color Color::named(const String &p_name) { |
400 | int idx = find_named_color(p_name); |
401 | if (idx == -1) { |
402 | ERR_FAIL_V_MSG(Color(), "Invalid color name: " + p_name + "." ); |
403 | } |
404 | return named_colors[idx].color; |
405 | } |
406 | |
407 | Color Color::named(const String &p_name, const Color &p_default) { |
408 | int idx = find_named_color(p_name); |
409 | if (idx == -1) { |
410 | return p_default; |
411 | } |
412 | return named_colors[idx].color; |
413 | } |
414 | |
415 | int Color::find_named_color(const String &p_name) { |
416 | String name = p_name; |
417 | // Normalize name |
418 | name = name.replace(" " , "" ); |
419 | name = name.replace("-" , "" ); |
420 | name = name.replace("_" , "" ); |
421 | name = name.replace("'" , "" ); |
422 | name = name.replace("." , "" ); |
423 | name = name.to_upper(); |
424 | |
425 | int idx = 0; |
426 | while (named_colors[idx].name != nullptr) { |
427 | if (name == String(named_colors[idx].name).replace("_" , "" )) { |
428 | return idx; |
429 | } |
430 | idx++; |
431 | } |
432 | |
433 | return -1; |
434 | } |
435 | |
436 | int Color::get_named_color_count() { |
437 | int idx = 0; |
438 | while (named_colors[idx].name != nullptr) { |
439 | idx++; |
440 | } |
441 | return idx; |
442 | } |
443 | |
444 | String Color::get_named_color_name(int p_idx) { |
445 | ERR_FAIL_INDEX_V(p_idx, get_named_color_count(), "" ); |
446 | return named_colors[p_idx].name; |
447 | } |
448 | |
449 | Color Color::get_named_color(int p_idx) { |
450 | ERR_FAIL_INDEX_V(p_idx, get_named_color_count(), Color()); |
451 | return named_colors[p_idx].color; |
452 | } |
453 | |
454 | // For a version that errors on invalid values instead of returning |
455 | // a default color, use the Color(String) constructor instead. |
456 | Color Color::from_string(const String &p_string, const Color &p_default) { |
457 | if (html_is_valid(p_string)) { |
458 | return html(p_string); |
459 | } else { |
460 | return named(p_string, p_default); |
461 | } |
462 | } |
463 | |
464 | Color Color::from_hsv(float p_h, float p_s, float p_v, float p_alpha) { |
465 | Color c; |
466 | c.set_hsv(p_h, p_s, p_v, p_alpha); |
467 | return c; |
468 | } |
469 | |
470 | Color Color::from_rgbe9995(uint32_t p_rgbe) { |
471 | float r = p_rgbe & 0x1ff; |
472 | float g = (p_rgbe >> 9) & 0x1ff; |
473 | float b = (p_rgbe >> 18) & 0x1ff; |
474 | float e = (p_rgbe >> 27); |
475 | float m = Math::pow(2.0f, e - 15.0f - 9.0f); |
476 | |
477 | float rd = r * m; |
478 | float gd = g * m; |
479 | float bd = b * m; |
480 | |
481 | return Color(rd, gd, bd, 1.0f); |
482 | } |
483 | |
484 | Color::operator String() const { |
485 | return "(" + String::num(r, 4) + ", " + String::num(g, 4) + ", " + String::num(b, 4) + ", " + String::num(a, 4) + ")" ; |
486 | } |
487 | |
488 | Color Color::operator+(const Color &p_color) const { |
489 | return Color( |
490 | r + p_color.r, |
491 | g + p_color.g, |
492 | b + p_color.b, |
493 | a + p_color.a); |
494 | } |
495 | |
496 | void Color::operator+=(const Color &p_color) { |
497 | r = r + p_color.r; |
498 | g = g + p_color.g; |
499 | b = b + p_color.b; |
500 | a = a + p_color.a; |
501 | } |
502 | |
503 | Color Color::operator-(const Color &p_color) const { |
504 | return Color( |
505 | r - p_color.r, |
506 | g - p_color.g, |
507 | b - p_color.b, |
508 | a - p_color.a); |
509 | } |
510 | |
511 | void Color::operator-=(const Color &p_color) { |
512 | r = r - p_color.r; |
513 | g = g - p_color.g; |
514 | b = b - p_color.b; |
515 | a = a - p_color.a; |
516 | } |
517 | |
518 | Color Color::operator*(const Color &p_color) const { |
519 | return Color( |
520 | r * p_color.r, |
521 | g * p_color.g, |
522 | b * p_color.b, |
523 | a * p_color.a); |
524 | } |
525 | |
526 | Color Color::operator*(float p_scalar) const { |
527 | return Color( |
528 | r * p_scalar, |
529 | g * p_scalar, |
530 | b * p_scalar, |
531 | a * p_scalar); |
532 | } |
533 | |
534 | void Color::operator*=(const Color &p_color) { |
535 | r = r * p_color.r; |
536 | g = g * p_color.g; |
537 | b = b * p_color.b; |
538 | a = a * p_color.a; |
539 | } |
540 | |
541 | void Color::operator*=(float p_scalar) { |
542 | r = r * p_scalar; |
543 | g = g * p_scalar; |
544 | b = b * p_scalar; |
545 | a = a * p_scalar; |
546 | } |
547 | |
548 | Color Color::operator/(const Color &p_color) const { |
549 | return Color( |
550 | r / p_color.r, |
551 | g / p_color.g, |
552 | b / p_color.b, |
553 | a / p_color.a); |
554 | } |
555 | |
556 | Color Color::operator/(float p_scalar) const { |
557 | return Color( |
558 | r / p_scalar, |
559 | g / p_scalar, |
560 | b / p_scalar, |
561 | a / p_scalar); |
562 | } |
563 | |
564 | void Color::operator/=(const Color &p_color) { |
565 | r = r / p_color.r; |
566 | g = g / p_color.g; |
567 | b = b / p_color.b; |
568 | a = a / p_color.a; |
569 | } |
570 | |
571 | void Color::operator/=(float p_scalar) { |
572 | r = r / p_scalar; |
573 | g = g / p_scalar; |
574 | b = b / p_scalar; |
575 | a = a / p_scalar; |
576 | } |
577 | |
578 | Color Color::operator-() const { |
579 | return Color( |
580 | 1.0f - r, |
581 | 1.0f - g, |
582 | 1.0f - b, |
583 | 1.0f - a); |
584 | } |
585 | |
586 | Color Color::from_ok_hsl(float p_h, float p_s, float p_l, float p_alpha) { |
587 | Color c; |
588 | c.set_ok_hsl(p_h, p_s, p_l, p_alpha); |
589 | return c; |
590 | } |
591 | |
592 | float Color::get_ok_hsl_h() const { |
593 | ok_color::RGB rgb; |
594 | rgb.r = r; |
595 | rgb.g = g; |
596 | rgb.b = b; |
597 | ok_color::HSL ok_hsl = ok_color::srgb_to_okhsl(rgb); |
598 | if (Math::is_nan(ok_hsl.h)) { |
599 | return 0.0f; |
600 | } |
601 | return CLAMP(ok_hsl.h, 0.0f, 1.0f); |
602 | } |
603 | |
604 | float Color::get_ok_hsl_s() const { |
605 | ok_color::RGB rgb; |
606 | rgb.r = r; |
607 | rgb.g = g; |
608 | rgb.b = b; |
609 | ok_color::HSL ok_hsl = ok_color::srgb_to_okhsl(rgb); |
610 | if (Math::is_nan(ok_hsl.s)) { |
611 | return 0.0f; |
612 | } |
613 | return CLAMP(ok_hsl.s, 0.0f, 1.0f); |
614 | } |
615 | |
616 | float Color::get_ok_hsl_l() const { |
617 | ok_color::RGB rgb; |
618 | rgb.r = r; |
619 | rgb.g = g; |
620 | rgb.b = b; |
621 | ok_color::HSL ok_hsl = ok_color::srgb_to_okhsl(rgb); |
622 | if (Math::is_nan(ok_hsl.l)) { |
623 | return 0.0f; |
624 | } |
625 | return CLAMP(ok_hsl.l, 0.0f, 1.0f); |
626 | } |
627 | |