From d843a7ab97a0310d9458c4ed11546bb2da18f8f9 Mon Sep 17 00:00:00 2001 From: Raul Santos Date: Tue, 6 Dec 2022 04:58:16 +0100 Subject: C#: Synchronize Color with Core - Add `Luminance` readonly property. - Add `LinearToSrgb` and `SrgbToLinear` static methods. - Add `FromOkHsl` static method. - Add `FromRgbe9995` static method. - Add `FromString` static method. - Expose `FromHtml` static method. - Expose `HtmlIsValid` static method. - Add and update some Color documentation. --- doc/classes/Color.xml | 30 ++-- .../mono/glue/GodotSharp/GodotSharp/Core/Color.cs | 167 +++++++++++++++++++-- .../GodotSharp/Core/NativeInterop/NativeFuncs.cs | 2 + modules/mono/glue/runtime_interop.cpp | 8 + 4 files changed, 183 insertions(+), 24 deletions(-) diff --git a/doc/classes/Color.xml b/doc/classes/Color.xml index 4d78433915..d1387d088d 100644 --- a/doc/classes/Color.xml +++ b/doc/classes/Color.xml @@ -205,11 +205,18 @@ Returns the [Color] associated with the provided [param hex] integer in 32-bit ARGB format (8 bits per channel, alpha channel first). In GDScript and C#, the [int] is best visualized with hexadecimal notation ([code]"0x"[/code] prefix). - [codeblock] + [codeblocks] + [gdscript] var red = Color.hex(0xffff0000) var dark_cyan = Color.hex(0xff008b8b) var my_color = Color.hex(0xa4bbefd2) - [/codeblock] + [/gdscript] + [csharp] + var red = new Color(0xffff0000); + var dark_cyan = new Color(0xff008b8b); + var my_color = new Color(0xa4bbefd2); + [/csharp] + [/codeblocks] @@ -234,9 +241,9 @@ var col = Color.html("663399cc") # col is Color(0.4, 0.2, 0.6, 0.8) [/gdscript] [csharp] - var blue = new Color("#0000ff"); // blue is Color(0.0, 0.0, 1.0, 1.0) - var green = new Color("#0F0"); // green is Color(0.0, 1.0, 0.0, 1.0) - var col = new Color("663399cc"); // col is Color(0.4, 0.2, 0.6, 0.8) + var blue = Color.FromHtml("#0000ff"); // blue is Color(0.0, 0.0, 1.0, 1.0) + var green = Color.FromHtml("#0F0"); // green is Color(0.0, 1.0, 0.0, 1.0) + var col = Color.FromHtml("663399cc"); // col is Color(0.4, 0.2, 0.6, 0.8) [/csharp] [/codeblocks] @@ -257,14 +264,13 @@ Color.html_is_valid("#55aaFF5") # Returns false [/gdscript] [csharp] - // This method is not available in C#. Use `StringExtensions.IsValidHtmlColor()`, instead. - "#55AAFF".IsValidHtmlColor(); // Returns true - "#55AAFF20".IsValidHtmlColor(); // Returns true - "55AAFF".IsValidHtmlColor(); // Returns true - "#F2C".IsValidHtmlColor(); // Returns true + Color.IsHtmlValid("#55AAFF"); // Returns true + Color.IsHtmlValid("#55AAFF20"); // Returns true + Color.IsHtmlValid("55AAFF"); // Returns true + Color.IsHtmlValid("#F2C"); // Returns true - "#AABBC".IsValidHtmlColor(); // Returns false - "#55aaFF5".IsValidHtmlColor(); // Returns false + Color.IsHtmlValid("#AABBC"); // Returns false + Color.IsHtmlValid("#55aaFF5"); // Returns false [/csharp] [/codeblocks] diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Color.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Color.cs index f49023555b..4075a878d2 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Color.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Color.cs @@ -1,5 +1,6 @@ using System; using System.Runtime.InteropServices; +using Godot.NativeInterop; namespace Godot { @@ -186,6 +187,19 @@ namespace Godot } } + /// + /// Returns the light intensity of the color, as a value between 0.0 and 1.0 (inclusive). + /// This is useful when determining light or dark color. Colors with a luminance smaller + /// than 0.5 can be generally considered dark. + /// Note: relies on the color being in the linear color space to + /// return an accurate relative luminance value. If the color is in the sRGB color space + /// use to convert it to the linear color space first. + /// + public readonly float Luminance + { + get { return 0.2126f * r + 0.7152f * g + 0.0722f * b; } + } + /// /// Access color components using their index. /// @@ -362,6 +376,35 @@ namespace Godot ); } + /// + /// Returns the color converted to the sRGB color space. + /// This method assumes the original color is in the linear color space. + /// See also which performs the opposite operation. + /// + /// The sRGB color. + public readonly Color LinearToSrgb() + { + return new Color( + r < 0.0031308f ? 12.92f * r : (1.0f + 0.055f) * (float)Mathf.Pow(r, 1.0f / 2.4f) - 0.055f, + g < 0.0031308f ? 12.92f * g : (1.0f + 0.055f) * (float)Mathf.Pow(g, 1.0f / 2.4f) - 0.055f, + b < 0.0031308f ? 12.92f * b : (1.0f + 0.055f) * (float)Mathf.Pow(b, 1.0f / 2.4f) - 0.055f, a); + } + + /// + /// Returns the color converted to linear color space. + /// This method assumes the original color already is in sRGB color space. + /// See also which performs the opposite operation. + /// + /// The color in linear color space. + public readonly Color SrgbToLinear() + { + return new Color( + r < 0.04045f ? r * (1.0f / 12.92f) : (float)Mathf.Pow((r + 0.055f) * (float)(1.0 / (1.0 + 0.055)), 2.4f), + g < 0.04045f ? g * (1.0f / 12.92f) : (float)Mathf.Pow((g + 0.055f) * (float)(1.0 / (1.0 + 0.055)), 2.4f), + b < 0.04045f ? b * (1.0f / 12.92f) : (float)Mathf.Pow((b + 0.055f) * (float)(1.0 / (1.0 + 0.055)), 2.4f), + a); + } + /// /// Returns the color converted to an unsigned 32-bit integer in ABGR /// format (each byte represents a color channel). @@ -565,6 +608,10 @@ namespace Godot /// constants. /// /// The HTML color code or color name to construct from. + /// + /// A color cannot be inferred from the given . + /// It was invalid HTML and a color with that name was not found. + /// public Color(string code) { if (HtmlIsValid(code)) @@ -597,7 +644,7 @@ namespace Godot /// /// color code is invalid. /// - private static Color FromHTML(ReadOnlySpan rgba) + public static Color FromHTML(ReadOnlySpan rgba) { Color c; if (rgba.Length == 0) @@ -705,28 +752,59 @@ namespace Godot /// the constants defined in . /// /// The name of the color. + /// + /// A color with the given name is not found. + /// /// The constructed color. private static Color Named(string name) + { + if (!FindNamedColor(name, out Color color)) + { + throw new ArgumentOutOfRangeException($"Invalid Color Name: {name}"); + } + + return color; + } + + /// + /// Returns a color according to the standardized name, with the + /// specified alpha value. Supported color names are the same as + /// the constants defined in . + /// If a color with the given name is not found, it returns + /// . + /// + /// The name of the color. + /// + /// The default color to return when a color with the given name + /// is not found. + /// + /// The constructed color. + private static Color Named(string name, Color @default) + { + if (!FindNamedColor(name, out Color color)) + { + return @default; + } + + return color; + } + + private static bool FindNamedColor(string name, out Color color) { name = name.Replace(" ", string.Empty); name = name.Replace("-", string.Empty); name = name.Replace("_", string.Empty); name = name.Replace("'", string.Empty); name = name.Replace(".", string.Empty); - name = name.ToUpper(); - - if (!Colors.namedColors.ContainsKey(name)) - { - throw new ArgumentOutOfRangeException($"Invalid Color Name: {name}"); - } + name = name.ToUpperInvariant(); - return Colors.namedColors[name]; + return Colors.namedColors.TryGetValue(name, out color); } /// - /// Constructs a color from an HSV profile, with values on the - /// range of 0 to 1. This is equivalent to using each of - /// the h/s/v properties, but much more efficient. + /// Constructs a color from an HSV profile. The , + /// , and are typically + /// between 0.0 and 1.0. /// /// The HSV hue, typically on the range of 0 to 1. /// The HSV saturation, typically on the range of 0 to 1. @@ -841,13 +919,78 @@ namespace Godot return ParseCol4(str, index) * 16 + ParseCol4(str, index + 1); } + /// + /// Constructs a color from an OK HSL profile. The , + /// , and are typically + /// between 0.0 and 1.0. + /// + /// The OK HSL hue, typically on the range of 0 to 1. + /// The OK HSL saturation, typically on the range of 0 to 1. + /// The OK HSL lightness, typically on the range of 0 to 1. + /// The alpha (transparency) value, typically on the range of 0 to 1. + /// The constructed color. + public static Color FromOkHsl(float hue, float saturation, float lightness, float alpha = 1.0f) + { + return NativeFuncs.godotsharp_color_from_ok_hsl(hue, saturation, lightness, alpha); + } + + /// + /// Encodes a from a RGBE9995 format integer. + /// See . + /// + /// The RGBE9995 encoded color. + /// The constructed color. + public static Color FromRgbe9995(uint rgbe) + { + float r = rgbe & 0x1ff; + float g = (rgbe >> 9) & 0x1ff; + float b = (rgbe >> 18) & 0x1ff; + float e = rgbe >> 27; + float m = (float)Mathf.Pow(2.0f, e - 15.0f - 9.0f); + + float rd = r * m; + float gd = g * m; + float bd = b * m; + + return new Color(rd, gd, bd, 1.0f); + } + + /// + /// Constructs a color from the given string, which can be either an HTML color + /// code or a named color. Returns if the color cannot + /// be inferred from the string. Supported color names are the same as the + /// constants. + /// + /// The HTML color code or color name. + /// The fallback color to return if the color cannot be inferred. + /// The constructed color. + public static Color FromString(string str, Color @default) + { + if (HtmlIsValid(str)) + { + return FromHTML(str); + } + else + { + return Named(str, @default); + } + } + private static string ToHex32(float val) { byte b = (byte)Mathf.RoundToInt(Mathf.Clamp(val * 255, 0, 255)); return b.HexEncode(); } - internal static bool HtmlIsValid(ReadOnlySpan color) + /// + /// Returns if is a valid HTML hexadecimal + /// color string. The string must be a hexadecimal value (case-insensitive) of either 3, + /// 4, 6 or 8 digits, and may be prefixed by a hash sign (#). This method is + /// identical to . + /// + /// The HTML hexadecimal color string. + /// Whether or not the string was a valid HTML hexadecimal color string. + public static bool HtmlIsValid(ReadOnlySpan color) { if (color.IsEmpty) { diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs index c7deb6423b..57488bd586 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs @@ -153,6 +153,8 @@ namespace Godot.NativeInterop internal static partial void godotsharp_callable_call_deferred(in godot_callable p_callable, godot_variant** p_args, int p_arg_count); + internal static partial Color godotsharp_color_from_ok_hsl(float p_h, float p_s, float p_l, float p_alpha); + // GDNative functions // gdnative.h diff --git a/modules/mono/glue/runtime_interop.cpp b/modules/mono/glue/runtime_interop.cpp index 338e5a0147..a3280a0739 100644 --- a/modules/mono/glue/runtime_interop.cpp +++ b/modules/mono/glue/runtime_interop.cpp @@ -514,6 +514,13 @@ void godotsharp_callable_call_deferred(Callable *p_callable, const Variant **p_a p_callable->call_deferredp(p_args, p_arg_count); } +godot_color godotsharp_color_from_ok_hsl(float p_h, float p_s, float p_l, float p_alpha) { + godot_color ret; + Color *dest = (Color *)&ret; + memnew_placement(dest, Color(Color::from_ok_hsl(p_h, p_s, p_l, p_alpha))); + return ret; +} + // GDNative functions // gdnative.h @@ -1345,6 +1352,7 @@ static const void *unmanaged_callbacks[]{ (void *)godotsharp_callable_get_data_for_marshalling, (void *)godotsharp_callable_call, (void *)godotsharp_callable_call_deferred, + (void *)godotsharp_color_from_ok_hsl, (void *)godotsharp_method_bind_ptrcall, (void *)godotsharp_method_bind_call, (void *)godotsharp_variant_new_string_name, -- cgit v1.2.3