فهرست منبع

Merge pull request #40868 from aaronfranke/color-html-rgba

Change Color HTML conversion from ARGB to RGBA
Rémi Verschelde 5 سال پیش
والد
کامیت
eac32f082a
6فایلهای تغییر یافته به همراه127 افزوده شده و 170 حذف شده
  1. 52 78
      core/color.cpp
  2. 1 1
      core/color.h
  3. 14 6
      doc/classes/Color.xml
  4. 2 2
      editor/plugins/theme_editor_plugin.cpp
  5. 55 78
      modules/mono/glue/GodotSharp/GodotSharp/Core/Color.cs
  6. 3 5
      tests/test_color.h

+ 52 - 78
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;
-
-	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;
-		}
+static int _parse_col4(const String &p_str, int p_ofs) {
+	char character = p_str[p_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;
+}
 
-	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;
 }

+ 1 - 1
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;

+ 14 - 6
doc/classes/Color.xml

@@ -18,13 +18,21 @@
 			<argument index="0" name="from" type="String">
 			</argument>
 			<description>
-				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]
 			</description>
 		</method>
@@ -243,11 +251,11 @@
 			<argument index="0" name="with_alpha" type="bool" default="true">
 			</argument>
 			<description>
-				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]
 			</description>

+ 2 - 2
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).");

+ 55 - 78
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);
+            }
         }
 
         /// <summary>
@@ -751,45 +767,28 @@ namespace Godot
             value = max;
         }
 
-        private static int ParseCol8(string str, int ofs)
+        private static int ParseCol4(string str, int ofs)
         {
-            int ig = 0;
+            char character = str[ofs];
 
-            for (int i = 0; i < 2; i++)
+            if (character >= '0' && character <= '9')
             {
-                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 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;
+        private static int ParseCol8(string str, int ofs)
+        {
+            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;
         }
 

+ 3 - 5
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,