diff --git a/core/math/color.cpp b/core/math/color.cpp index f4b89031578c..c99801c0484b 100644 --- a/core/math/color.cpp +++ b/core/math/color.cpp @@ -321,6 +321,20 @@ void Color::set_hsl(float p_h, float p_s, float p_l, float p_alpha) { } } +void Color::set_ok_hsv(float p_h, float p_s, float p_v, float p_alpha) { + ok_color::HSV hsv; + hsv.h = p_h; + hsv.s = p_s; + hsv.v = p_v; + ok_color new_ok_color; + ok_color::RGB rgb = new_ok_color.okhsv_to_srgb(hsv); + Color c = Color(rgb.r, rgb.g, rgb.b, p_alpha).clamp(); + r = c.r; + g = c.g; + b = c.b; + a = c.a; +} + void Color::set_ok_hsl(float p_h, float p_s, float p_l, float p_alpha) { ok_color::HSL hsl; hsl.h = p_h; @@ -669,13 +683,71 @@ Color Color::operator-() const { 1.0f - a); } +Color Color::from_ok_hsv(float p_h, float p_s, float p_v, float p_alpha) { + Color c; + c.set_ok_hsv(p_h, p_s, p_v, p_alpha); + return c; +} + Color Color::from_ok_hsl(float p_h, float p_s, float p_l, float p_alpha) { Color c; c.set_ok_hsl(p_h, p_s, p_l, p_alpha); return c; } +float Color::get_ok_hsv_h() const { + if (r == g && g == b) { + return 0.0f; + } + ok_color::RGB rgb; + rgb.r = r; + rgb.g = g; + rgb.b = b; + ok_color new_ok_color; + ok_color::HSV ok_hsv = new_ok_color.srgb_to_okhsv(rgb); + if (Math::is_nan(ok_hsv.h)) { + return 0.0f; + } + return CLAMP(ok_hsv.h, 0.0f, 1.0f); +} + +float Color::get_ok_hsv_s() const { + if (r == g && g == b) { + return 0.0f; + } + ok_color::RGB rgb; + rgb.r = r; + rgb.g = g; + rgb.b = b; + ok_color new_ok_color; + ok_color::HSV ok_hsv = new_ok_color.srgb_to_okhsv(rgb); + if (Math::is_nan(ok_hsv.s)) { + return 0.0f; + } + return CLAMP(ok_hsv.s, 0.0f, 1.0f); +} + +float Color::get_ok_hsv_v() const { + if (r == g && g == b) { + // Workaround for edge cases causing NaN. + return get_ok_hsl_l(); + } + ok_color::RGB rgb; + rgb.r = r; + rgb.g = g; + rgb.b = b; + ok_color new_ok_color; + ok_color::HSV ok_hsv = new_ok_color.srgb_to_okhsv(rgb); + if (Math::is_nan(ok_hsv.v)) { + return 0.0f; + } + return CLAMP(ok_hsv.v, 0.0f, 1.0f); +} + float Color::get_ok_hsl_h() const { + if (r == g && g == b) { + return 0.0f; + } ok_color::RGB rgb; rgb.r = r; rgb.g = g; @@ -689,6 +761,9 @@ float Color::get_ok_hsl_h() const { } float Color::get_ok_hsl_s() const { + if (r == g && g == b) { + return 0.0f; + } ok_color::RGB rgb; rgb.r = r; rgb.g = g; diff --git a/core/math/color.h b/core/math/color.h index 4a056335c1c4..20bb624336f1 100644 --- a/core/math/color.h +++ b/core/math/color.h @@ -61,6 +61,10 @@ struct _NO_DISCARD_ Color { float get_hsl_s() const; float get_hsl_l() const; void set_hsl(float p_h, float p_s, float p_l, float p_alpha = 1.0f); + float get_ok_hsv_h() const; + float get_ok_hsv_s() const; + float get_ok_hsv_v() const; + void set_ok_hsv(float p_h, float p_s, float p_v, float p_alpha = 1.0f); float get_ok_hsl_h() const; float get_ok_hsl_s() const; float get_ok_hsl_l() const; @@ -203,6 +207,7 @@ struct _NO_DISCARD_ Color { static Color from_string(const String &p_string, const Color &p_default); static Color from_hsv(float p_h, float p_s, float p_v, float p_alpha = 1.0f); static Color from_hsl(float p_h, float p_s, float p_l, float p_alpha = 1.0f); + static Color from_ok_hsv(float p_h, float p_s, float p_v, float p_alpha = 1.0f); static Color from_ok_hsl(float p_h, float p_s, float p_l, float p_alpha = 1.0f); static Color from_rgbe9995(uint32_t p_rgbe); @@ -225,6 +230,9 @@ struct _NO_DISCARD_ Color { _FORCE_INLINE_ void set_hsl_h(float p_h) { set_hsl(p_h, get_hsl_s(), get_hsl_l(), a); } _FORCE_INLINE_ void set_hsl_s(float p_s) { set_hsl(get_hsl_h(), p_s, get_hsl_l(), a); } _FORCE_INLINE_ void set_hsl_l(float p_l) { set_hsl(get_hsl_h(), get_hsl_s(), p_l, a); } + _FORCE_INLINE_ void set_ok_hsv_h(float p_h) { set_ok_hsv(p_h, get_ok_hsv_s(), get_ok_hsv_v(), a); } + _FORCE_INLINE_ void set_ok_hsv_s(float p_s) { set_ok_hsv(get_ok_hsv_h(), p_s, get_ok_hsv_v(), a); } + _FORCE_INLINE_ void set_ok_hsv_v(float p_v) { set_ok_hsv(get_ok_hsv_h(), get_ok_hsv_s(), p_v, a); } _FORCE_INLINE_ void set_ok_hsl_h(float p_h) { set_ok_hsl(p_h, get_ok_hsl_s(), get_ok_hsl_l(), a); } _FORCE_INLINE_ void set_ok_hsl_s(float p_s) { set_ok_hsl(get_ok_hsl_h(), p_s, get_ok_hsl_l(), a); } _FORCE_INLINE_ void set_ok_hsl_l(float p_l) { set_ok_hsl(get_ok_hsl_h(), get_ok_hsl_s(), p_l, a); } diff --git a/core/variant/variant_call.cpp b/core/variant/variant_call.cpp index e22970ef5c9b..7825c7968216 100644 --- a/core/variant/variant_call.cpp +++ b/core/variant/variant_call.cpp @@ -2003,6 +2003,7 @@ static void _register_variant_builtin_methods() { bind_static_method(Color, from_string, sarray("str", "default"), varray()); bind_static_method(Color, from_hsv, sarray("h", "s", "v", "alpha"), varray(1.0)); bind_static_method(Color, from_hsl, sarray("h", "s", "l", "alpha"), varray(1.0)); + bind_static_method(Color, from_ok_hsv, sarray("h", "s", "v", "alpha"), varray(1.0)); bind_static_method(Color, from_ok_hsl, sarray("h", "s", "l", "alpha"), varray(1.0)); bind_static_method(Color, from_rgbe9995, sarray("rgbe"), varray()); diff --git a/core/variant/variant_setget.cpp b/core/variant/variant_setget.cpp index ce035f5f7ac2..2369921e3b2e 100644 --- a/core/variant/variant_setget.cpp +++ b/core/variant/variant_setget.cpp @@ -143,6 +143,14 @@ void register_named_setters_getters() { REGISTER_MEMBER(Color, hsl_h); REGISTER_MEMBER(Color, hsl_s); REGISTER_MEMBER(Color, hsl_l); + + REGISTER_MEMBER(Color, ok_hsv_h); + REGISTER_MEMBER(Color, ok_hsv_s); + REGISTER_MEMBER(Color, ok_hsv_v); + + REGISTER_MEMBER(Color, ok_hsl_h); + REGISTER_MEMBER(Color, ok_hsl_s); + REGISTER_MEMBER(Color, ok_hsl_l); } void unregister_named_setters_getters() { diff --git a/core/variant/variant_setget.h b/core/variant/variant_setget.h index db6e2738172f..77c18ea11178 100644 --- a/core/variant/variant_setget.h +++ b/core/variant/variant_setget.h @@ -348,6 +348,10 @@ SETGET_NUMBER_STRUCT_FUNC(Color, double, hsl_h, set_hsl_h, get_hsl_h) SETGET_NUMBER_STRUCT_FUNC(Color, double, hsl_s, set_hsl_s, get_hsl_s) SETGET_NUMBER_STRUCT_FUNC(Color, double, hsl_l, set_hsl_l, get_hsl_l) +SETGET_NUMBER_STRUCT_FUNC(Color, double, ok_hsv_h, set_ok_hsv_h, get_ok_hsv_h) +SETGET_NUMBER_STRUCT_FUNC(Color, double, ok_hsv_s, set_ok_hsv_s, get_ok_hsv_s) +SETGET_NUMBER_STRUCT_FUNC(Color, double, ok_hsv_v, set_ok_hsv_v, get_ok_hsv_v) + SETGET_NUMBER_STRUCT_FUNC(Color, double, ok_hsl_h, set_ok_hsl_h, get_ok_hsl_h) SETGET_NUMBER_STRUCT_FUNC(Color, double, ok_hsl_s, set_ok_hsl_s, get_ok_hsl_s) SETGET_NUMBER_STRUCT_FUNC(Color, double, ok_hsl_l, set_ok_hsl_l, get_ok_hsl_l) diff --git a/doc/classes/Color.xml b/doc/classes/Color.xml index 0592f5287ba2..98eee5d9af42 100644 --- a/doc/classes/Color.xml +++ b/doc/classes/Color.xml @@ -195,6 +195,24 @@ [/codeblocks] + + + + + + + + Constructs a color from an [url=https://bottosson.github.io/posts/colorpicker/]OK HSV profile[/url]. The hue ([param h]), saturation ([param s]), and value ([param v]) are typically between 0.0 and 1.0. + [codeblocks] + [gdscript] + var color = Color.from_ok_hsv(0.58, 0.5, 0.79, 0.8) + [/gdscript] + [csharp] + var color = Color.FromOkHsv(0.58f, 0.5f, 0.79f, 0.8f); + [/csharp] + [/codeblocks] + + @@ -520,6 +538,24 @@ The HSL saturation of this color, on the range 0 to 1. + + The OK HSL hue of this color, on the range 0 to 1. + + + The OK HSL lightness of this color, on the range 0 to 1. + + + The OK HSL saturation of this color, on the range 0 to 1. + + + The OK HSV hue of this color, on the range 0 to 1. + + + The OK HSV saturation of this color, on the range 0 to 1. + + + The OK HSV value (brightness) of this color, on the range 0 to 1. + The color's red component, typically on the range of 0 to 1. diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Color.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Color.cs index 98e2556dbbfc..3fce435ad32d 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Color.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Color.cs @@ -1062,6 +1062,32 @@ private static int ParseCol8(ReadOnlySpan str, int index) return ParseCol4(str, index) * 16 + ParseCol4(str, index + 1); } + /// + /// Constructs a color from an OK HSV profile. The , + /// , and are typically + /// between 0.0 and 1.0. + /// + /// The OK HSV hue, typically on the range of 0 to 1. + /// The OK HSV saturation, typically on the range of 0 to 1. + /// The OK HSV value, 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 FromOkHsv(float hue, float saturation, float value, float alpha = 1.0f) + { + return NativeFuncs.godotsharp_color_from_ok_hsv(hue, saturation, value, alpha); + } + + /// + /// Converts a color to OK HSV values. + /// + /// Output parameter for the OK HSV hue. + /// Output parameter for the OK HSV saturation. + /// Output parameter for the OK HSV value. + public readonly void ToOkHsv(out float hue, out float saturation, out float value) + { + NativeFuncs.godotsharp_color_to_ok_hsv(this, out hue, out saturation, out value); + } + /// /// Constructs a color from an OK HSL profile. The , /// , and are typically @@ -1077,6 +1103,17 @@ public static Color FromOkHsl(float hue, float saturation, float lightness, floa return NativeFuncs.godotsharp_color_from_ok_hsl(hue, saturation, lightness, alpha); } + /// + /// Converts a color to OK HSL values. + /// + /// Output parameter for the OK HSL hue. + /// Output parameter for the OK HSL saturation. + /// Output parameter for the OK HSL lightness. + public readonly void ToOkHsl(out float hue, out float saturation, out float lightness) + { + NativeFuncs.godotsharp_color_to_ok_hsl(this, out hue, out saturation, out lightness); + } + /// /// Encodes a from a RGBE9995 format integer. /// See . diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs index 3d72ee003669..fba8474e364d 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs @@ -153,8 +153,14 @@ internal static partial godot_variant godotsharp_callable_call(in godot_callable 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_hsv(float p_h, float p_s, float p_v, float p_alpha); + + internal static partial void godotsharp_color_to_ok_hsv(in Color p_color, out float r_h, out float r_s, out float r_v); + internal static partial Color godotsharp_color_from_ok_hsl(float p_h, float p_s, float p_l, float p_alpha); + internal static partial void godotsharp_color_to_ok_hsl(in Color p_color, out float r_h, out float r_s, out float r_l); + // GDNative functions // gdnative.h diff --git a/modules/mono/glue/runtime_interop.cpp b/modules/mono/glue/runtime_interop.cpp index 306ac333eb46..1730a590385a 100644 --- a/modules/mono/glue/runtime_interop.cpp +++ b/modules/mono/glue/runtime_interop.cpp @@ -517,6 +517,20 @@ 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_hsv(float p_h, float p_s, float p_v, float p_alpha) { + godot_color ret; + Color *dest = (Color *)&ret; + memnew_placement(dest, Color(Color::from_ok_hsv(p_h, p_s, p_v, p_alpha))); + return ret; +} + +void godotsharp_color_to_ok_hsv(godot_color *p_color, float *r_h, float *r_s, float *r_v) { + Color *color_val = (Color *)p_color; + *r_h = color_val->get_ok_hsv_h(); + *r_s = color_val->get_ok_hsv_s(); + *r_v = color_val->get_ok_hsv_v(); +} + 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; @@ -524,6 +538,13 @@ godot_color godotsharp_color_from_ok_hsl(float p_h, float p_s, float p_l, float return ret; } +void godotsharp_color_to_ok_hsl(godot_color *p_color, float *r_h, float *r_s, float *r_l) { + Color *color_val = (Color *)p_color; + *r_h = color_val->get_ok_hsl_h(); + *r_s = color_val->get_ok_hsl_s(); + *r_l = color_val->get_ok_hsl_l(); +} + // GDNative functions // gdnative.h @@ -1432,7 +1453,10 @@ 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_hsv, + (void *)godotsharp_color_to_ok_hsv, (void *)godotsharp_color_from_ok_hsl, + (void *)godotsharp_color_to_ok_hsl, (void *)godotsharp_method_bind_ptrcall, (void *)godotsharp_method_bind_call, (void *)godotsharp_variant_new_string_name, diff --git a/tests/core/math/test_color.h b/tests/core/math/test_color.h index 398c2da17598..616a97043a67 100644 --- a/tests/core/math/test_color.h +++ b/tests/core/math/test_color.h @@ -71,6 +71,17 @@ TEST_CASE("[Color] Constructor methods") { CHECK_MESSAGE( green_rgba.is_equal_approx(green_hsla), "Creation with HSL notation should result in components approximately equal to the default constructor."); + + const Color red_rgba = Color(1, 0, 0, 0.25); + const Color red_ok_hsva = Color(0, 0, 0).from_ok_hsv(0.081205, 1, 1, 0.25); + const Color red_ok_hsla = Color(0, 0, 0).from_ok_hsl(0.081205, 1, 0.568085, 0.25); + + CHECK_MESSAGE( + red_rgba.is_equal_approx(red_ok_hsva), + "Creation with OK HSV notation should result in components approximately equal to the default constructor."); + CHECK_MESSAGE( + red_rgba.is_equal_approx(red_ok_hsla), + "Creation with OK HSL notation should result in components approximately equal to the default constructor."); } TEST_CASE("[Color] Operators") { @@ -123,6 +134,26 @@ TEST_CASE("[Color] Reading methods") { CHECK_MESSAGE( dark_blue.get_hsl_l() == doctest::Approx(0.25f), "The returned HSL lightness should match the expected value."); + + CHECK_MESSAGE( + dark_blue.get_ok_hsv_h() == doctest::Approx(0.733478f), + "The returned OK HSV hue should match the expected value."); + CHECK_MESSAGE( + dark_blue.get_ok_hsv_s() == doctest::Approx(0.999991f), + "The returned OK HSV saturation should match the expected value."); + CHECK_MESSAGE( + dark_blue.get_ok_hsv_v() == doctest::Approx(0.474967f), + "The returned OK HSV value should match the expected value."); + + CHECK_MESSAGE( + dark_blue.get_ok_hsl_h() == doctest::Approx(0.733478f), + "The returned OK HSL hue should match the expected value."); + CHECK_MESSAGE( + dark_blue.get_ok_hsl_s() == doctest::Approx(0.999998f), + "The returned OK HSL saturation should match the expected value."); + CHECK_MESSAGE( + dark_blue.get_ok_hsl_l() == doctest::Approx(0.167343f), + "The returned OK HSL lightness should match the expected value."); } TEST_CASE("[Color] Conversion methods") {