diff --git a/core/color.cpp b/core/color.cpp index 27a2d0af5cf..c85cd9100df 100644 --- a/core/color.cpp +++ b/core/color.cpp @@ -261,33 +261,21 @@ Color Color::from_rgbe9995(uint32_t p_rgbe) { return Color(rd, gd, bd, 1.0f); } -static float _parse_col(const String &p_str, int p_ofs) { - int ig = 0; +static int _parse_col4(const String &p_str, int p_ofs) { + char character = p_str[p_ofs]; - for (int i = 0; i < 2; i++) { - int c = p_str[i + p_ofs]; - int v = 0; - - if (c >= '0' && c <= '9') { - v = c - '0'; - } else if (c >= 'a' && c <= 'f') { - v = c - 'a'; - v += 10; - } else if (c >= 'A' && c <= 'F') { - v = c - 'A'; - v += 10; - } else { - return -1; - } - - if (i == 0) { - ig += v * 16; - } else { - ig += v; - } + if (character >= '0' && character <= '9') { + return character - '0'; + } else if (character >= 'a' && character <= 'f') { + return character + (10 - 'a'); + } else if (character >= 'A' && character <= 'F') { + return character + (10 - 'A'); } + return -1; +} - return ig; +static int _parse_col8(const String &p_str, int p_ofs) { + return _parse_col4(p_str, p_ofs) * 16 + _parse_col4(p_str, p_ofs + 1); } Color Color::inverted() const { @@ -302,49 +290,54 @@ Color Color::contrasted() const { return c; } -Color Color::html(const String &p_color) { - String color = p_color; +Color Color::html(const String &p_rgba) { + String color = p_rgba; if (color.length() == 0) { return Color(); } if (color[0] == '#') { - color = color.substr(1, color.length() - 1); - } - if (color.length() == 3 || color.length() == 4) { - String exp_color; - for (int i = 0; i < color.length(); i++) { - exp_color += color[i]; - exp_color += color[i]; - } - color = exp_color; + color = color.substr(1); } + // If enabled, use 1 hex digit per channel instead of 2. + // Other sizes aren't in the HTML/CSS spec but we could add them if desired. + bool is_shorthand = color.length() < 5; bool alpha = false; if (color.length() == 8) { alpha = true; } else if (color.length() == 6) { alpha = false; + } else if (color.length() == 4) { + alpha = true; + } else if (color.length() == 3) { + alpha = false; } else { - ERR_FAIL_V_MSG(Color(), "Invalid color code: " + p_color + "."); + ERR_FAIL_V_MSG(Color(), "Invalid color code: " + p_rgba + "."); } - int a = 255; - if (alpha) { - a = _parse_col(color, 0); - ERR_FAIL_COND_V_MSG(a < 0, Color(), "Invalid color code: " + p_color + "."); + float r, g, b, a = 1.0; + if (is_shorthand) { + r = _parse_col4(color, 0) / 15.0; + g = _parse_col4(color, 1) / 15.0; + b = _parse_col4(color, 2) / 15.0; + if (alpha) { + a = _parse_col4(color, 3) / 15.0; + } + } else { + r = _parse_col8(color, 0) / 255.0; + g = _parse_col8(color, 2) / 255.0; + b = _parse_col8(color, 4) / 255.0; + if (alpha) { + a = _parse_col8(color, 6) / 255.0; + } } + ERR_FAIL_COND_V_MSG(r < 0, Color(), "Invalid color code: " + p_rgba + "."); + ERR_FAIL_COND_V_MSG(g < 0, Color(), "Invalid color code: " + p_rgba + "."); + ERR_FAIL_COND_V_MSG(b < 0, Color(), "Invalid color code: " + p_rgba + "."); + ERR_FAIL_COND_V_MSG(a < 0, Color(), "Invalid color code: " + p_rgba + "."); - int from = alpha ? 2 : 0; - - int r = _parse_col(color, from + 0); - ERR_FAIL_COND_V_MSG(r < 0, Color(), "Invalid color code: " + p_color + "."); - int g = _parse_col(color, from + 2); - ERR_FAIL_COND_V_MSG(g < 0, Color(), "Invalid color code: " + p_color + "."); - int b = _parse_col(color, from + 4); - ERR_FAIL_COND_V_MSG(b < 0, Color(), "Invalid color code: " + p_color + "."); - - return Color(r / 255.0, g / 255.0, b / 255.0, a / 255.0); + return Color(r, g, b, a); } bool Color::html_is_valid(const String &p_color) { @@ -354,41 +347,22 @@ bool Color::html_is_valid(const String &p_color) { return false; } if (color[0] == '#') { - color = color.substr(1, color.length() - 1); + color = color.substr(1); } - bool alpha = false; - - if (color.length() == 8) { - alpha = true; - } else if (color.length() == 6) { - alpha = false; - } else { + // Check if the amount of hex digits is valid. + int len = color.length(); + if (!(len == 3 || len == 4 || len == 6 || len == 8)) { return false; } - if (alpha) { - int a = _parse_col(color, 0); - if (a < 0) { + // Check if each hex digit is valid. + for (int i = 0; i < len; i++) { + if (_parse_col4(color, i) == -1) { return false; } } - int from = alpha ? 2 : 0; - - int r = _parse_col(color, from + 0); - if (r < 0) { - return false; - } - int g = _parse_col(color, from + 2); - if (g < 0) { - return false; - } - int b = _parse_col(color, from + 4); - if (b < 0) { - return false; - } - return true; } @@ -438,7 +412,7 @@ String Color::to_html(bool p_alpha) const { txt += _to_hex(g); txt += _to_hex(b); if (p_alpha) { - txt = _to_hex(a) + txt; + txt += _to_hex(a); } return txt; } diff --git a/core/color.h b/core/color.h index 258965fd161..096e97e9aee 100644 --- a/core/color.h +++ b/core/color.h @@ -185,7 +185,7 @@ struct Color { static Color hex(uint32_t p_hex); static Color hex64(uint64_t p_hex); - static Color html(const String &p_color); + static Color html(const String &p_rgba); static bool html_is_valid(const String &p_color); static Color named(const String &p_name); String to_html(bool p_alpha = true) const; diff --git a/doc/classes/Color.xml b/doc/classes/Color.xml index 240e70db9fd..fcaba99eb7d 100644 --- a/doc/classes/Color.xml +++ b/doc/classes/Color.xml @@ -18,13 +18,21 @@ - Constructs a color from an HTML hexadecimal color string in ARGB or RGB format. See also [method @GDScript.ColorN]. + Constructs a color from an HTML hexadecimal color string in RGB or RGBA format. See also [method @GDScript.ColorN]. [codeblock] # Each of the following creates the same color RGBA(178, 217, 10, 255). - var c1 = Color("#ffb2d90a") # ARGB format with "#". - var c2 = Color("ffb2d90a") # ARGB format. var c3 = Color("#b2d90a") # RGB format with "#". var c4 = Color("b2d90a") # RGB format. + var c1 = Color("#b2d90aff") # RGBA format with "#". + var c2 = Color("b2d90aff") # RGBA format. + [/codeblock] + You can also use the "web color" short-hand form by only using 3 or 4 digits. + [codeblock] + # Each of the following creates the same color RGBA(17, 34, 51, 255). + var c3 = Color("#123") # RGB format with "#". + var c4 = Color("123") # RGB format. + var c1 = Color("#123f") # RGBA format with "#". + var c2 = Color("123f") # RGBA format. [/codeblock] @@ -243,11 +251,11 @@ - Returns the color's HTML hexadecimal color string in ARGB format (ex: [code]ff34f822[/code]). - Setting [code]with_alpha[/code] to [code]false[/code] excludes alpha from the hexadecimal string. + Returns the color's HTML hexadecimal color string in RGBA format (ex: [code]ff34f822[/code]). + Setting [code]with_alpha[/code] to [code]false[/code] excludes alpha from the hexadecimal string (and uses RGB instead of RGBA format). [codeblock] var c = Color(1, 1, 1, 0.5) - var s1 = c.to_html() # Returns "7fffffff" + var s1 = c.to_html() # Returns "ffffff7f" var s2 = c.to_html(false) # Returns "ffffff" [/codeblock] diff --git a/editor/plugins/theme_editor_plugin.cpp b/editor/plugins/theme_editor_plugin.cpp index 76dd6e38e0c..932ded69388 100644 --- a/editor/plugins/theme_editor_plugin.cpp +++ b/editor/plugins/theme_editor_plugin.cpp @@ -206,8 +206,8 @@ void ThemeEditor::_save_template_cbk(String fname) { file->store_line("; [value] examples:"); file->store_line("; "); file->store_line("; Type.item = 6 ; numeric constant. "); - file->store_line("; Type.item = #FF00FF ; HTML color "); - file->store_line("; Type.item = #55FF00FF ; HTML color with alpha 55."); + file->store_line("; Type.item = #FF00FF ; HTML color (magenta)."); + file->store_line("; Type.item = #FF00FF55 ; HTML color (magenta with alpha 0x55)."); file->store_line("; Type.item = icon(image.png) ; icon in a png file (relative to theme file)."); file->store_line("; Type.item = font(font.xres) ; font in a resource (relative to theme file)."); file->store_line("; Type.item = sbox(stylebox.xres) ; stylebox in a resource (relative to theme file)."); diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Color.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Color.cs index d851abc6d37..3700a6194f2 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Color.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Color.cs @@ -565,6 +565,9 @@ namespace Godot rgba = rgba.Substring(1); } + // If enabled, use 1 hex digit per channel instead of 2. + // Other sizes aren't in the HTML/CSS spec but we could add them if desired. + bool isShorthand = rgba.Length < 5; bool alpha; if (rgba.Length == 8) @@ -575,47 +578,60 @@ namespace Godot { alpha = false; } + else if (rgba.Length == 4) + { + alpha = true; + } + else if (rgba.Length == 3) + { + alpha = false; + } else { throw new ArgumentOutOfRangeException("Invalid color code. Length is " + rgba.Length + " but a length of 6 or 8 is expected: " + rgba); } - if (alpha) + a = 1.0f; + if (isShorthand) { - a = ParseCol8(rgba, 6) / 255f; - - if (a < 0) + r = ParseCol4(rgba, 0) / 15f; + g = ParseCol4(rgba, 1) / 15f; + b = ParseCol4(rgba, 2) / 15f; + if (alpha) { - throw new ArgumentOutOfRangeException("Invalid color code. Alpha part is not valid hexadecimal: " + rgba); + a = ParseCol4(rgba, 3) / 15f; } } else { - a = 1.0f; + r = ParseCol8(rgba, 0) / 255f; + g = ParseCol8(rgba, 2) / 255f; + b = ParseCol8(rgba, 4) / 255f; + if (alpha) + { + a = ParseCol8(rgba, 6) / 255f; + } } - int from = alpha ? 2 : 0; - - r = ParseCol8(rgba, 0) / 255f; - if (r < 0) { throw new ArgumentOutOfRangeException("Invalid color code. Red part is not valid hexadecimal: " + rgba); } - g = ParseCol8(rgba, 2) / 255f; - if (g < 0) { throw new ArgumentOutOfRangeException("Invalid color code. Green part is not valid hexadecimal: " + rgba); } - b = ParseCol8(rgba, 4) / 255f; - if (b < 0) { throw new ArgumentOutOfRangeException("Invalid color code. Blue part is not valid hexadecimal: " + rgba); } + + if (a < 0) + { + throw new ArgumentOutOfRangeException("Invalid color code. Alpha part is not valid hexadecimal: " + rgba); + } } /// @@ -751,45 +767,28 @@ namespace Godot value = max; } + private static int ParseCol4(string str, int ofs) + { + char character = str[ofs]; + + if (character >= '0' && character <= '9') + { + return character - '0'; + } + else if (character >= 'a' && character <= 'f') + { + return character + (10 - 'a'); + } + else if (character >= 'A' && character <= 'F') + { + return character + (10 - 'A'); + } + return -1; + } + private static int ParseCol8(string str, int ofs) { - int ig = 0; - - for (int i = 0; i < 2; i++) - { - int c = str[i + ofs]; - int v; - - if (c >= '0' && c <= '9') - { - v = c - '0'; - } - else if (c >= 'a' && c <= 'f') - { - v = c - 'a'; - v += 10; - } - else if (c >= 'A' && c <= 'F') - { - v = c - 'A'; - v += 10; - } - else - { - return -1; - } - - if (i == 0) - { - ig += v * 16; - } - else - { - ig += v; - } - } - - return ig; + return ParseCol4(str, ofs) * 16 + ParseCol4(str, ofs + 1); } private String ToHex32(float val) @@ -828,46 +827,24 @@ namespace Godot if (color[0] == '#') { - color = color.Substring(1, color.Length - 1); + color = color.Substring(1); } - bool alpha; - - switch (color.Length) + // Check if the amount of hex digits is valid. + int len = color.Length; + if (!(len == 3 || len == 4 || len == 6 || len == 8)) { - case 8: - alpha = true; - break; - case 6: - alpha = false; - break; - default: - return false; + return false; } - if (alpha) - { - if (ParseCol8(color, 0) < 0) + // Check if each hex digit is valid. + for (int i = 0; i < len; i++) { + if (ParseCol4(color, i) == -1) { return false; } } - int from = alpha ? 2 : 0; - - if (ParseCol8(color, from + 0) < 0) - { - return false; - } - if (ParseCol8(color, from + 2) < 0) - { - return false; - } - if (ParseCol8(color, from + 4) < 0) - { - return false; - } - return true; } diff --git a/tests/test_color.h b/tests/test_color.h index 3633f2746d7..dfdc29ec7de 100644 --- a/tests/test_color.h +++ b/tests/test_color.h @@ -39,9 +39,7 @@ namespace TestColor { TEST_CASE("[Color] Constructor methods") { const Color blue_rgba = Color(0.25098, 0.376471, 1, 0.501961); - // HTML currently uses ARGB notation, which is contrary to the CSS standard. - // This may be changed to RGBA in 4.0. - const Color blue_html = Color::html("#804060ff"); + const Color blue_html = Color::html("#4060ff80"); const Color blue_hex = Color::hex(0x4060ff80); const Color blue_hex64 = Color::hex64(0x4040'6060'ffff'8080); @@ -118,10 +116,10 @@ TEST_CASE("[Color] Conversion methods") { const Color cyan_transparent = Color(0, 1, 1, 0); CHECK_MESSAGE( - cyan.to_html() == "ff00ffff", + cyan.to_html() == "00ffffff", "The returned RGB HTML color code should match the expected value."); CHECK_MESSAGE( - cyan_transparent.to_html() == "0000ffff", + cyan_transparent.to_html() == "00ffff00", "The returned RGBA HTML color code should match the expected value."); CHECK_MESSAGE( cyan.to_argb32() == 0xff00ffff,