summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--COPYRIGHT.txt6
-rw-r--r--core/config/project_settings.cpp19
-rw-r--r--core/config/project_settings.h8
-rw-r--r--core/core_constants.cpp1
-rw-r--r--core/io/config_file.cpp13
-rw-r--r--core/math/color.cpp61
-rw-r--r--core/math/color.h8
-rw-r--r--core/variant/array.cpp44
-rw-r--r--core/variant/array.h2
-rw-r--r--core/variant/variant_call.cpp4
-rw-r--r--core/variant/variant_setget.h4
-rw-r--r--doc/classes/@GlobalScope.xml2
-rw-r--r--doc/classes/Array.xml45
-rw-r--r--doc/classes/Color.xml18
-rw-r--r--doc/classes/ColorPicker.xml3
-rw-r--r--doc/classes/ProjectSettings.xml9
-rw-r--r--doc/classes/Texture2D.xml3
-rw-r--r--doc/classes/VisualShaderNodeFloatFunc.xml2
-rw-r--r--doc/classes/VisualShaderNodeVectorFunc.xml2
-rw-r--r--editor/editor_node.cpp4
-rw-r--r--editor/editor_sectioned_inspector.cpp5
-rw-r--r--editor/editor_spin_slider.cpp1
-rw-r--r--editor/plugins/canvas_item_editor_plugin.cpp26
-rw-r--r--editor/plugins/visual_shader_editor_plugin.cpp8
-rw-r--r--scene/gui/color_picker.cpp173
-rw-r--r--scene/gui/color_picker.h6
-rw-r--r--scene/gui/line_edit.cpp16
-rw-r--r--scene/resources/environment.cpp2
-rw-r--r--scene/resources/texture.cpp32
-rw-r--r--scene/resources/texture.h5
-rw-r--r--scene/resources/visual_shader_nodes.cpp8
-rw-r--r--scene/resources/visual_shader_nodes.h4
-rw-r--r--thirdparty/README.md9
-rw-r--r--thirdparty/misc/ok_color.h688
-rw-r--r--thirdparty/misc/ok_color_shader.h663
35 files changed, 1779 insertions, 125 deletions
diff --git a/COPYRIGHT.txt b/COPYRIGHT.txt
index 277ced8c3d..14c61c187f 100644
--- a/COPYRIGHT.txt
+++ b/COPYRIGHT.txt
@@ -326,6 +326,12 @@ Comment: Tangent Space Normal Maps implementation
Copyright: 2011, Morten S. Mikkelsen
License: Zlib
+Files: ./thirdparty/misc/ok_color.h
+ ./thirdparty/misc/ok_color_shader.h
+Comment: OK Lab color space
+Copyright: 2021, Björn Ottosson
+License: Expat
+
Files: ./thirdparty/noise/FastNoiseLite.h
Comment: FastNoise Lite
Copyright: 2020, Jordan Peck and contributors
diff --git a/core/config/project_settings.cpp b/core/config/project_settings.cpp
index ff5ff83bf8..12d936d456 100644
--- a/core/config/project_settings.cpp
+++ b/core/config/project_settings.cpp
@@ -205,6 +205,11 @@ void ProjectSettings::set_as_basic(const String &p_name, bool p_basic) {
props[p_name].basic = p_basic;
}
+void ProjectSettings::set_as_internal(const String &p_name, bool p_internal) {
+ ERR_FAIL_COND_MSG(!props.has(p_name), "Request for nonexistent project setting: " + p_name + ".");
+ props[p_name].internal = p_internal;
+}
+
void ProjectSettings::set_ignore_value_in_docs(const String &p_name, bool p_ignore) {
ERR_FAIL_COND_MSG(!props.has(p_name), "Request for nonexistent project setting: " + p_name + ".");
#ifdef DEBUG_METHODS_ENABLED
@@ -344,7 +349,7 @@ void ProjectSettings::_get_property_list(List<PropertyInfo> *p_list) const {
vc.name = E.key;
vc.order = v->order;
vc.type = v->variant.get_type();
- if (vc.name.begins_with("input/") || vc.name.begins_with("import/") || vc.name.begins_with("export/") || vc.name.begins_with("/remap") || vc.name.begins_with("/locale") || vc.name.begins_with("/autoload")) {
+ if (v->internal || vc.name.begins_with("input/") || vc.name.begins_with("importer_defaults/") || vc.name.begins_with("import/") || vc.name.begins_with("autoload/") || vc.name.begins_with("editor_plugins/") || vc.name.begins_with("shader_globals/")) {
vc.flags = PROPERTY_USAGE_STORAGE;
} else {
vc.flags = PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_STORAGE;
@@ -890,7 +895,7 @@ Error ProjectSettings::_save_custom_bnd(const String &p_file) { // add other par
Error ProjectSettings::save_custom(const String &p_path, const CustomMap &p_custom, const Vector<String> &p_custom_features, bool p_merge_with_current) {
ERR_FAIL_COND_V_MSG(p_path.is_empty(), ERR_INVALID_PARAMETER, "Project settings save path cannot be empty.");
- PackedStringArray project_features = has_setting("application/config/features") ? (PackedStringArray)get_setting("application/config/features") : PackedStringArray();
+ PackedStringArray project_features = get_setting("application/config/features");
// If there is no feature list currently present, force one to generate.
if (project_features.is_empty()) {
project_features = ProjectSettings::get_required_features();
@@ -994,7 +999,7 @@ Error ProjectSettings::save_custom(const String &p_path, const CustomMap &p_cust
}
}
-Variant _GLOBAL_DEF(const String &p_var, const Variant &p_default, bool p_restart_if_changed, bool p_ignore_value_in_docs, bool p_basic) {
+Variant _GLOBAL_DEF(const String &p_var, const Variant &p_default, bool p_restart_if_changed, bool p_ignore_value_in_docs, bool p_basic, bool p_internal) {
Variant ret;
if (!ProjectSettings::get_singleton()->has_setting(p_var)) {
ProjectSettings::get_singleton()->set(p_var, p_default);
@@ -1006,6 +1011,7 @@ Variant _GLOBAL_DEF(const String &p_var, const Variant &p_default, bool p_restar
ProjectSettings::get_singleton()->set_as_basic(p_var, p_basic);
ProjectSettings::get_singleton()->set_restart_if_changed(p_var, p_restart_if_changed);
ProjectSettings::get_singleton()->set_ignore_value_in_docs(p_var, p_ignore_value_in_docs);
+ ProjectSettings::get_singleton()->set_as_internal(p_var, p_internal);
return ret;
}
@@ -1151,7 +1157,7 @@ void ProjectSettings::_add_builtin_input_map() {
action["events"] = events;
String action_name = "input/" + E.key;
- GLOBAL_DEF(action_name, action);
+ GLOBAL_DEF_INTERNAL(action_name, action);
input_presets.push_back(action_name);
}
}
@@ -1235,6 +1241,11 @@ ProjectSettings::ProjectSettings() {
GLOBAL_DEF("compression/formats/gzip/compression_level", Compression::gzip_level);
custom_prop_info["compression/formats/gzip/compression_level"] = PropertyInfo(Variant::INT, "compression/formats/gzip/compression_level", PROPERTY_HINT_RANGE, "-1,9,1");
+
+ // These properties will not show up in the dialog nor in the documentation. If you want to exclude whole groups, see _get_property_list() method.
+ GLOBAL_DEF_INTERNAL("application/config/features", PackedStringArray());
+ GLOBAL_DEF_INTERNAL("internationalization/locale/translation_remaps", PackedStringArray());
+ GLOBAL_DEF_INTERNAL("internationalization/locale/translations", PackedStringArray());
}
ProjectSettings::~ProjectSettings() {
diff --git a/core/config/project_settings.h b/core/config/project_settings.h
index d9b1a9b81b..c3992a4db2 100644
--- a/core/config/project_settings.h
+++ b/core/config/project_settings.h
@@ -62,6 +62,7 @@ protected:
int order = 0;
bool persist = false;
bool basic = false;
+ bool internal = false;
Variant variant;
Variant initial;
bool hide_from_editor = false;
@@ -141,6 +142,7 @@ public:
void set_initial_value(const String &p_name, const Variant &p_value);
void set_as_basic(const String &p_name, bool p_basic);
+ void set_as_internal(const String &p_name, bool p_internal);
void set_restart_if_changed(const String &p_name, bool p_restart);
void set_ignore_value_in_docs(const String &p_name, bool p_ignore);
bool get_ignore_value_in_docs(const String &p_name) const;
@@ -191,8 +193,8 @@ public:
~ProjectSettings();
};
-//not a macro any longer
-Variant _GLOBAL_DEF(const String &p_var, const Variant &p_default, bool p_restart_if_changed = false, bool p_ignore_value_in_docs = false, bool p_basic = false);
+// Not a macro any longer.
+Variant _GLOBAL_DEF(const String &p_var, const Variant &p_default, bool p_restart_if_changed = false, bool p_ignore_value_in_docs = false, bool p_basic = false, bool p_internal = false);
#define GLOBAL_DEF(m_var, m_value) _GLOBAL_DEF(m_var, m_value)
#define GLOBAL_DEF_RST(m_var, m_value) _GLOBAL_DEF(m_var, m_value, true)
#define GLOBAL_DEF_NOVAL(m_var, m_value) _GLOBAL_DEF(m_var, m_value, false, true)
@@ -204,4 +206,6 @@ Variant _GLOBAL_DEF(const String &p_var, const Variant &p_default, bool p_restar
#define GLOBAL_DEF_NOVAL_BASIC(m_var, m_value) _GLOBAL_DEF(m_var, m_value, false, true, true)
#define GLOBAL_DEF_RST_NOVAL_BASIC(m_var, m_value) _GLOBAL_DEF(m_var, m_value, true, true, true)
+#define GLOBAL_DEF_INTERNAL(m_var, m_value) _GLOBAL_DEF(m_var, m_value, false, false, false, true)
+
#endif // PROJECT_SETTINGS_H
diff --git a/core/core_constants.cpp b/core/core_constants.cpp
index ea1304f5ba..bbc937d575 100644
--- a/core/core_constants.cpp
+++ b/core/core_constants.cpp
@@ -638,6 +638,7 @@ void register_global_constants() {
BIND_CORE_ENUM_CONSTANT(METHOD_FLAG_REVERSE);
BIND_CORE_ENUM_CONSTANT(METHOD_FLAG_VIRTUAL);
BIND_CORE_ENUM_CONSTANT(METHOD_FLAG_FROM_SCRIPT);
+ BIND_CORE_ENUM_CONSTANT(METHOD_FLAG_VARARG);
BIND_CORE_ENUM_CONSTANT(METHOD_FLAG_STATIC);
BIND_CORE_ENUM_CONSTANT(METHOD_FLAG_OBJECT_CORE);
BIND_CORE_ENUM_CONSTANT(METHOD_FLAGS_DEFAULT);
diff --git a/core/io/config_file.cpp b/core/io/config_file.cpp
index dd0191f43f..ae421654ca 100644
--- a/core/io/config_file.cpp
+++ b/core/io/config_file.cpp
@@ -61,19 +61,19 @@ PackedStringArray ConfigFile::_get_section_keys(const String &p_section) const {
}
void ConfigFile::set_value(const String &p_section, const String &p_key, const Variant &p_value) {
- if (p_value.get_type() == Variant::NIL) {
- //erase
+ if (p_value.get_type() == Variant::NIL) { // Erase key.
if (!values.has(p_section)) {
- return; // ?
+ return;
}
+
values[p_section].erase(p_key);
if (values[p_section].is_empty()) {
values.erase(p_section);
}
-
} else {
if (!values.has(p_section)) {
- values[p_section] = HashMap<String, Variant>();
+ // Insert section-less keys at the beginning.
+ values.insert(p_section, HashMap<String, Variant>(), p_section.is_empty());
}
values[p_section][p_key] = p_value;
@@ -125,6 +125,9 @@ void ConfigFile::erase_section_key(const String &p_section, const String &p_key)
ERR_FAIL_COND_MSG(!values[p_section].has(p_key), vformat("Cannot erase nonexistent key \"%s\" from section \"%s\".", p_key, p_section));
values[p_section].erase(p_key);
+ if (values[p_section].is_empty()) {
+ values.erase(p_section);
+ }
}
Error ConfigFile::save(const String &p_path) {
diff --git a/core/math/color.cpp b/core/math/color.cpp
index 74552a2894..4bdeafd2f2 100644
--- a/core/math/color.cpp
+++ b/core/math/color.cpp
@@ -35,6 +35,8 @@
#include "core/string/print_string.h"
#include "core/templates/rb_map.h"
+#include "thirdparty/misc/ok_color.h"
+
uint32_t Color::to_argb32() const {
uint32_t c = (uint8_t)Math::round(a * 255);
c <<= 8;
@@ -240,6 +242,20 @@ void Color::set_hsv(float p_h, float p_s, float p_v, float p_alpha) {
}
}
+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;
+ hsl.s = p_s;
+ hsl.l = p_l;
+ ok_color new_ok_color;
+ ok_color::RGB rgb = new_ok_color.okhsl_to_srgb(hsl);
+ Color c = Color(rgb.r, rgb.g, rgb.b, p_alpha).clamp();
+ r = c.r;
+ g = c.g;
+ b = c.b;
+ a = c.a;
+}
+
bool Color::is_equal_approx(const Color &p_color) const {
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);
}
@@ -568,3 +584,48 @@ Color Color::operator-() const {
1.0f - b,
1.0f - a);
}
+
+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_hsl_h() const {
+ ok_color::RGB rgb;
+ rgb.r = r;
+ rgb.g = g;
+ rgb.b = b;
+ ok_color new_ok_color;
+ ok_color::HSL ok_hsl = new_ok_color.srgb_to_okhsl(rgb);
+ if (Math::is_nan(ok_hsl.h)) {
+ return 0.0f;
+ }
+ return CLAMP(ok_hsl.h, 0.0f, 1.0f);
+}
+
+float Color::get_ok_hsl_s() const {
+ ok_color::RGB rgb;
+ rgb.r = r;
+ rgb.g = g;
+ rgb.b = b;
+ ok_color new_ok_color;
+ ok_color::HSL ok_hsl = new_ok_color.srgb_to_okhsl(rgb);
+ if (Math::is_nan(ok_hsl.s)) {
+ return 0.0f;
+ }
+ return CLAMP(ok_hsl.s, 0.0f, 1.0f);
+}
+
+float Color::get_ok_hsl_l() const {
+ ok_color::RGB rgb;
+ rgb.r = r;
+ rgb.g = g;
+ rgb.b = b;
+ ok_color new_ok_color;
+ ok_color::HSL ok_hsl = new_ok_color.srgb_to_okhsl(rgb);
+ if (Math::is_nan(ok_hsl.l)) {
+ return 0.0f;
+ }
+ return CLAMP(ok_hsl.l, 0.0f, 1.0f);
+}
diff --git a/core/math/color.h b/core/math/color.h
index 91e0bf5532..0afa6006a8 100644
--- a/core/math/color.h
+++ b/core/math/color.h
@@ -56,6 +56,10 @@ struct _NO_DISCARD_ Color {
float get_s() const;
float get_v() const;
void set_hsv(float p_h, float p_s, float p_v, float p_alpha = 1.0);
+ float get_ok_hsl_h() const;
+ float get_ok_hsl_s() const;
+ float get_ok_hsl_l() const;
+ void set_ok_hsl(float p_h, float p_s, float p_l, float p_alpha = 1.0);
_FORCE_INLINE_ float &operator[](int p_idx) {
return components[p_idx];
@@ -195,6 +199,7 @@ struct _NO_DISCARD_ Color {
static Color get_named_color(int p_idx);
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.0);
+ static Color from_ok_hsl(float p_h, float p_s, float p_l, float p_alpha = 1.0);
static Color from_rgbe9995(uint32_t p_rgbe);
_FORCE_INLINE_ bool operator<(const Color &p_color) const; //used in set keys
@@ -213,6 +218,9 @@ struct _NO_DISCARD_ Color {
_FORCE_INLINE_ void set_h(float p_h) { set_hsv(p_h, get_s(), get_v()); }
_FORCE_INLINE_ void set_s(float p_s) { set_hsv(get_h(), p_s, get_v()); }
_FORCE_INLINE_ void set_v(float p_v) { set_hsv(get_h(), get_s(), p_v); }
+ _FORCE_INLINE_ void set_ok_hsl_h(float p_h) { set_ok_hsl(p_h, get_ok_hsl_s(), get_ok_hsl_l()); }
+ _FORCE_INLINE_ void set_ok_hsl_s(float p_s) { set_ok_hsl(get_ok_hsl_h(), p_s, get_ok_hsl_l()); }
+ _FORCE_INLINE_ void set_ok_hsl_l(float p_l) { set_ok_hsl(get_ok_hsl_h(), get_ok_hsl_s(), p_l); }
_FORCE_INLINE_ Color() {}
diff --git a/core/variant/array.cpp b/core/variant/array.cpp
index 7551350c95..b1e142d239 100644
--- a/core/variant/array.cpp
+++ b/core/variant/array.cpp
@@ -501,6 +501,50 @@ Variant Array::reduce(const Callable &p_callable, const Variant &p_accum) const
return ret;
}
+bool Array::any(const Callable &p_callable) const {
+ const Variant *argptrs[1];
+ for (int i = 0; i < size(); i++) {
+ argptrs[0] = &get(i);
+
+ Variant result;
+ Callable::CallError ce;
+ p_callable.call(argptrs, 1, result, ce);
+ if (ce.error != Callable::CallError::CALL_OK) {
+ ERR_FAIL_V_MSG(false, "Error calling method from 'any': " + Variant::get_callable_error_text(p_callable, argptrs, 1, ce));
+ }
+
+ if (result.operator bool()) {
+ // Return as early as possible when one of the conditions is `true`.
+ // This improves performance compared to relying on `filter(...).size() >= 1`.
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool Array::all(const Callable &p_callable) const {
+ const Variant *argptrs[1];
+ for (int i = 0; i < size(); i++) {
+ argptrs[0] = &get(i);
+
+ Variant result;
+ Callable::CallError ce;
+ p_callable.call(argptrs, 1, result, ce);
+ if (ce.error != Callable::CallError::CALL_OK) {
+ ERR_FAIL_V_MSG(false, "Error calling method from 'all': " + Variant::get_callable_error_text(p_callable, argptrs, 1, ce));
+ }
+
+ if (!(result.operator bool())) {
+ // Return as early as possible when one of the inverted conditions is `false`.
+ // This improves performance compared to relying on `filter(...).size() >= array_size().`.
+ return false;
+ }
+ }
+
+ return true;
+}
+
struct _ArrayVariantSort {
_FORCE_INLINE_ bool operator()(const Variant &p_l, const Variant &p_r) const {
bool valid = false;
diff --git a/core/variant/array.h b/core/variant/array.h
index f537700f99..c007376734 100644
--- a/core/variant/array.h
+++ b/core/variant/array.h
@@ -108,6 +108,8 @@ public:
Array filter(const Callable &p_callable) const;
Array map(const Callable &p_callable) const;
Variant reduce(const Callable &p_callable, const Variant &p_accum) const;
+ bool any(const Callable &p_callable) const;
+ bool all(const Callable &p_callable) const;
bool operator<(const Array &p_array) const;
bool operator<=(const Array &p_array) const;
diff --git a/core/variant/variant_call.cpp b/core/variant/variant_call.cpp
index 3fe5ead347..741d3e5b8f 100644
--- a/core/variant/variant_call.cpp
+++ b/core/variant/variant_call.cpp
@@ -1675,6 +1675,8 @@ static void _register_variant_builtin_methods() {
bind_static_method(Color, get_named_color, sarray("idx"), varray());
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_ok_hsl, sarray("h", "s", "l", "alpha"), varray(1.0));
+
bind_static_method(Color, from_rgbe9995, sarray("rgbe"), varray());
/* RID */
@@ -1859,6 +1861,8 @@ static void _register_variant_builtin_methods() {
bind_method(Array, filter, sarray("method"), varray());
bind_method(Array, map, sarray("method"), varray());
bind_method(Array, reduce, sarray("method", "accum"), varray(Variant()));
+ bind_method(Array, any, sarray("method"), varray());
+ bind_method(Array, all, sarray("method"), varray());
bind_method(Array, max, sarray(), varray());
bind_method(Array, min, sarray(), varray());
diff --git a/core/variant/variant_setget.h b/core/variant/variant_setget.h
index 3b95f0531b..bc4dc4b408 100644
--- a/core/variant/variant_setget.h
+++ b/core/variant/variant_setget.h
@@ -329,4 +329,8 @@ SETGET_NUMBER_STRUCT_FUNC(Color, double, h, set_h, get_h)
SETGET_NUMBER_STRUCT_FUNC(Color, double, s, set_s, get_s)
SETGET_NUMBER_STRUCT_FUNC(Color, double, v, set_v, get_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)
+
#endif // VARIANT_SETGET_H
diff --git a/doc/classes/@GlobalScope.xml b/doc/classes/@GlobalScope.xml
index 43101d6e5e..dce96fb315 100644
--- a/doc/classes/@GlobalScope.xml
+++ b/doc/classes/@GlobalScope.xml
@@ -2626,6 +2626,8 @@
<constant name="METHOD_FLAG_FROM_SCRIPT" value="64" enum="MethodFlags">
Deprecated method flag, unused.
</constant>
+ <constant name="METHOD_FLAG_VARARG" value="128" enum="MethodFlags">
+ </constant>
<constant name="METHOD_FLAG_STATIC" value="256" enum="MethodFlags">
</constant>
<constant name="METHOD_FLAG_OBJECT_CORE" value="512" enum="MethodFlags">
diff --git a/doc/classes/Array.xml b/doc/classes/Array.xml
index ef4f86f1a9..94181db95f 100644
--- a/doc/classes/Array.xml
+++ b/doc/classes/Array.xml
@@ -123,6 +123,48 @@
</constructor>
</constructors>
<methods>
+ <method name="all" qualifiers="const">
+ <return type="bool" />
+ <argument index="0" name="method" type="Callable" />
+ <description>
+ Calls the provided [Callable] on each element in the array and returns [code]true[/code] if the [Callable] returns [code]true[/code] for [i]all[/i] elements in the array. If the [Callable] returns [code]false[/code] for one array element or more, this method returns [code]false[/code].
+ The callable's method should take one [Variant] parameter (the current array element) and return a boolean value.
+ [codeblock]
+ func _ready():
+ print([6, 10, 6].all(greater_than_5)) # Prints True (3 elements evaluate to `true`).
+ print([4, 10, 4].all(greater_than_5)) # Prints False (1 elements evaluate to `true`).
+ print([4, 4, 4].all(greater_than_5)) # Prints False (0 elements evaluate to `true`).
+
+ print([6, 10, 6].all(func(number): return number &gt; 5)) # Prints True. Same as the first line above, but using lambda function.
+
+ func greater_than_5(number):
+ return number &gt; 5
+ [/codeblock]
+ See also [method any], [method filter], [method map] and [method reduce].
+ [b]Note:[/b] Unlike relying on the size of an array returned by [method filter], this method will return as early as possible to improve performance (especially with large arrays).
+ </description>
+ </method>
+ <method name="any" qualifiers="const">
+ <return type="bool" />
+ <argument index="0" name="method" type="Callable" />
+ <description>
+ Calls the provided [Callable] on each element in the array and returns [code]true[/code] if the [Callable] returns [code]true[/code] for [i]one or more[/i] elements in the array. If the [Callable] returns [code]false[/code] for all elements in the array, this method returns [code]false[/code].
+ The callable's method should take one [Variant] parameter (the current array element) and return a boolean value.
+ [codeblock]
+ func _ready():
+ print([6, 10, 6].any(greater_than_5)) # Prints True (3 elements evaluate to `true`).
+ print([4, 10, 4].any(greater_than_5)) # Prints True (1 elements evaluate to `true`).
+ print([4, 4, 4].any(greater_than_5)) # Prints False (0 elements evaluate to `true`).
+
+ print([6, 10, 6].any(func(number): return number &gt; 5)) # Prints True. Same as the first line above, but using lambda function.
+
+ func greater_than_5(number):
+ return number &gt; 5
+ [/codeblock]
+ See also [method all], [method filter], [method map] and [method reduce].
+ [b]Note:[/b] Unlike relying on the size of an array returned by [method filter], this method will return as early as possible to improve performance (especially with large arrays).
+ </description>
+ </method>
<method name="append">
<return type="void" />
<argument index="0" name="value" type="Variant" />
@@ -232,6 +274,7 @@
func remove_1(number):
return number != 1
[/codeblock]
+ See also [method any], [method all], [method map] and [method reduce].
</description>
</method>
<method name="find" qualifiers="const">
@@ -333,6 +376,7 @@
func negate(number):
return -number
[/codeblock]
+ See also [method filter], [method reduce], [method any] and [method all].
</description>
</method>
<method name="max" qualifiers="const">
@@ -398,6 +442,7 @@
func sum(accum, number):
return accum + number
[/codeblock]
+ See also [method map], [method filter], [method any] and [method all].
</description>
</method>
<method name="remove_at">
diff --git a/doc/classes/Color.xml b/doc/classes/Color.xml
index 7b8a57ed22..d5a62d2d75 100644
--- a/doc/classes/Color.xml
+++ b/doc/classes/Color.xml
@@ -165,6 +165,24 @@
[/codeblocks]
</description>
</method>
+ <method name="from_ok_hsl" qualifiers="static">
+ <return type="Color" />
+ <argument index="0" name="h" type="float" />
+ <argument index="1" name="s" type="float" />
+ <argument index="2" name="l" type="float" />
+ <argument index="3" name="alpha" type="float" default="1.0" />
+ <description>
+ Constructs a color from an [url=https://bottosson.github.io/posts/colorpicker/]OK HSL profile[/url]. [code]h[/code] (hue), [code]s[/code] (saturation), and [code]v[/code] (value) are typically between 0 and 1.
+ [codeblocks]
+ [gdscript]
+ var c = Color.from_ok_hsl(0.58, 0.5, 0.79, 0.8)
+ [/gdscript]
+ [csharp]
+ var c = Color.FromOkHsl(0.58f, 0.5f, 0.79f, 0.8f);
+ [/csharp]
+ [/codeblocks]
+ </description>
+ </method>
<method name="from_rgbe9995" qualifiers="static">
<return type="Color" />
<argument index="0" name="rgbe" type="int" />
diff --git a/doc/classes/ColorPicker.xml b/doc/classes/ColorPicker.xml
index 5c47d6fb54..7c9c4ed4d6 100644
--- a/doc/classes/ColorPicker.xml
+++ b/doc/classes/ColorPicker.xml
@@ -91,6 +91,9 @@
<constant name="SHAPE_VHS_CIRCLE" value="2" enum="PickerShapeType">
HSV Color Model circle color space. Use Saturation as a radius.
</constant>
+ <constant name="SHAPE_OKHSL_CIRCLE" value="3" enum="PickerShapeType">
+ HSL OK Color Model circle color space.
+ </constant>
</constants>
<theme_items>
<theme_item name="h_width" data_type="constant" type="int" default="30">
diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml
index 0293769a1f..a5ac8f3601 100644
--- a/doc/classes/ProjectSettings.xml
+++ b/doc/classes/ProjectSettings.xml
@@ -216,6 +216,9 @@
<member name="application/config/description" type="String" setter="" getter="" default="&quot;&quot;">
The project's description, displayed as a tooltip in the Project Manager when hovering the project.
</member>
+ <member name="application/config/features" type="PackedStringArray" setter="" getter="">
+ List of internal features associated with the project, like [code]Double Precision[/code] or [code]C#[/code]. Not to be confused with feature tags.
+ </member>
<member name="application/config/icon" type="String" setter="" getter="" default="&quot;&quot;">
Icon used for the project, set when project loads. Exporters will also use this icon when possible.
</member>
@@ -811,6 +814,12 @@
<member name="internationalization/locale/test" type="String" setter="" getter="" default="&quot;&quot;">
If non-empty, this locale will be used when running the project from the editor.
</member>
+ <member name="internationalization/locale/translation_remaps" type="PackedStringArray" setter="" getter="">
+ Locale-dependent resource remaps. Edit them in the "Localization" tab of Project Settings editor.
+ </member>
+ <member name="internationalization/locale/translations" type="PackedStringArray" setter="" getter="">
+ List of translation files available in the project. Edit them in the "Localization" tab of Project Settings editor.
+ </member>
<member name="internationalization/pseudolocalization/double_vowels" type="bool" setter="" getter="" default="false">
Double vowels in strings during pseudolocalization to simulate the lengthening of text due to localization.
</member>
diff --git a/doc/classes/Texture2D.xml b/doc/classes/Texture2D.xml
index 1bbebe085e..3721058d25 100644
--- a/doc/classes/Texture2D.xml
+++ b/doc/classes/Texture2D.xml
@@ -106,7 +106,8 @@
<method name="get_image" qualifiers="const">
<return type="Image" />
<description>
- Returns an [Image] that is a copy of data from this [Texture2D]. [Image]s can be accessed and manipulated directly.
+ Returns an [Image] that is a copy of data from this [Texture2D] (a new [Image] is created each time). [Image]s can be accessed and manipulated directly.
+ [b]Note:[/b] This will fetch the texture data from the GPU, which might cause performance problems when overused.
</description>
</method>
<method name="get_size" qualifiers="const">
diff --git a/doc/classes/VisualShaderNodeFloatFunc.xml b/doc/classes/VisualShaderNodeFloatFunc.xml
index 0f057b2e6d..1226013c67 100644
--- a/doc/classes/VisualShaderNodeFloatFunc.xml
+++ b/doc/classes/VisualShaderNodeFloatFunc.xml
@@ -65,7 +65,7 @@
<constant name="FUNC_CEIL" value="16" enum="Function">
Finds the nearest integer that is greater than or equal to the parameter. Translates to [code]ceil(x)[/code] in the Godot Shader Language.
</constant>
- <constant name="FUNC_FRAC" value="17" enum="Function">
+ <constant name="FUNC_FRACT" value="17" enum="Function">
Computes the fractional part of the argument. Translates to [code]fract(x)[/code] in the Godot Shader Language.
</constant>
<constant name="FUNC_SATURATE" value="18" enum="Function">
diff --git a/doc/classes/VisualShaderNodeVectorFunc.xml b/doc/classes/VisualShaderNodeVectorFunc.xml
index bc4e12c0b3..7524025f21 100644
--- a/doc/classes/VisualShaderNodeVectorFunc.xml
+++ b/doc/classes/VisualShaderNodeVectorFunc.xml
@@ -68,7 +68,7 @@
<constant name="FUNC_FLOOR" value="17" enum="Function">
Finds the nearest integer less than or equal to the parameter.
</constant>
- <constant name="FUNC_FRAC" value="18" enum="Function">
+ <constant name="FUNC_FRACT" value="18" enum="Function">
Computes the fractional part of the argument.
</constant>
<constant name="FUNC_INVERSE_SQRT" value="19" enum="Function">
diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp
index c59c7de603..e23c36c1d4 100644
--- a/editor/editor_node.cpp
+++ b/editor/editor_node.cpp
@@ -6109,8 +6109,8 @@ EditorNode::EditorNode() {
EDITOR_DEF("interface/inspector/resources_to_open_in_new_inspector", "Script,MeshLibrary");
EDITOR_DEF("interface/inspector/default_color_picker_mode", 0);
EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::INT, "interface/inspector/default_color_picker_mode", PROPERTY_HINT_ENUM, "RGB,HSV,RAW", PROPERTY_USAGE_DEFAULT));
- EDITOR_DEF("interface/inspector/default_color_picker_shape", (int32_t)ColorPicker::SHAPE_VHS_CIRCLE);
- EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::INT, "interface/inspector/default_color_picker_shape", PROPERTY_HINT_ENUM, "HSV Rectangle,HSV Rectangle Wheel,VHS Circle", PROPERTY_USAGE_DEFAULT));
+ EDITOR_DEF("interface/inspector/default_color_picker_shape", (int32_t)ColorPicker::SHAPE_OKHSL_CIRCLE);
+ EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::INT, "interface/inspector/default_color_picker_shape", PROPERTY_HINT_ENUM, "HSV Rectangle,HSV Rectangle Wheel,VHS Circle,OKHSL Circle", PROPERTY_USAGE_DEFAULT));
ED_SHORTCUT("canvas_item_editor/pan_view", TTR("Pan View"), Key::SPACE);
diff --git a/editor/editor_sectioned_inspector.cpp b/editor/editor_sectioned_inspector.cpp
index b7073665a8..801a1a4641 100644
--- a/editor/editor_sectioned_inspector.cpp
+++ b/editor/editor_sectioned_inspector.cpp
@@ -250,11 +250,6 @@ void SectionedInspector::update_category_list() {
continue;
}
- // Filter out unnecessary ProjectSettings sections, as they already have their dedicated tabs.
- if (pi.name.begins_with("autoload") || pi.name.begins_with("editor_plugins") || pi.name.begins_with("shader_globals")) {
- continue;
- }
-
if (!filter.is_empty() && !_property_path_matches(pi.name, filter, name_style)) {
continue;
}
diff --git a/editor/editor_spin_slider.cpp b/editor/editor_spin_slider.cpp
index 09e407de57..83944cc553 100644
--- a/editor/editor_spin_slider.cpp
+++ b/editor/editor_spin_slider.cpp
@@ -448,6 +448,7 @@ void EditorSpinSlider::_notification(int p_what) {
if (grabbing_spinner) {
grabber->hide();
Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_VISIBLE);
+ Input::get_singleton()->warp_mouse(grabbing_spinner_mouse_pos);
grabbing_spinner = false;
grabbing_spinner_attempt = false;
}
diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp
index 4377eff322..224d97795b 100644
--- a/editor/plugins/canvas_item_editor_plugin.cpp
+++ b/editor/plugins/canvas_item_editor_plugin.cpp
@@ -4934,6 +4934,19 @@ CanvasItemEditor::CanvasItemEditor() {
controls_vb = memnew(VBoxContainer);
controls_vb->set_begin(Point2(5, 5));
+ // To ensure that scripts can parse the list of shortcuts correctly, we have to define
+ // those shortcuts one by one. Define shortcut before using it (by EditorZoomWidget)
+ ED_SHORTCUT("canvas_item_editor/zoom_3.125_percent", TTR("Zoom to 3.125%"), KeyModifierMask::SHIFT | Key::KEY_5);
+ ED_SHORTCUT("canvas_item_editor/zoom_6.25_percent", TTR("Zoom to 6.25%"), KeyModifierMask::SHIFT | Key::KEY_4);
+ ED_SHORTCUT("canvas_item_editor/zoom_12.5_percent", TTR("Zoom to 12.5%"), KeyModifierMask::SHIFT | Key::KEY_3);
+ ED_SHORTCUT("canvas_item_editor/zoom_25_percent", TTR("Zoom to 25%"), KeyModifierMask::SHIFT | Key::KEY_2);
+ ED_SHORTCUT("canvas_item_editor/zoom_50_percent", TTR("Zoom to 50%"), KeyModifierMask::SHIFT | Key::KEY_1);
+ ED_SHORTCUT_ARRAY("canvas_item_editor/zoom_100_percent", TTR("Zoom to 100%"), { (int32_t)Key::KEY_1, (int32_t)(KeyModifierMask::CMD | Key::KEY_0) });
+ ED_SHORTCUT("canvas_item_editor/zoom_200_percent", TTR("Zoom to 200%"), Key::KEY_2);
+ ED_SHORTCUT("canvas_item_editor/zoom_400_percent", TTR("Zoom to 400%"), Key::KEY_3);
+ ED_SHORTCUT("canvas_item_editor/zoom_800_percent", TTR("Zoom to 800%"), Key::KEY_4);
+ ED_SHORTCUT("canvas_item_editor/zoom_1600_percent", TTR("Zoom to 1600%"), Key::KEY_5);
+
zoom_widget = memnew(EditorZoomWidget);
controls_vb->add_child(zoom_widget);
zoom_widget->set_anchors_and_offsets_preset(Control::PRESET_TOP_LEFT, Control::PRESET_MODE_MINSIZE, 2 * EDSCALE);
@@ -5295,19 +5308,6 @@ CanvasItemEditor::CanvasItemEditor() {
// Store the singleton instance.
singleton = this;
- // To ensure that scripts can parse the list of shortcuts correctly, we have to define
- // those shortcuts one by one.
- ED_SHORTCUT("canvas_item_editor/zoom_3.125_percent", TTR("Zoom to 3.125%"), KeyModifierMask::SHIFT | Key::KEY_5);
- ED_SHORTCUT("canvas_item_editor/zoom_6.25_percent", TTR("Zoom to 6.25%"), KeyModifierMask::SHIFT | Key::KEY_4);
- ED_SHORTCUT("canvas_item_editor/zoom_12.5_percent", TTR("Zoom to 12.5%"), KeyModifierMask::SHIFT | Key::KEY_3);
- ED_SHORTCUT("canvas_item_editor/zoom_25_percent", TTR("Zoom to 25%"), KeyModifierMask::SHIFT | Key::KEY_2);
- ED_SHORTCUT("canvas_item_editor/zoom_50_percent", TTR("Zoom to 50%"), KeyModifierMask::SHIFT | Key::KEY_1);
- ED_SHORTCUT_ARRAY("canvas_item_editor/zoom_100_percent", TTR("Zoom to 100%"), { (int32_t)Key::KEY_1, (int32_t)(KeyModifierMask::CMD | Key::KEY_0) });
- ED_SHORTCUT("canvas_item_editor/zoom_200_percent", TTR("Zoom to 200%"), Key::KEY_2);
- ED_SHORTCUT("canvas_item_editor/zoom_400_percent", TTR("Zoom to 400%"), Key::KEY_3);
- ED_SHORTCUT("canvas_item_editor/zoom_800_percent", TTR("Zoom to 800%"), Key::KEY_4);
- ED_SHORTCUT("canvas_item_editor/zoom_1600_percent", TTR("Zoom to 1600%"), Key::KEY_5);
-
set_process_shortcut_input(true);
// Update the menus' checkboxes
diff --git a/editor/plugins/visual_shader_editor_plugin.cpp b/editor/plugins/visual_shader_editor_plugin.cpp
index 642973648e..19cd354eaf 100644
--- a/editor/plugins/visual_shader_editor_plugin.cpp
+++ b/editor/plugins/visual_shader_editor_plugin.cpp
@@ -5329,7 +5329,7 @@ VisualShaderEditor::VisualShaderEditor() {
add_options.push_back(AddOption("Exp", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("Base-e Exponential."), { VisualShaderNodeFloatFunc::FUNC_EXP }, VisualShaderNode::PORT_TYPE_SCALAR));
add_options.push_back(AddOption("Exp2", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("Base-2 Exponential."), { VisualShaderNodeFloatFunc::FUNC_EXP2 }, VisualShaderNode::PORT_TYPE_SCALAR));
add_options.push_back(AddOption("Floor", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("Finds the nearest integer less than or equal to the parameter."), { VisualShaderNodeFloatFunc::FUNC_FLOOR }, VisualShaderNode::PORT_TYPE_SCALAR));
- add_options.push_back(AddOption("Fract", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("Computes the fractional part of the argument."), { VisualShaderNodeFloatFunc::FUNC_FRAC }, VisualShaderNode::PORT_TYPE_SCALAR));
+ add_options.push_back(AddOption("Fract", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("Computes the fractional part of the argument."), { VisualShaderNodeFloatFunc::FUNC_FRACT }, VisualShaderNode::PORT_TYPE_SCALAR));
add_options.push_back(AddOption("InverseSqrt", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("Returns the inverse of the square root of the parameter."), { VisualShaderNodeFloatFunc::FUNC_INVERSE_SQRT }, VisualShaderNode::PORT_TYPE_SCALAR));
add_options.push_back(AddOption("Log", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("Natural logarithm."), { VisualShaderNodeFloatFunc::FUNC_LOG }, VisualShaderNode::PORT_TYPE_SCALAR));
add_options.push_back(AddOption("Log2", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("Base-2 logarithm."), { VisualShaderNodeFloatFunc::FUNC_LOG2 }, VisualShaderNode::PORT_TYPE_SCALAR));
@@ -5513,9 +5513,9 @@ VisualShaderEditor::VisualShaderEditor() {
add_options.push_back(AddOption("Floor", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Finds the nearest integer less than or equal to the parameter."), { VisualShaderNodeVectorFunc::FUNC_FLOOR, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D));
add_options.push_back(AddOption("Floor", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Finds the nearest integer less than or equal to the parameter."), { VisualShaderNodeVectorFunc::FUNC_FLOOR, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D));
add_options.push_back(AddOption("Floor", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Finds the nearest integer less than or equal to the parameter."), { VisualShaderNodeVectorFunc::FUNC_FLOOR, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D));
- add_options.push_back(AddOption("Fract", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Computes the fractional part of the argument."), { VisualShaderNodeVectorFunc::FUNC_FRAC, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D));
- add_options.push_back(AddOption("Fract", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Computes the fractional part of the argument."), { VisualShaderNodeVectorFunc::FUNC_FRAC, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D));
- add_options.push_back(AddOption("Fract", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Computes the fractional part of the argument."), { VisualShaderNodeVectorFunc::FUNC_FRAC, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D));
+ add_options.push_back(AddOption("Fract", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Computes the fractional part of the argument."), { VisualShaderNodeVectorFunc::FUNC_FRACT, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D));
+ add_options.push_back(AddOption("Fract", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Computes the fractional part of the argument."), { VisualShaderNodeVectorFunc::FUNC_FRACT, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D));
+ add_options.push_back(AddOption("Fract", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Computes the fractional part of the argument."), { VisualShaderNodeVectorFunc::FUNC_FRACT, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D));
add_options.push_back(AddOption("Fresnel", "Vector", "Functions", "VisualShaderNodeFresnel", TTR("Returns falloff based on the dot product of surface normal and view direction of camera (pass associated inputs to it)."), {}, VisualShaderNode::PORT_TYPE_SCALAR));
add_options.push_back(AddOption("InverseSqrt", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Returns the inverse of the square root of the parameter."), { VisualShaderNodeVectorFunc::FUNC_INVERSE_SQRT, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D));
add_options.push_back(AddOption("InverseSqrt", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Returns the inverse of the square root of the parameter."), { VisualShaderNodeVectorFunc::FUNC_INVERSE_SQRT, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D));
diff --git a/scene/gui/color_picker.cpp b/scene/gui/color_picker.cpp
index 6f7ad94139..5fff1e1df3 100644
--- a/scene/gui/color_picker.cpp
+++ b/scene/gui/color_picker.cpp
@@ -31,6 +31,7 @@
#include "color_picker.h"
#include "core/input/input.h"
+#include "core/math/color.h"
#include "core/os/keyboard.h"
#include "core/os/os.h"
#include "scene/main/window.h"
@@ -39,6 +40,9 @@
#include "editor/editor_settings.h"
#endif
+#include "thirdparty/misc/ok_color.h"
+#include "thirdparty/misc/ok_color_shader.h"
+
List<Color> ColorPicker::preset_cache;
void ColorPicker::_notification(int p_what) {
@@ -102,6 +106,7 @@ void ColorPicker::_notification(int p_what) {
Ref<Shader> ColorPicker::wheel_shader;
Ref<Shader> ColorPicker::circle_shader;
+Ref<Shader> ColorPicker::circle_ok_color_shader;
void ColorPicker::init_shaders() {
wheel_shader.instantiate();
@@ -152,11 +157,36 @@ void fragment() {
COLOR = vec4(mix(vec3(1.0), clamp(abs(fract(vec3((a - TAU) / TAU) + vec3(1.0, 2.0 / 3.0, 1.0 / 3.0)) * 6.0 - vec3(3.0)) - vec3(1.0), 0.0, 1.0), ((float(sqrt(x * x + y * y)) * 2.0)) / 1.0) * vec3(v), (b + b2 + b3 + b4) / 4.00);
})");
+
+ circle_ok_color_shader.instantiate();
+ circle_ok_color_shader->set_code(OK_COLOR_SHADER + R"(
+// ColorPicker ok color hsv circle shader.
+
+uniform float v = 1.0;
+
+void fragment() {
+ float x = UV.x - 0.5;
+ float y = UV.y - 0.5;
+ x += 0.001;
+ y += 0.001;
+ float b = float(sqrt(x * x + y * y) < 0.5);
+ x -= 0.002;
+ float b2 = float(sqrt(x * x + y * y) < 0.5);
+ y -= 0.002;
+ float b3 = float(sqrt(x * x + y * y) < 0.5);
+ x += 0.002;
+ float b4 = float(sqrt(x * x + y * y) < 0.5);
+ float s = sqrt(x * x + y * y);
+ float h = atan(y, x) / (2.0*M_PI);
+ vec3 col = okhsl_to_srgb(vec3(h, s, v));
+ COLOR = vec4(col, (b + b2 + b3 + b4) / 4.00);
+})");
}
void ColorPicker::finish_shaders() {
wheel_shader.unref();
circle_shader.unref();
+ circle_ok_color_shader.unref();
}
void ColorPicker::set_focus_on_line_edit() {
@@ -166,8 +196,12 @@ void ColorPicker::set_focus_on_line_edit() {
void ColorPicker::_update_controls() {
const char *rgb[3] = { "R", "G", "B" };
const char *hsv[3] = { "H", "S", "V" };
-
- if (hsv_mode_enabled) {
+ const char *hsl[3] = { "H", "S", "L" };
+ if (hsv_mode_enabled && picker_type == SHAPE_OKHSL_CIRCLE) {
+ for (int i = 0; i < 3; i++) {
+ labels[i]->set_text(hsl[i]);
+ }
+ } else if (hsv_mode_enabled && picker_type != SHAPE_OKHSL_CIRCLE) {
for (int i = 0; i < 3; i++) {
labels[i]->set_text(hsv[i]);
}
@@ -176,14 +210,23 @@ void ColorPicker::_update_controls() {
labels[i]->set_text(rgb[i]);
}
}
-
+ if (picker_type == SHAPE_OKHSL_CIRCLE) {
+ btn_hsv->set_text(RTR("OKHSL"));
+ } else {
+ btn_hsv->set_text(RTR("HSV"));
+ }
if (hsv_mode_enabled) {
set_raw_mode(false);
+ set_hsv_mode(true);
btn_raw->set_disabled(true);
} else if (raw_mode_enabled) {
+ set_raw_mode(true);
set_hsv_mode(false);
+ btn_raw->set_disabled(false);
btn_hsv->set_disabled(true);
} else {
+ set_raw_mode(false);
+ set_hsv_mode(false);
btn_raw->set_disabled(false);
btn_hsv->set_disabled(false);
}
@@ -236,8 +279,15 @@ void ColorPicker::_update_controls() {
wheel_edit->show();
w_edit->show();
uv_edit->hide();
-
wheel->set_material(circle_mat);
+ circle_mat->set_shader(circle_shader);
+ break;
+ case SHAPE_OKHSL_CIRCLE:
+ wheel_edit->show();
+ w_edit->show();
+ uv_edit->hide();
+ wheel->set_material(circle_mat);
+ circle_mat->set_shader(circle_ok_color_shader);
break;
default: {
}
@@ -246,11 +296,17 @@ void ColorPicker::_update_controls() {
void ColorPicker::_set_pick_color(const Color &p_color, bool p_update_sliders) {
color = p_color;
- if (color != last_hsv) {
- h = color.get_h();
- s = color.get_s();
- v = color.get_v();
- last_hsv = color;
+ if (color != last_color) {
+ if (picker_type == SHAPE_OKHSL_CIRCLE) {
+ h = color.get_ok_hsl_h();
+ s = color.get_ok_hsl_s();
+ v = color.get_ok_hsl_l();
+ } else {
+ h = color.get_h();
+ s = color.get_s();
+ v = color.get_v();
+ }
+ last_color = color;
}
if (!is_inside_tree()) {
@@ -301,10 +357,13 @@ void ColorPicker::_value_changed(double) {
h = scroll[0]->get_value() / 360.0;
s = scroll[1]->get_value() / 100.0;
v = scroll[2]->get_value() / 100.0;
- color.set_hsv(h, s, v, scroll[3]->get_value() / 255.0);
-
- last_hsv = color;
+ if (picker_type == SHAPE_OKHSL_CIRCLE) {
+ color.set_ok_hsl(h, s, v, Math::round(scroll[3]->get_value() / 255.0));
+ } else {
+ color.set_hsv(h, s, v, Math::round(scroll[3]->get_value() / 255.0));
+ }
+ last_color = color;
} else {
for (int i = 0; i < 4; i++) {
color.components[i] = scroll[i]->get_value() / (raw_mode_enabled ? 1.0 : 255.0);
@@ -342,7 +401,6 @@ void ColorPicker::_update_color(bool p_update_sliders) {
for (int i = 0; i < 4; i++) {
scroll[i]->set_step(1.0);
}
-
scroll[0]->set_max(359);
scroll[0]->set_value(h * 360.0);
scroll[1]->set_max(100);
@@ -350,7 +408,7 @@ void ColorPicker::_update_color(bool p_update_sliders) {
scroll[2]->set_max(100);
scroll[2]->set_value(v * 100.0);
scroll[3]->set_max(255);
- scroll[3]->set_value(color.components[3] * 255.0);
+ scroll[3]->set_value(Math::round(color.components[3] * 255.0));
} else {
for (int i = 0; i < 4; i++) {
if (raw_mode_enabled) {
@@ -362,7 +420,7 @@ void ColorPicker::_update_color(bool p_update_sliders) {
scroll[i]->set_value(color.components[i]);
} else {
scroll[i]->set_step(1);
- const float byte_value = color.components[i] * 255.0;
+ const float byte_value = Math::round(color.components[i] * 255.0);
scroll[i]->set_max(next_power_of_2(MAX(255, byte_value)) - 1);
scroll[i]->set_value(byte_value);
}
@@ -426,7 +484,6 @@ Color ColorPicker::get_pick_color() const {
void ColorPicker::set_picker_shape(PickerShapeType p_picker_type) {
ERR_FAIL_INDEX(p_picker_type, SHAPE_MAX);
picker_type = p_picker_type;
-
_update_controls();
_update_color();
}
@@ -702,7 +759,7 @@ void ColorPicker::_hsv_draw(int p_which, Control *c) {
Ref<Texture2D> cursor = get_theme_icon(SNAME("picker_cursor"), SNAME("ColorPicker"));
int x;
int y;
- if (picker_type == SHAPE_VHS_CIRCLE) {
+ if (picker_type == SHAPE_VHS_CIRCLE || picker_type == SHAPE_OKHSL_CIRCLE) {
x = center.x + (center.x * Math::cos(h * Math_TAU) * s) - (cursor->get_width() / 2);
y = center.y + (center.y * Math::sin(h * Math_TAU) * s) - (cursor->get_height() / 2);
} else {
@@ -735,6 +792,25 @@ void ColorPicker::_hsv_draw(int p_which, Control *c) {
Color col;
col.set_hsv(h, 1, 1);
c->draw_line(Point2(0, y), Point2(c->get_size().x, y), col.inverted());
+ } else if (picker_type == SHAPE_OKHSL_CIRCLE) {
+ Vector<Point2> points;
+ Vector<Color> colors;
+ Color col;
+ col.set_ok_hsl(h, s, 1);
+ points.resize(4);
+ colors.resize(4);
+ points.set(0, Vector2());
+ points.set(1, Vector2(c->get_size().x, 0));
+ points.set(2, c->get_size());
+ points.set(3, Vector2(0, c->get_size().y));
+ colors.set(0, col);
+ colors.set(1, col);
+ colors.set(2, Color(0, 0, 0));
+ colors.set(3, Color(0, 0, 0));
+ c->draw_polygon(points, colors);
+ int y = c->get_size().y - c->get_size().y * CLAMP(v, 0, 1);
+ col.set_ok_hsl(h, 1, v);
+ c->draw_line(Point2(0, y), Point2(c->get_size().x, y), col.inverted());
} else if (picker_type == SHAPE_VHS_CIRCLE) {
Vector<Point2> points;
Vector<Color> colors;
@@ -757,7 +833,7 @@ void ColorPicker::_hsv_draw(int p_which, Control *c) {
}
} else if (p_which == 2) {
c->draw_rect(Rect2(Point2(), c->get_size()), Color(1, 1, 1));
- if (picker_type == SHAPE_VHS_CIRCLE) {
+ if (picker_type == SHAPE_VHS_CIRCLE || picker_type == SHAPE_OKHSL_CIRCLE) {
circle_mat->set_shader_param("v", v);
}
}
@@ -793,10 +869,19 @@ void ColorPicker::_slider_draw(int p_which) {
}
Color s_col;
Color v_col;
- s_col.set_hsv(h, 0, v);
+ if (picker_type == SHAPE_OKHSL_CIRCLE) {
+ s_col.set_ok_hsl(h, 0, v);
+ } else {
+ s_col.set_hsv(h, 0, v);
+ }
left_color = (p_which == 1) ? s_col : Color(0, 0, 0);
- s_col.set_hsv(h, 1, v);
- v_col.set_hsv(h, s, 1);
+ if (picker_type == SHAPE_OKHSL_CIRCLE) {
+ s_col.set_ok_hsl(h, 1, v);
+ v_col.set_ok_hsl(h, s, 1);
+ } else {
+ s_col.set_hsv(h, 1, v);
+ v_col.set_hsv(h, s, 1);
+ }
right_color = (p_which == 1) ? s_col : v_col;
} else {
left_color = Color(
@@ -828,9 +913,8 @@ void ColorPicker::_uv_input(const Ref<InputEvent> &p_event, Control *c) {
if (bev.is_valid()) {
if (bev->is_pressed() && bev->get_button_index() == MouseButton::LEFT) {
Vector2 center = c->get_size() / 2.0;
- if (picker_type == SHAPE_VHS_CIRCLE) {
+ if (picker_type == SHAPE_VHS_CIRCLE || picker_type == SHAPE_OKHSL_CIRCLE) {
real_t dist = center.distance_to(bev->get_position());
-
if (dist <= center.x) {
real_t rad = center.angle_to_point(bev->get_position());
h = ((rad >= 0) ? rad : (Math_TAU + rad)) / Math_TAU;
@@ -867,8 +951,13 @@ void ColorPicker::_uv_input(const Ref<InputEvent> &p_event, Control *c) {
}
}
changing_color = true;
- color.set_hsv(h, s, v, color.a);
- last_hsv = color;
+ if (picker_type == SHAPE_OKHSL_CIRCLE) {
+ color.set_ok_hsl(h, s, v, color.a);
+ } else if (picker_type != SHAPE_OKHSL_CIRCLE) {
+ color.set_hsv(h, s, v, color.a);
+ }
+ last_color = color;
+
set_pick_color(color);
_update_color();
if (!deferred_mode_enabled) {
@@ -892,7 +981,7 @@ void ColorPicker::_uv_input(const Ref<InputEvent> &p_event, Control *c) {
}
Vector2 center = c->get_size() / 2.0;
- if (picker_type == SHAPE_VHS_CIRCLE) {
+ if (picker_type == SHAPE_VHS_CIRCLE || picker_type == SHAPE_OKHSL_CIRCLE) {
real_t dist = center.distance_to(mev->get_position());
real_t rad = center.angle_to_point(mev->get_position());
h = ((rad >= 0) ? rad : (Math_TAU + rad)) / Math_TAU;
@@ -913,9 +1002,12 @@ void ColorPicker::_uv_input(const Ref<InputEvent> &p_event, Control *c) {
v = 1.0 - (y - corner_y) / real_size.y;
}
}
-
- color.set_hsv(h, s, v, color.a);
- last_hsv = color;
+ if (picker_type != SHAPE_OKHSL_CIRCLE) {
+ color.set_hsv(h, s, v, color.a);
+ } else if (picker_type == SHAPE_OKHSL_CIRCLE) {
+ color.set_ok_hsl(h, s, v, color.a);
+ }
+ last_color = color;
set_pick_color(color);
_update_color();
if (!deferred_mode_enabled) {
@@ -931,7 +1023,7 @@ void ColorPicker::_w_input(const Ref<InputEvent> &p_event) {
if (bev->is_pressed() && bev->get_button_index() == MouseButton::LEFT) {
changing_color = true;
float y = CLAMP((float)bev->get_position().y, 0, w_edit->get_size().height);
- if (picker_type == SHAPE_VHS_CIRCLE) {
+ if (picker_type == SHAPE_VHS_CIRCLE || picker_type == SHAPE_OKHSL_CIRCLE) {
v = 1.0 - (y / w_edit->get_size().height);
} else {
h = y / w_edit->get_size().height;
@@ -939,8 +1031,12 @@ void ColorPicker::_w_input(const Ref<InputEvent> &p_event) {
} else {
changing_color = false;
}
- color.set_hsv(h, s, v, color.a);
- last_hsv = color;
+ if (picker_type != SHAPE_OKHSL_CIRCLE) {
+ color.set_hsv(h, s, v, color.a);
+ } else if (picker_type == SHAPE_OKHSL_CIRCLE) {
+ color.set_ok_hsl(h, s, v, color.a);
+ }
+ last_color = color;
set_pick_color(color);
_update_color();
if (!deferred_mode_enabled) {
@@ -957,13 +1053,17 @@ void ColorPicker::_w_input(const Ref<InputEvent> &p_event) {
return;
}
float y = CLAMP((float)mev->get_position().y, 0, w_edit->get_size().height);
- if (picker_type == SHAPE_VHS_CIRCLE) {
+ if (picker_type == SHAPE_VHS_CIRCLE || picker_type == SHAPE_OKHSL_CIRCLE) {
v = 1.0 - (y / w_edit->get_size().height);
} else {
h = y / w_edit->get_size().height;
}
- color.set_hsv(h, s, v, color.a);
- last_hsv = color;
+ if (hsv_mode_enabled && picker_type != SHAPE_OKHSL_CIRCLE) {
+ color.set_hsv(h, s, v, color.a);
+ } else if (hsv_mode_enabled && picker_type == SHAPE_OKHSL_CIRCLE) {
+ color.set_ok_hsl(h, s, v, color.a);
+ }
+ last_color = color;
set_pick_color(color);
_update_color();
if (!deferred_mode_enabled) {
@@ -1128,7 +1228,7 @@ void ColorPicker::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "hsv_mode"), "set_hsv_mode", "is_hsv_mode");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "raw_mode"), "set_raw_mode", "is_raw_mode");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "deferred_mode"), "set_deferred_mode", "is_deferred_mode");
- ADD_PROPERTY(PropertyInfo(Variant::INT, "picker_shape", PROPERTY_HINT_ENUM, "HSV Rectangle,HSV Rectangle Wheel,VHS Circle"), "set_picker_shape", "get_picker_shape");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "picker_shape", PROPERTY_HINT_ENUM, "HSV Rectangle,HSV Rectangle Wheel,VHS Circle,OKHSL Circle"), "set_picker_shape", "get_picker_shape");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "presets_enabled"), "set_presets_enabled", "are_presets_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "presets_visible"), "set_presets_visible", "are_presets_visible");
@@ -1139,6 +1239,7 @@ void ColorPicker::_bind_methods() {
BIND_ENUM_CONSTANT(SHAPE_HSV_RECTANGLE);
BIND_ENUM_CONSTANT(SHAPE_HSV_WHEEL);
BIND_ENUM_CONSTANT(SHAPE_VHS_CIRCLE);
+ BIND_ENUM_CONSTANT(SHAPE_OKHSL_CIRCLE);
}
ColorPicker::ColorPicker() :
diff --git a/scene/gui/color_picker.h b/scene/gui/color_picker.h
index 6f3e16009c..953be032ec 100644
--- a/scene/gui/color_picker.h
+++ b/scene/gui/color_picker.h
@@ -68,6 +68,7 @@ public:
SHAPE_HSV_RECTANGLE,
SHAPE_HSV_WHEEL,
SHAPE_VHS_CIRCLE,
+ SHAPE_OKHSL_CIRCLE,
SHAPE_MAX
};
@@ -75,6 +76,7 @@ public:
private:
static Ref<Shader> wheel_shader;
static Ref<Shader> circle_shader;
+ static Ref<Shader> circle_ok_color_shader;
static List<Color> preset_cache;
Control *screen = nullptr;
@@ -124,7 +126,7 @@ private:
float h = 0.0;
float s = 0.0;
float v = 0.0;
- Color last_hsv;
+ Color last_color;
void _html_submitted(const String &p_html);
void _value_changed(double);
@@ -161,6 +163,8 @@ public:
void set_edit_alpha(bool p_show);
bool is_editing_alpha() const;
+ int get_preset_size();
+
void _set_pick_color(const Color &p_color, bool p_update_sliders);
void set_pick_color(const Color &p_color);
Color get_pick_color() const;
diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp
index fd9db4ea1b..fa600d24ef 100644
--- a/scene/gui/line_edit.cpp
+++ b/scene/gui/line_edit.cpp
@@ -1686,13 +1686,17 @@ Size2 LineEdit::get_minimum_size() const {
min_size.height = MAX(TS->shaped_text_get_size(text_rid).y + font->get_spacing(TextServer::SPACING_TOP) + font->get_spacing(TextServer::SPACING_BOTTOM), font->get_height(font_size));
// Take icons into account.
- bool using_placeholder = text.is_empty() && ime_text.is_empty();
- bool display_clear_icon = !using_placeholder && is_editable() && clear_button_enabled;
- if (right_icon.is_valid() || display_clear_icon) {
- Ref<Texture2D> r_icon = display_clear_icon ? Control::get_theme_icon(SNAME("clear")) : right_icon;
- min_size.width += r_icon->get_width();
- min_size.height = MAX(min_size.height, r_icon->get_height());
+ int icon_max_width = 0;
+ if (right_icon.is_valid()) {
+ min_size.height = MAX(min_size.height, right_icon->get_height());
+ icon_max_width = right_icon->get_width();
+ }
+ if (clear_button_enabled) {
+ Ref<Texture2D> clear_icon = Control::get_theme_icon(SNAME("clear"));
+ min_size.height = MAX(min_size.height, clear_icon->get_height());
+ icon_max_width = MAX(icon_max_width, clear_icon->get_width());
}
+ min_size.width += icon_max_width;
return style->get_minimum_size() + min_size;
}
diff --git a/scene/resources/environment.cpp b/scene/resources/environment.cpp
index 8d515eca09..afff05c688 100644
--- a/scene/resources/environment.cpp
+++ b/scene/resources/environment.cpp
@@ -1466,7 +1466,7 @@ void Environment::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "volumetric_fog_ambient_inject", PROPERTY_HINT_RANGE, "0.0,16,0.01,exp"), "set_volumetric_fog_ambient_inject", "get_volumetric_fog_ambient_inject");
ADD_SUBGROUP("Temporal Reprojection", "volumetric_fog_temporal_reprojection_");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "volumetric_fog_temporal_reprojection_enabled"), "set_volumetric_fog_temporal_reprojection_enabled", "is_volumetric_fog_temporal_reprojection_enabled");
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "volumetric_fog_temporal_reprojection_amount", PROPERTY_HINT_RANGE, "0.0,0.999,0.001"), "set_volumetric_fog_temporal_reprojection_amount", "get_volumetric_fog_temporal_reprojection_amount");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "volumetric_fog_temporal_reprojection_amount", PROPERTY_HINT_RANGE, "0.5,0.99,0.001"), "set_volumetric_fog_temporal_reprojection_amount", "get_volumetric_fog_temporal_reprojection_amount");
// Adjustment
diff --git a/scene/resources/texture.cpp b/scene/resources/texture.cpp
index 04b85e3e36..e001aeffc4 100644
--- a/scene/resources/texture.cpp
+++ b/scene/resources/texture.cpp
@@ -147,38 +147,6 @@ void ImageTexture::reload_from_file() {
}
}
-bool ImageTexture::_set(const StringName &p_name, const Variant &p_value) {
- if (p_name == "image") {
- create_from_image(p_value);
- } else if (p_name == "size") {
- Size2 s = p_value;
- w = s.width;
- h = s.height;
- RenderingServer::get_singleton()->texture_set_size_override(texture, w, h);
- } else {
- return false;
- }
-
- return true;
-}
-
-bool ImageTexture::_get(const StringName &p_name, Variant &r_ret) const {
- if (p_name == "image") {
- r_ret = get_image();
- } else if (p_name == "size") {
- r_ret = Size2(w, h);
- } else {
- return false;
- }
-
- return true;
-}
-
-void ImageTexture::_get_property_list(List<PropertyInfo> *p_list) const {
- p_list->push_back(PropertyInfo(Variant::OBJECT, PNAME("image"), PROPERTY_HINT_RESOURCE_TYPE, "Image", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESOURCE_NOT_PERSISTENT));
- p_list->push_back(PropertyInfo(Variant::VECTOR2, PNAME("size"), PROPERTY_HINT_NONE, ""));
-}
-
void ImageTexture::create_from_image(const Ref<Image> &p_image) {
ERR_FAIL_COND_MSG(p_image.is_null() || p_image->is_empty(), "Invalid image");
w = p_image->get_width();
diff --git a/scene/resources/texture.h b/scene/resources/texture.h
index 317756e313..3e8ac060c4 100644
--- a/scene/resources/texture.h
+++ b/scene/resources/texture.h
@@ -102,11 +102,6 @@ class ImageTexture : public Texture2D {
protected:
virtual void reload_from_file() override;
-
- bool _set(const StringName &p_name, const Variant &p_value);
- bool _get(const StringName &p_name, Variant &r_ret) const;
- void _get_property_list(List<PropertyInfo> *p_list) const;
-
static void _bind_methods();
public:
diff --git a/scene/resources/visual_shader_nodes.cpp b/scene/resources/visual_shader_nodes.cpp
index 5c0f36ca92..b8667f07fe 100644
--- a/scene/resources/visual_shader_nodes.cpp
+++ b/scene/resources/visual_shader_nodes.cpp
@@ -2462,7 +2462,7 @@ void VisualShaderNodeFloatFunc::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_function", "func"), &VisualShaderNodeFloatFunc::set_function);
ClassDB::bind_method(D_METHOD("get_function"), &VisualShaderNodeFloatFunc::get_function);
- ADD_PROPERTY(PropertyInfo(Variant::INT, "function", PROPERTY_HINT_ENUM, "Sin,Cos,Tan,ASin,ACos,ATan,SinH,CosH,TanH,Log,Exp,Sqrt,Abs,Sign,Floor,Round,Ceil,Frac,Saturate,Negate,ACosH,ASinH,ATanH,Degrees,Exp2,InverseSqrt,Log2,Radians,Reciprocal,RoundEven,Trunc,OneMinus"), "set_function", "get_function");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "function", PROPERTY_HINT_ENUM, "Sin,Cos,Tan,ASin,ACos,ATan,SinH,CosH,TanH,Log,Exp,Sqrt,Abs,Sign,Floor,Round,Ceil,Fract,Saturate,Negate,ACosH,ASinH,ATanH,Degrees,Exp2,InverseSqrt,Log2,Radians,Reciprocal,RoundEven,Trunc,OneMinus"), "set_function", "get_function");
BIND_ENUM_CONSTANT(FUNC_SIN);
BIND_ENUM_CONSTANT(FUNC_COS);
@@ -2481,7 +2481,7 @@ void VisualShaderNodeFloatFunc::_bind_methods() {
BIND_ENUM_CONSTANT(FUNC_FLOOR);
BIND_ENUM_CONSTANT(FUNC_ROUND);
BIND_ENUM_CONSTANT(FUNC_CEIL);
- BIND_ENUM_CONSTANT(FUNC_FRAC);
+ BIND_ENUM_CONSTANT(FUNC_FRACT);
BIND_ENUM_CONSTANT(FUNC_SATURATE);
BIND_ENUM_CONSTANT(FUNC_NEGATE);
BIND_ENUM_CONSTANT(FUNC_ACOSH);
@@ -2713,7 +2713,7 @@ void VisualShaderNodeVectorFunc::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_function", "func"), &VisualShaderNodeVectorFunc::set_function);
ClassDB::bind_method(D_METHOD("get_function"), &VisualShaderNodeVectorFunc::get_function);
- ADD_PROPERTY(PropertyInfo(Variant::INT, "function", PROPERTY_HINT_ENUM, "Normalize,Saturate,Negate,Reciprocal,Abs,ACos,ACosH,ASin,ASinH,ATan,ATanH,Ceil,Cos,CosH,Degrees,Exp,Exp2,Floor,Frac,InverseSqrt,Log,Log2,Radians,Round,RoundEven,Sign,Sin,SinH,Sqrt,Tan,TanH,Trunc,OneMinus"), "set_function", "get_function");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "function", PROPERTY_HINT_ENUM, "Normalize,Saturate,Negate,Reciprocal,Abs,ACos,ACosH,ASin,ASinH,ATan,ATanH,Ceil,Cos,CosH,Degrees,Exp,Exp2,Floor,Fract,InverseSqrt,Log,Log2,Radians,Round,RoundEven,Sign,Sin,SinH,Sqrt,Tan,TanH,Trunc,OneMinus"), "set_function", "get_function");
BIND_ENUM_CONSTANT(FUNC_NORMALIZE);
BIND_ENUM_CONSTANT(FUNC_SATURATE);
@@ -2733,7 +2733,7 @@ void VisualShaderNodeVectorFunc::_bind_methods() {
BIND_ENUM_CONSTANT(FUNC_EXP);
BIND_ENUM_CONSTANT(FUNC_EXP2);
BIND_ENUM_CONSTANT(FUNC_FLOOR);
- BIND_ENUM_CONSTANT(FUNC_FRAC);
+ BIND_ENUM_CONSTANT(FUNC_FRACT);
BIND_ENUM_CONSTANT(FUNC_INVERSE_SQRT);
BIND_ENUM_CONSTANT(FUNC_LOG);
BIND_ENUM_CONSTANT(FUNC_LOG2);
diff --git a/scene/resources/visual_shader_nodes.h b/scene/resources/visual_shader_nodes.h
index b159d25eba..1eb7b7240f 100644
--- a/scene/resources/visual_shader_nodes.h
+++ b/scene/resources/visual_shader_nodes.h
@@ -934,7 +934,7 @@ public:
FUNC_FLOOR,
FUNC_ROUND,
FUNC_CEIL,
- FUNC_FRAC,
+ FUNC_FRACT,
FUNC_SATURATE,
FUNC_NEGATE,
FUNC_ACOSH,
@@ -1053,7 +1053,7 @@ public:
FUNC_EXP,
FUNC_EXP2,
FUNC_FLOOR,
- FUNC_FRAC,
+ FUNC_FRACT,
FUNC_INVERSE_SQRT,
FUNC_LOG,
FUNC_LOG2,
diff --git a/thirdparty/README.md b/thirdparty/README.md
index 31b19451b3..97ba4bdca4 100644
--- a/thirdparty/README.md
+++ b/thirdparty/README.md
@@ -432,6 +432,15 @@ Collection of single-file libraries used in Godot components.
* Upstream: https://github.com/Auburn/FastNoiseLite
* Version: git (6be3d6bf7fb408de341285f9ee8a29b67fd953f1, 2022) + custom changes
* License: MIT
+- `ok_color.h`
+ * Upstream: https://github.com/bottosson/bottosson.github.io/blob/master/misc/ok_color.h
+ * Version: git (d69831edb90ffdcd08b7e64da3c5405acd48ad2c, 2022)
+ * License: MIT
+ * Modifications: License included in header.
+- `ok_color_shader.h`
+ * https://www.shadertoy.com/view/7sK3D1
+ * Version: 2021-09-13
+ * License: MIT
- `pcg.{cpp,h}`
* Upstream: http://www.pcg-random.org
* Version: minimal C implementation, http://www.pcg-random.org/download.html
diff --git a/thirdparty/misc/ok_color.h b/thirdparty/misc/ok_color.h
new file mode 100644
index 0000000000..dbc7dafc36
--- /dev/null
+++ b/thirdparty/misc/ok_color.h
@@ -0,0 +1,688 @@
+// Copyright(c) 2021 Björn Ottosson
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of
+// this software and associated documentation files(the "Software"), to deal in
+// the Software without restriction, including without limitation the rights to
+// use, copy, modify, merge, publish, distribute, sublicense, and /or sell copies
+// of the Software, and to permit persons to whom the Software is furnished to do
+// so, subject to the following conditions :
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+
+#ifndef OK_COLOR_H
+#define OK_COLOR_H
+
+#include <cmath>
+#include <cfloat>
+
+class ok_color
+{
+public:
+
+struct Lab { float L; float a; float b; };
+struct RGB { float r; float g; float b; };
+struct HSV { float h; float s; float v; };
+struct HSL { float h; float s; float l; };
+struct LC { float L; float C; };
+
+// Alternative representation of (L_cusp, C_cusp)
+// Encoded so S = C_cusp/L_cusp and T = C_cusp/(1-L_cusp)
+// The maximum value for C in the triangle is then found as fmin(S*L, T*(1-L)), for a given L
+struct ST { float S; float T; };
+
+static constexpr float pi = 3.1415926535897932384626433832795028841971693993751058209749445923078164062f;
+
+float clamp(float x, float min, float max)
+{
+ if (x < min)
+ return min;
+ if (x > max)
+ return max;
+
+ return x;
+}
+
+float sgn(float x)
+{
+ return (float)(0.f < x) - (float)(x < 0.f);
+}
+
+float srgb_transfer_function(float a)
+{
+ return .0031308f >= a ? 12.92f * a : 1.055f * powf(a, .4166666666666667f) - .055f;
+}
+
+float srgb_transfer_function_inv(float a)
+{
+ return .04045f < a ? powf((a + .055f) / 1.055f, 2.4f) : a / 12.92f;
+}
+
+Lab linear_srgb_to_oklab(RGB c)
+{
+ float l = 0.4122214708f * c.r + 0.5363325363f * c.g + 0.0514459929f * c.b;
+ float m = 0.2119034982f * c.r + 0.6806995451f * c.g + 0.1073969566f * c.b;
+ float s = 0.0883024619f * c.r + 0.2817188376f * c.g + 0.6299787005f * c.b;
+
+ float l_ = cbrtf(l);
+ float m_ = cbrtf(m);
+ float s_ = cbrtf(s);
+
+ return {
+ 0.2104542553f * l_ + 0.7936177850f * m_ - 0.0040720468f * s_,
+ 1.9779984951f * l_ - 2.4285922050f * m_ + 0.4505937099f * s_,
+ 0.0259040371f * l_ + 0.7827717662f * m_ - 0.8086757660f * s_,
+ };
+}
+
+RGB oklab_to_linear_srgb(Lab c)
+{
+ float l_ = c.L + 0.3963377774f * c.a + 0.2158037573f * c.b;
+ float m_ = c.L - 0.1055613458f * c.a - 0.0638541728f * c.b;
+ float s_ = c.L - 0.0894841775f * c.a - 1.2914855480f * c.b;
+
+ float l = l_ * l_ * l_;
+ float m = m_ * m_ * m_;
+ float s = s_ * s_ * s_;
+
+ return {
+ +4.0767416621f * l - 3.3077115913f * m + 0.2309699292f * s,
+ -1.2684380046f * l + 2.6097574011f * m - 0.3413193965f * s,
+ -0.0041960863f * l - 0.7034186147f * m + 1.7076147010f * s,
+ };
+}
+
+// Finds the maximum saturation possible for a given hue that fits in sRGB
+// Saturation here is defined as S = C/L
+// a and b must be normalized so a^2 + b^2 == 1
+float compute_max_saturation(float a, float b)
+{
+ // Max saturation will be when one of r, g or b goes below zero.
+
+ // Select different coefficients depending on which component goes below zero first
+ float k0, k1, k2, k3, k4, wl, wm, ws;
+
+ if (-1.88170328f * a - 0.80936493f * b > 1)
+ {
+ // Red component
+ k0 = +1.19086277f; k1 = +1.76576728f; k2 = +0.59662641f; k3 = +0.75515197f; k4 = +0.56771245f;
+ wl = +4.0767416621f; wm = -3.3077115913f; ws = +0.2309699292f;
+ }
+ else if (1.81444104f * a - 1.19445276f * b > 1)
+ {
+ // Green component
+ k0 = +0.73956515f; k1 = -0.45954404f; k2 = +0.08285427f; k3 = +0.12541070f; k4 = +0.14503204f;
+ wl = -1.2684380046f; wm = +2.6097574011f; ws = -0.3413193965f;
+ }
+ else
+ {
+ // Blue component
+ k0 = +1.35733652f; k1 = -0.00915799f; k2 = -1.15130210f; k3 = -0.50559606f; k4 = +0.00692167f;
+ wl = -0.0041960863f; wm = -0.7034186147f; ws = +1.7076147010f;
+ }
+
+ // Approximate max saturation using a polynomial:
+ float S = k0 + k1 * a + k2 * b + k3 * a * a + k4 * a * b;
+
+ // Do one step Halley's method to get closer
+ // this gives an error less than 10e6, except for some blue hues where the dS/dh is close to infinite
+ // this should be sufficient for most applications, otherwise do two/three steps
+
+ float k_l = +0.3963377774f * a + 0.2158037573f * b;
+ float k_m = -0.1055613458f * a - 0.0638541728f * b;
+ float k_s = -0.0894841775f * a - 1.2914855480f * b;
+
+ {
+ float l_ = 1.f + S * k_l;
+ float m_ = 1.f + S * k_m;
+ float s_ = 1.f + S * k_s;
+
+ float l = l_ * l_ * l_;
+ float m = m_ * m_ * m_;
+ float s = s_ * s_ * s_;
+
+ float l_dS = 3.f * k_l * l_ * l_;
+ float m_dS = 3.f * k_m * m_ * m_;
+ float s_dS = 3.f * k_s * s_ * s_;
+
+ float l_dS2 = 6.f * k_l * k_l * l_;
+ float m_dS2 = 6.f * k_m * k_m * m_;
+ float s_dS2 = 6.f * k_s * k_s * s_;
+
+ float f = wl * l + wm * m + ws * s;
+ float f1 = wl * l_dS + wm * m_dS + ws * s_dS;
+ float f2 = wl * l_dS2 + wm * m_dS2 + ws * s_dS2;
+
+ S = S - f * f1 / (f1 * f1 - 0.5f * f * f2);
+ }
+
+ return S;
+}
+
+// finds L_cusp and C_cusp for a given hue
+// a and b must be normalized so a^2 + b^2 == 1
+LC find_cusp(float a, float b)
+{
+ // First, find the maximum saturation (saturation S = C/L)
+ float S_cusp = compute_max_saturation(a, b);
+
+ // Convert to linear sRGB to find the first point where at least one of r,g or b >= 1:
+ RGB rgb_at_max = oklab_to_linear_srgb({ 1, S_cusp * a, S_cusp * b });
+ float L_cusp = cbrtf(1.f / fmax(fmax(rgb_at_max.r, rgb_at_max.g), rgb_at_max.b));
+ float C_cusp = L_cusp * S_cusp;
+
+ return { L_cusp , C_cusp };
+}
+
+// Finds intersection of the line defined by
+// L = L0 * (1 - t) + t * L1;
+// C = t * C1;
+// a and b must be normalized so a^2 + b^2 == 1
+float find_gamut_intersection(float a, float b, float L1, float C1, float L0, LC cusp)
+{
+ // Find the intersection for upper and lower half seprately
+ float t;
+ if (((L1 - L0) * cusp.C - (cusp.L - L0) * C1) <= 0.f)
+ {
+ // Lower half
+
+ t = cusp.C * L0 / (C1 * cusp.L + cusp.C * (L0 - L1));
+ }
+ else
+ {
+ // Upper half
+
+ // First intersect with triangle
+ t = cusp.C * (L0 - 1.f) / (C1 * (cusp.L - 1.f) + cusp.C * (L0 - L1));
+
+ // Then one step Halley's method
+ {
+ float dL = L1 - L0;
+ float dC = C1;
+
+ float k_l = +0.3963377774f * a + 0.2158037573f * b;
+ float k_m = -0.1055613458f * a - 0.0638541728f * b;
+ float k_s = -0.0894841775f * a - 1.2914855480f * b;
+
+ float l_dt = dL + dC * k_l;
+ float m_dt = dL + dC * k_m;
+ float s_dt = dL + dC * k_s;
+
+
+ // If higher accuracy is required, 2 or 3 iterations of the following block can be used:
+ {
+ float L = L0 * (1.f - t) + t * L1;
+ float C = t * C1;
+
+ float l_ = L + C * k_l;
+ float m_ = L + C * k_m;
+ float s_ = L + C * k_s;
+
+ float l = l_ * l_ * l_;
+ float m = m_ * m_ * m_;
+ float s = s_ * s_ * s_;
+
+ float ldt = 3 * l_dt * l_ * l_;
+ float mdt = 3 * m_dt * m_ * m_;
+ float sdt = 3 * s_dt * s_ * s_;
+
+ float ldt2 = 6 * l_dt * l_dt * l_;
+ float mdt2 = 6 * m_dt * m_dt * m_;
+ float sdt2 = 6 * s_dt * s_dt * s_;
+
+ float r = 4.0767416621f * l - 3.3077115913f * m + 0.2309699292f * s - 1;
+ float r1 = 4.0767416621f * ldt - 3.3077115913f * mdt + 0.2309699292f * sdt;
+ float r2 = 4.0767416621f * ldt2 - 3.3077115913f * mdt2 + 0.2309699292f * sdt2;
+
+ float u_r = r1 / (r1 * r1 - 0.5f * r * r2);
+ float t_r = -r * u_r;
+
+ float g = -1.2684380046f * l + 2.6097574011f * m - 0.3413193965f * s - 1;
+ float g1 = -1.2684380046f * ldt + 2.6097574011f * mdt - 0.3413193965f * sdt;
+ float g2 = -1.2684380046f * ldt2 + 2.6097574011f * mdt2 - 0.3413193965f * sdt2;
+
+ float u_g = g1 / (g1 * g1 - 0.5f * g * g2);
+ float t_g = -g * u_g;
+
+ b = -0.0041960863f * l - 0.7034186147f * m + 1.7076147010f * s - 1;
+ float b1 = -0.0041960863f * ldt - 0.7034186147f * mdt + 1.7076147010f * sdt;
+ float b2 = -0.0041960863f * ldt2 - 0.7034186147f * mdt2 + 1.7076147010f * sdt2;
+
+ float u_b = b1 / (b1 * b1 - 0.5f * b * b2);
+ float t_b = -b * u_b;
+
+ t_r = u_r >= 0.f ? t_r : FLT_MAX;
+ t_g = u_g >= 0.f ? t_g : FLT_MAX;
+ t_b = u_b >= 0.f ? t_b : FLT_MAX;
+
+ t += fmin(t_r, fmin(t_g, t_b));
+ }
+ }
+ }
+
+ return t;
+}
+
+float find_gamut_intersection(float a, float b, float L1, float C1, float L0)
+{
+ // Find the cusp of the gamut triangle
+ LC cusp = find_cusp(a, b);
+
+ return find_gamut_intersection(a, b, L1, C1, L0, cusp);
+}
+
+RGB gamut_clip_preserve_chroma(RGB rgb)
+{
+ if (rgb.r < 1 && rgb.g < 1 && rgb.b < 1 && rgb.r > 0 && rgb.g > 0 && rgb.b > 0)
+ return rgb;
+
+ Lab lab = linear_srgb_to_oklab(rgb);
+
+ float L = lab.L;
+ float eps = 0.00001f;
+ float C = fmax(eps, sqrtf(lab.a * lab.a + lab.b * lab.b));
+ float a_ = lab.a / C;
+ float b_ = lab.b / C;
+
+ float L0 = clamp(L, 0, 1);
+
+ float t = find_gamut_intersection(a_, b_, L, C, L0);
+ float L_clipped = L0 * (1 - t) + t * L;
+ float C_clipped = t * C;
+
+ return oklab_to_linear_srgb({ L_clipped, C_clipped * a_, C_clipped * b_ });
+}
+
+RGB gamut_clip_project_to_0_5(RGB rgb)
+{
+ if (rgb.r < 1 && rgb.g < 1 && rgb.b < 1 && rgb.r > 0 && rgb.g > 0 && rgb.b > 0)
+ return rgb;
+
+ Lab lab = linear_srgb_to_oklab(rgb);
+
+ float L = lab.L;
+ float eps = 0.00001f;
+ float C = fmax(eps, sqrtf(lab.a * lab.a + lab.b * lab.b));
+ float a_ = lab.a / C;
+ float b_ = lab.b / C;
+
+ float L0 = 0.5;
+
+ float t = find_gamut_intersection(a_, b_, L, C, L0);
+ float L_clipped = L0 * (1 - t) + t * L;
+ float C_clipped = t * C;
+
+ return oklab_to_linear_srgb({ L_clipped, C_clipped * a_, C_clipped * b_ });
+}
+
+RGB gamut_clip_project_to_L_cusp(RGB rgb)
+{
+ if (rgb.r < 1 && rgb.g < 1 && rgb.b < 1 && rgb.r > 0 && rgb.g > 0 && rgb.b > 0)
+ return rgb;
+
+ Lab lab = linear_srgb_to_oklab(rgb);
+
+ float L = lab.L;
+ float eps = 0.00001f;
+ float C = fmax(eps, sqrtf(lab.a * lab.a + lab.b * lab.b));
+ float a_ = lab.a / C;
+ float b_ = lab.b / C;
+
+ // The cusp is computed here and in find_gamut_intersection, an optimized solution would only compute it once.
+ LC cusp = find_cusp(a_, b_);
+
+ float L0 = cusp.L;
+
+ float t = find_gamut_intersection(a_, b_, L, C, L0);
+
+ float L_clipped = L0 * (1 - t) + t * L;
+ float C_clipped = t * C;
+
+ return oklab_to_linear_srgb({ L_clipped, C_clipped * a_, C_clipped * b_ });
+}
+
+RGB gamut_clip_adaptive_L0_0_5(RGB rgb, float alpha = 0.05f)
+{
+ if (rgb.r < 1 && rgb.g < 1 && rgb.b < 1 && rgb.r > 0 && rgb.g > 0 && rgb.b > 0)
+ return rgb;
+
+ Lab lab = linear_srgb_to_oklab(rgb);
+
+ float L = lab.L;
+ float eps = 0.00001f;
+ float C = fmax(eps, sqrtf(lab.a * lab.a + lab.b * lab.b));
+ float a_ = lab.a / C;
+ float b_ = lab.b / C;
+
+ float Ld = L - 0.5f;
+ float e1 = 0.5f + fabs(Ld) + alpha * C;
+ float L0 = 0.5f * (1.f + sgn(Ld) * (e1 - sqrtf(e1 * e1 - 2.f * fabs(Ld))));
+
+ float t = find_gamut_intersection(a_, b_, L, C, L0);
+ float L_clipped = L0 * (1.f - t) + t * L;
+ float C_clipped = t * C;
+
+ return oklab_to_linear_srgb({ L_clipped, C_clipped * a_, C_clipped * b_ });
+}
+
+RGB gamut_clip_adaptive_L0_L_cusp(RGB rgb, float alpha = 0.05f)
+{
+ if (rgb.r < 1 && rgb.g < 1 && rgb.b < 1 && rgb.r > 0 && rgb.g > 0 && rgb.b > 0)
+ return rgb;
+
+ Lab lab = linear_srgb_to_oklab(rgb);
+
+ float L = lab.L;
+ float eps = 0.00001f;
+ float C = fmax(eps, sqrtf(lab.a * lab.a + lab.b * lab.b));
+ float a_ = lab.a / C;
+ float b_ = lab.b / C;
+
+ // The cusp is computed here and in find_gamut_intersection, an optimized solution would only compute it once.
+ LC cusp = find_cusp(a_, b_);
+
+ float Ld = L - cusp.L;
+ float k = 2.f * (Ld > 0 ? 1.f - cusp.L : cusp.L);
+
+ float e1 = 0.5f * k + fabs(Ld) + alpha * C / k;
+ float L0 = cusp.L + 0.5f * (sgn(Ld) * (e1 - sqrtf(e1 * e1 - 2.f * k * fabs(Ld))));
+
+ float t = find_gamut_intersection(a_, b_, L, C, L0);
+ float L_clipped = L0 * (1.f - t) + t * L;
+ float C_clipped = t * C;
+
+ return oklab_to_linear_srgb({ L_clipped, C_clipped * a_, C_clipped * b_ });
+}
+
+float toe(float x)
+{
+ constexpr float k_1 = 0.206f;
+ constexpr float k_2 = 0.03f;
+ constexpr float k_3 = (1.f + k_1) / (1.f + k_2);
+ return 0.5f * (k_3 * x - k_1 + sqrtf((k_3 * x - k_1) * (k_3 * x - k_1) + 4 * k_2 * k_3 * x));
+}
+
+float toe_inv(float x)
+{
+ constexpr float k_1 = 0.206f;
+ constexpr float k_2 = 0.03f;
+ constexpr float k_3 = (1.f + k_1) / (1.f + k_2);
+ return (x * x + k_1 * x) / (k_3 * (x + k_2));
+}
+
+ST to_ST(LC cusp)
+{
+ float L = cusp.L;
+ float C = cusp.C;
+ return { C / L, C / (1 - L) };
+}
+
+// Returns a smooth approximation of the location of the cusp
+// This polynomial was created by an optimization process
+// It has been designed so that S_mid < S_max and T_mid < T_max
+ST get_ST_mid(float a_, float b_)
+{
+ float S = 0.11516993f + 1.f / (
+ +7.44778970f + 4.15901240f * b_
+ + a_ * (-2.19557347f + 1.75198401f * b_
+ + a_ * (-2.13704948f - 10.02301043f * b_
+ + a_ * (-4.24894561f + 5.38770819f * b_ + 4.69891013f * a_
+ )))
+ );
+
+ float T = 0.11239642f + 1.f / (
+ +1.61320320f - 0.68124379f * b_
+ + a_ * (+0.40370612f + 0.90148123f * b_
+ + a_ * (-0.27087943f + 0.61223990f * b_
+ + a_ * (+0.00299215f - 0.45399568f * b_ - 0.14661872f * a_
+ )))
+ );
+
+ return { S, T };
+}
+
+struct Cs { float C_0; float C_mid; float C_max; };
+Cs get_Cs(float L, float a_, float b_)
+{
+ LC cusp = find_cusp(a_, b_);
+
+ float C_max = find_gamut_intersection(a_, b_, L, 1, L, cusp);
+ ST ST_max = to_ST(cusp);
+
+ // Scale factor to compensate for the curved part of gamut shape:
+ float k = C_max / fmin((L * ST_max.S), (1 - L) * ST_max.T);
+
+ float C_mid;
+ {
+ ST ST_mid = get_ST_mid(a_, b_);
+
+ // Use a soft minimum function, instead of a sharp triangle shape to get a smooth value for chroma.
+ float C_a = L * ST_mid.S;
+ float C_b = (1.f - L) * ST_mid.T;
+ C_mid = 0.9f * k * sqrtf(sqrtf(1.f / (1.f / (C_a * C_a * C_a * C_a) + 1.f / (C_b * C_b * C_b * C_b))));
+ }
+
+ float C_0;
+ {
+ // for C_0, the shape is independent of hue, so ST are constant. Values picked to roughly be the average values of ST.
+ float C_a = L * 0.4f;
+ float C_b = (1.f - L) * 0.8f;
+
+ // Use a soft minimum function, instead of a sharp triangle shape to get a smooth value for chroma.
+ C_0 = sqrtf(1.f / (1.f / (C_a * C_a) + 1.f / (C_b * C_b)));
+ }
+
+ return { C_0, C_mid, C_max };
+}
+
+RGB okhsl_to_srgb(HSL hsl)
+{
+ float h = hsl.h;
+ float s = hsl.s;
+ float l = hsl.l;
+
+ if (l == 1.0f)
+ {
+ return { 1.f, 1.f, 1.f };
+ }
+
+ else if (l == 0.f)
+ {
+ return { 0.f, 0.f, 0.f };
+ }
+
+ float a_ = cosf(2.f * pi * h);
+ float b_ = sinf(2.f * pi * h);
+ float L = toe_inv(l);
+
+ Cs cs = get_Cs(L, a_, b_);
+ float C_0 = cs.C_0;
+ float C_mid = cs.C_mid;
+ float C_max = cs.C_max;
+
+ float mid = 0.8f;
+ float mid_inv = 1.25f;
+
+ float C, t, k_0, k_1, k_2;
+
+ if (s < mid)
+ {
+ t = mid_inv * s;
+
+ k_1 = mid * C_0;
+ k_2 = (1.f - k_1 / C_mid);
+
+ C = t * k_1 / (1.f - k_2 * t);
+ }
+ else
+ {
+ t = (s - mid)/ (1 - mid);
+
+ k_0 = C_mid;
+ k_1 = (1.f - mid) * C_mid * C_mid * mid_inv * mid_inv / C_0;
+ k_2 = (1.f - (k_1) / (C_max - C_mid));
+
+ C = k_0 + t * k_1 / (1.f - k_2 * t);
+ }
+
+ RGB rgb = oklab_to_linear_srgb({ L, C * a_, C * b_ });
+ return {
+ srgb_transfer_function(rgb.r),
+ srgb_transfer_function(rgb.g),
+ srgb_transfer_function(rgb.b),
+ };
+}
+
+HSL srgb_to_okhsl(RGB rgb)
+{
+ Lab lab = linear_srgb_to_oklab({
+ srgb_transfer_function_inv(rgb.r),
+ srgb_transfer_function_inv(rgb.g),
+ srgb_transfer_function_inv(rgb.b)
+ });
+
+ float C = sqrtf(lab.a * lab.a + lab.b * lab.b);
+ float a_ = lab.a / C;
+ float b_ = lab.b / C;
+
+ float L = lab.L;
+ float h = 0.5f + 0.5f * atan2f(-lab.b, -lab.a) / pi;
+
+ Cs cs = get_Cs(L, a_, b_);
+ float C_0 = cs.C_0;
+ float C_mid = cs.C_mid;
+ float C_max = cs.C_max;
+
+ // Inverse of the interpolation in okhsl_to_srgb:
+
+ float mid = 0.8f;
+ float mid_inv = 1.25f;
+
+ float s;
+ if (C < C_mid)
+ {
+ float k_1 = mid * C_0;
+ float k_2 = (1.f - k_1 / C_mid);
+
+ float t = C / (k_1 + k_2 * C);
+ s = t * mid;
+ }
+ else
+ {
+ float k_0 = C_mid;
+ float k_1 = (1.f - mid) * C_mid * C_mid * mid_inv * mid_inv / C_0;
+ float k_2 = (1.f - (k_1) / (C_max - C_mid));
+
+ float t = (C - k_0) / (k_1 + k_2 * (C - k_0));
+ s = mid + (1.f - mid) * t;
+ }
+
+ float l = toe(L);
+ return { h, s, l };
+}
+
+
+RGB okhsv_to_srgb(HSV hsv)
+{
+ float h = hsv.h;
+ float s = hsv.s;
+ float v = hsv.v;
+
+ float a_ = cosf(2.f * pi * h);
+ float b_ = sinf(2.f * pi * h);
+
+ LC cusp = find_cusp(a_, b_);
+ ST ST_max = to_ST(cusp);
+ float S_max = ST_max.S;
+ float T_max = ST_max.T;
+ float S_0 = 0.5f;
+ float k = 1 - S_0 / S_max;
+
+ // first we compute L and V as if the gamut is a perfect triangle:
+
+ // L, C when v==1:
+ float L_v = 1 - s * S_0 / (S_0 + T_max - T_max * k * s);
+ float C_v = s * T_max * S_0 / (S_0 + T_max - T_max * k * s);
+
+ float L = v * L_v;
+ float C = v * C_v;
+
+ // then we compensate for both toe and the curved top part of the triangle:
+ float L_vt = toe_inv(L_v);
+ float C_vt = C_v * L_vt / L_v;
+
+ float L_new = toe_inv(L);
+ C = C * L_new / L;
+ L = L_new;
+
+ RGB rgb_scale = oklab_to_linear_srgb({ L_vt, a_ * C_vt, b_ * C_vt });
+ float scale_L = cbrtf(1.f / fmax(fmax(rgb_scale.r, rgb_scale.g), fmax(rgb_scale.b, 0.f)));
+
+ L = L * scale_L;
+ C = C * scale_L;
+
+ RGB rgb = oklab_to_linear_srgb({ L, C * a_, C * b_ });
+ return {
+ srgb_transfer_function(rgb.r),
+ srgb_transfer_function(rgb.g),
+ srgb_transfer_function(rgb.b),
+ };
+}
+
+HSV srgb_to_okhsv(RGB rgb)
+{
+ Lab lab = linear_srgb_to_oklab({
+ srgb_transfer_function_inv(rgb.r),
+ srgb_transfer_function_inv(rgb.g),
+ srgb_transfer_function_inv(rgb.b)
+ });
+
+ float C = sqrtf(lab.a * lab.a + lab.b * lab.b);
+ float a_ = lab.a / C;
+ float b_ = lab.b / C;
+
+ float L = lab.L;
+ float h = 0.5f + 0.5f * atan2f(-lab.b, -lab.a) / pi;
+
+ LC cusp = find_cusp(a_, b_);
+ ST ST_max = to_ST(cusp);
+ float S_max = ST_max.S;
+ float T_max = ST_max.T;
+ float S_0 = 0.5f;
+ float k = 1 - S_0 / S_max;
+
+ // first we find L_v, C_v, L_vt and C_vt
+
+ float t = T_max / (C + L * T_max);
+ float L_v = t * L;
+ float C_v = t * C;
+
+ float L_vt = toe_inv(L_v);
+ float C_vt = C_v * L_vt / L_v;
+
+ // we can then use these to invert the step that compensates for the toe and the curved top part of the triangle:
+ RGB rgb_scale = oklab_to_linear_srgb({ L_vt, a_ * C_vt, b_ * C_vt });
+ float scale_L = cbrtf(1.f / fmax(fmax(rgb_scale.r, rgb_scale.g), fmax(rgb_scale.b, 0.f)));
+
+ L = L / scale_L;
+ C = C / scale_L;
+
+ C = C * toe(L) / L;
+ L = toe(L);
+
+ // we can now compute v and s:
+
+ float v = L / L_v;
+ float s = (S_0 + T_max) * C_v / ((T_max * S_0) + T_max * k * C_v);
+
+ return { h, s, v };
+}
+
+};
+#endif // OK_COLOR_H
diff --git a/thirdparty/misc/ok_color_shader.h b/thirdparty/misc/ok_color_shader.h
new file mode 100644
index 0000000000..40d83366ee
--- /dev/null
+++ b/thirdparty/misc/ok_color_shader.h
@@ -0,0 +1,663 @@
+// Copyright(c) 2021 Björn Ottosson
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of
+// this software and associated documentation files(the "Software"), to deal in
+// the Software without restriction, including without limitation the rights to
+// use, copy, modify, merge, publish, distribute, sublicense, and /or sell copies
+// of the Software, and to permit persons to whom the Software is furnished to do
+// so, subject to the following conditions :
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+
+#ifndef OK_COLOR_SHADER_H
+#define OK_COLOR_SHADER_H
+
+#include "core/string/ustring.h"
+
+static String OK_COLOR_SHADER = R"(shader_type canvas_item;
+
+const float M_PI = 3.1415926535897932384626433832795;
+
+float cbrt( float x )
+{
+ return sign(x)*pow(abs(x),1.0f/3.0f);
+}
+
+float srgb_transfer_function(float a)
+{
+ return .0031308f >= a ? 12.92f * a : 1.055f * pow(a, .4166666666666667f) - .055f;
+}
+
+float srgb_transfer_function_inv(float a)
+{
+ return .04045f < a ? pow((a + .055f) / 1.055f, 2.4f) : a / 12.92f;
+}
+
+vec3 linear_srgb_to_oklab(vec3 c)
+{
+ float l = 0.4122214708f * c.r + 0.5363325363f * c.g + 0.0514459929f * c.b;
+ float m = 0.2119034982f * c.r + 0.6806995451f * c.g + 0.1073969566f * c.b;
+ float s = 0.0883024619f * c.r + 0.2817188376f * c.g + 0.6299787005f * c.b;
+
+ float l_ = cbrt(l);
+ float m_ = cbrt(m);
+ float s_ = cbrt(s);
+
+ return vec3(
+ 0.2104542553f * l_ + 0.7936177850f * m_ - 0.0040720468f * s_,
+ 1.9779984951f * l_ - 2.4285922050f * m_ + 0.4505937099f * s_,
+ 0.0259040371f * l_ + 0.7827717662f * m_ - 0.8086757660f * s_
+ );
+}
+
+vec3 oklab_to_linear_srgb(vec3 c)
+{
+ float l_ = c.x + 0.3963377774f * c.y + 0.2158037573f * c.z;
+ float m_ = c.x - 0.1055613458f * c.y - 0.0638541728f * c.z;
+ float s_ = c.x - 0.0894841775f * c.y - 1.2914855480f * c.z;
+
+ float l = l_ * l_ * l_;
+ float m = m_ * m_ * m_;
+ float s = s_ * s_ * s_;
+
+ return vec3(
+ +4.0767416621f * l - 3.3077115913f * m + 0.2309699292f * s,
+ -1.2684380046f * l + 2.6097574011f * m - 0.3413193965f * s,
+ -0.0041960863f * l - 0.7034186147f * m + 1.7076147010f * s
+ );
+}
+
+// Finds the maximum saturation possible for a given hue that fits in sRGB
+// Saturation here is defined as S = C/L
+// a and b must be normalized so a^2 + b^2 == 1
+float compute_max_saturation(float a, float b)
+{
+ // Max saturation will be when one of r, g or b goes below zero.
+
+ // Select different coefficients depending on which component goes below zero first
+ float k0, k1, k2, k3, k4, wl, wm, ws;
+
+ if (-1.88170328f * a - 0.80936493f * b > 1.f)
+ {
+ // Red component
+ k0 = +1.19086277f; k1 = +1.76576728f; k2 = +0.59662641f; k3 = +0.75515197f; k4 = +0.56771245f;
+ wl = +4.0767416621f; wm = -3.3077115913f; ws = +0.2309699292f;
+ }
+ else if (1.81444104f * a - 1.19445276f * b > 1.f)
+ {
+ // Green component
+ k0 = +0.73956515f; k1 = -0.45954404f; k2 = +0.08285427f; k3 = +0.12541070f; k4 = +0.14503204f;
+ wl = -1.2684380046f; wm = +2.6097574011f; ws = -0.3413193965f;
+ }
+ else
+ {
+ // Blue component
+ k0 = +1.35733652f; k1 = -0.00915799f; k2 = -1.15130210f; k3 = -0.50559606f; k4 = +0.00692167f;
+ wl = -0.0041960863f; wm = -0.7034186147f; ws = +1.7076147010f;
+ }
+
+ // Approximate max saturation using a polynomial:
+ float S = k0 + k1 * a + k2 * b + k3 * a * a + k4 * a * b;
+
+ // Do one step Halley's method to get closer
+ // this gives an error less than 10e6, except for some blue hues where the dS/dh is close to infinite
+ // this should be sufficient for most applications, otherwise do two/three steps
+
+ float k_l = +0.3963377774f * a + 0.2158037573f * b;
+ float k_m = -0.1055613458f * a - 0.0638541728f * b;
+ float k_s = -0.0894841775f * a - 1.2914855480f * b;
+
+ {
+ float l_ = 1.f + S * k_l;
+ float m_ = 1.f + S * k_m;
+ float s_ = 1.f + S * k_s;
+
+ float l = l_ * l_ * l_;
+ float m = m_ * m_ * m_;
+ float s = s_ * s_ * s_;
+
+ float l_dS = 3.f * k_l * l_ * l_;
+ float m_dS = 3.f * k_m * m_ * m_;
+ float s_dS = 3.f * k_s * s_ * s_;
+
+ float l_dS2 = 6.f * k_l * k_l * l_;
+ float m_dS2 = 6.f * k_m * k_m * m_;
+ float s_dS2 = 6.f * k_s * k_s * s_;
+
+ float f = wl * l + wm * m + ws * s;
+ float f1 = wl * l_dS + wm * m_dS + ws * s_dS;
+ float f2 = wl * l_dS2 + wm * m_dS2 + ws * s_dS2;
+
+ S = S - f * f1 / (f1 * f1 - 0.5f * f * f2);
+ }
+
+ return S;
+}
+
+// finds L_cusp and C_cusp for a given hue
+// a and b must be normalized so a^2 + b^2 == 1
+vec2 find_cusp(float a, float b)
+{
+ // First, find the maximum saturation (saturation S = C/L)
+ float S_cusp = compute_max_saturation(a, b);
+
+ // Convert to linear sRGB to find the first point where at least one of r,g or b >= 1:
+ vec3 rgb_at_max = oklab_to_linear_srgb(vec3( 1, S_cusp * a, S_cusp * b ));
+ float L_cusp = cbrt(1.f / max(max(rgb_at_max.r, rgb_at_max.g), rgb_at_max.b));
+ float C_cusp = L_cusp * S_cusp;
+
+ return vec2( L_cusp , C_cusp );
+} )"
+R"(// Finds intersection of the line defined by
+// L = L0 * (1 - t) + t * L1;
+// C = t * C1;
+// a and b must be normalized so a^2 + b^2 == 1
+float find_gamut_intersection(float a, float b, float L1, float C1, float L0, vec2 cusp)
+{
+ // Find the intersection for upper and lower half seprately
+ float t;
+ if (((L1 - L0) * cusp.y - (cusp.x - L0) * C1) <= 0.f)
+ {
+ // Lower half
+
+ t = cusp.y * L0 / (C1 * cusp.x + cusp.y * (L0 - L1));
+ }
+ else
+ {
+ // Upper half
+
+ // First intersect with triangle
+ t = cusp.y * (L0 - 1.f) / (C1 * (cusp.x - 1.f) + cusp.y * (L0 - L1));
+
+ // Then one step Halley's method
+ {
+ float dL = L1 - L0;
+ float dC = C1;
+
+ float k_l = +0.3963377774f * a + 0.2158037573f * b;
+ float k_m = -0.1055613458f * a - 0.0638541728f * b;
+ float k_s = -0.0894841775f * a - 1.2914855480f * b;
+
+ float l_dt = dL + dC * k_l;
+ float m_dt = dL + dC * k_m;
+ float s_dt = dL + dC * k_s;
+
+
+ // If higher accuracy is required, 2 or 3 iterations of the following block can be used:
+ {
+ float L = L0 * (1.f - t) + t * L1;
+ float C = t * C1;
+
+ float l_ = L + C * k_l;
+ float m_ = L + C * k_m;
+ float s_ = L + C * k_s;
+
+ float l = l_ * l_ * l_;
+ float m = m_ * m_ * m_;
+ float s = s_ * s_ * s_;
+
+ float ldt = 3.f * l_dt * l_ * l_;
+ float mdt = 3.f * m_dt * m_ * m_;
+ float sdt = 3.f * s_dt * s_ * s_;
+
+ float ldt2 = 6.f * l_dt * l_dt * l_;
+ float mdt2 = 6.f * m_dt * m_dt * m_;
+ float sdt2 = 6.f * s_dt * s_dt * s_;
+
+ float r = 4.0767416621f * l - 3.3077115913f * m + 0.2309699292f * s - 1.f;
+ float r1 = 4.0767416621f * ldt - 3.3077115913f * mdt + 0.2309699292f * sdt;
+ float r2 = 4.0767416621f * ldt2 - 3.3077115913f * mdt2 + 0.2309699292f * sdt2;
+
+ float u_r = r1 / (r1 * r1 - 0.5f * r * r2);
+ float t_r = -r * u_r;
+
+ float g = -1.2684380046f * l + 2.6097574011f * m - 0.3413193965f * s - 1.f;
+ float g1 = -1.2684380046f * ldt + 2.6097574011f * mdt - 0.3413193965f * sdt;
+ float g2 = -1.2684380046f * ldt2 + 2.6097574011f * mdt2 - 0.3413193965f * sdt2;
+
+ float u_g = g1 / (g1 * g1 - 0.5f * g * g2);
+ float t_g = -g * u_g;
+
+ float b = -0.0041960863f * l - 0.7034186147f * m + 1.7076147010f * s - 1.f;
+ float b1 = -0.0041960863f * ldt - 0.7034186147f * mdt + 1.7076147010f * sdt;
+ float b2 = -0.0041960863f * ldt2 - 0.7034186147f * mdt2 + 1.7076147010f * sdt2;
+
+ float u_b = b1 / (b1 * b1 - 0.5f * b * b2);
+ float t_b = -b * u_b;
+
+ t_r = u_r >= 0.f ? t_r : 10000.f;
+ t_g = u_g >= 0.f ? t_g : 10000.f;
+ t_b = u_b >= 0.f ? t_b : 10000.f;
+
+ t += min(t_r, min(t_g, t_b));
+ }
+ }
+ }
+
+ return t;
+}
+
+float find_gamut_intersection_5(float a, float b, float L1, float C1, float L0)
+{
+ // Find the cusp of the gamut triangle
+ vec2 cusp = find_cusp(a, b);
+
+ return find_gamut_intersection(a, b, L1, C1, L0, cusp);
+})"
+R"(
+
+vec3 gamut_clip_preserve_chroma(vec3 rgb)
+{
+ if (rgb.r < 1.f && rgb.g < 1.f && rgb.b < 1.f && rgb.r > 0.f && rgb.g > 0.f && rgb.b > 0.f)
+ return rgb;
+
+ vec3 lab = linear_srgb_to_oklab(rgb);
+
+ float L = lab.x;
+ float eps = 0.00001f;
+ float C = max(eps, sqrt(lab.y * lab.y + lab.z * lab.z));
+ float a_ = lab.y / C;
+ float b_ = lab.z / C;
+
+ float L0 = clamp(L, 0.f, 1.f);
+
+ float t = find_gamut_intersection_5(a_, b_, L, C, L0);
+ float L_clipped = L0 * (1.f - t) + t * L;
+ float C_clipped = t * C;
+
+ return oklab_to_linear_srgb(vec3( L_clipped, C_clipped * a_, C_clipped * b_ ));
+}
+
+vec3 gamut_clip_project_to_0_5(vec3 rgb)
+{
+ if (rgb.r < 1.f && rgb.g < 1.f && rgb.b < 1.f && rgb.r > 0.f && rgb.g > 0.f && rgb.b > 0.f)
+ return rgb;
+
+ vec3 lab = linear_srgb_to_oklab(rgb);
+
+ float L = lab.x;
+ float eps = 0.00001f;
+ float C = max(eps, sqrt(lab.y * lab.y + lab.z * lab.z));
+ float a_ = lab.y / C;
+ float b_ = lab.z / C;
+
+ float L0 = 0.5;
+
+ float t = find_gamut_intersection_5(a_, b_, L, C, L0);
+ float L_clipped = L0 * (1.f - t) + t * L;
+ float C_clipped = t * C;
+
+ return oklab_to_linear_srgb(vec3( L_clipped, C_clipped * a_, C_clipped * b_ ));
+}
+
+vec3 gamut_clip_project_to_L_cusp(vec3 rgb)
+{
+ if (rgb.r < 1.f && rgb.g < 1.f && rgb.b < 1.f && rgb.r > 0.f && rgb.g > 0.f && rgb.b > 0.f)
+ return rgb;
+
+ vec3 lab = linear_srgb_to_oklab(rgb);
+
+ float L = lab.x;
+ float eps = 0.00001f;
+ float C = max(eps, sqrt(lab.y * lab.y + lab.z * lab.z));
+ float a_ = lab.y / C;
+ float b_ = lab.z / C;
+
+ // The cusp is computed here and in find_gamut_intersection, an optimized solution would only compute it once.
+ vec2 cusp = find_cusp(a_, b_);
+
+ float L0 = cusp.x;
+
+ float t = find_gamut_intersection_5(a_, b_, L, C, L0);
+
+ float L_clipped = L0 * (1.f - t) + t * L;
+ float C_clipped = t * C;
+
+ return oklab_to_linear_srgb(vec3( L_clipped, C_clipped * a_, C_clipped * b_ ));
+}
+
+vec3 gamut_clip_adaptive_L0_0_5(vec3 rgb, float alpha)
+{
+ if (rgb.r < 1.f && rgb.g < 1.f && rgb.b < 1.f && rgb.r > 0.f && rgb.g > 0.f && rgb.b > 0.f)
+ return rgb;
+
+ vec3 lab = linear_srgb_to_oklab(rgb);
+
+ float L = lab.x;
+ float eps = 0.00001f;
+ float C = max(eps, sqrt(lab.y * lab.y + lab.z * lab.z));
+ float a_ = lab.y / C;
+ float b_ = lab.z / C;
+
+ float Ld = L - 0.5f;
+ float e1 = 0.5f + abs(Ld) + alpha * C;
+ float L0 = 0.5f * (1.f + sign(Ld) * (e1 - sqrt(e1 * e1 - 2.f * abs(Ld))));
+
+ float t = find_gamut_intersection_5(a_, b_, L, C, L0);
+ float L_clipped = L0 * (1.f - t) + t * L;
+ float C_clipped = t * C;
+
+ return oklab_to_linear_srgb(vec3( L_clipped, C_clipped * a_, C_clipped * b_ ));
+}
+
+vec3 gamut_clip_adaptive_L0_L_cusp(vec3 rgb, float alpha)
+{
+ if (rgb.r < 1.f && rgb.g < 1.f && rgb.b < 1.f && rgb.r > 0.f && rgb.g > 0.f && rgb.b > 0.f)
+ return rgb;
+
+ vec3 lab = linear_srgb_to_oklab(rgb);
+
+ float L = lab.x;
+ float eps = 0.00001f;
+ float C = max(eps, sqrt(lab.y * lab.y + lab.z * lab.z));
+ float a_ = lab.y / C;
+ float b_ = lab.z / C;
+
+ // The cusp is computed here and in find_gamut_intersection, an optimized solution would only compute it once.
+ vec2 cusp = find_cusp(a_, b_);
+
+ float Ld = L - cusp.x;
+ float k = 2.f * (Ld > 0.f ? 1.f - cusp.x : cusp.x);
+
+ float e1 = 0.5f * k + abs(Ld) + alpha * C / k;
+ float L0 = cusp.x + 0.5f * (sign(Ld) * (e1 - sqrt(e1 * e1 - 2.f * k * abs(Ld))));
+
+ float t = find_gamut_intersection_5(a_, b_, L, C, L0);
+ float L_clipped = L0 * (1.f - t) + t * L;
+ float C_clipped = t * C;
+
+ return oklab_to_linear_srgb(vec3( L_clipped, C_clipped * a_, C_clipped * b_ ));
+}
+
+float toe(float x)
+{
+ float k_1 = 0.206f;
+ float k_2 = 0.03f;
+ float k_3 = (1.f + k_1) / (1.f + k_2);
+ return 0.5f * (k_3 * x - k_1 + sqrt((k_3 * x - k_1) * (k_3 * x - k_1) + 4.f * k_2 * k_3 * x));
+}
+
+float toe_inv(float x)
+{
+ float k_1 = 0.206f;
+ float k_2 = 0.03f;
+ float k_3 = (1.f + k_1) / (1.f + k_2);
+ return (x * x + k_1 * x) / (k_3 * (x + k_2));
+}
+)"
+R"(vec2 to_ST(vec2 cusp)
+{
+ float L = cusp.x;
+ float C = cusp.y;
+ return vec2( C / L, C / (1.f - L) );
+}
+
+// Returns a smooth approximation of the location of the cusp
+// This polynomial was created by an optimization process
+// It has been designed so that S_mid < S_max and T_mid < T_max
+vec2 get_ST_mid(float a_, float b_)
+{
+ float S = 0.11516993f + 1.f / (
+ +7.44778970f + 4.15901240f * b_
+ + a_ * (-2.19557347f + 1.75198401f * b_
+ + a_ * (-2.13704948f - 10.02301043f * b_
+ + a_ * (-4.24894561f + 5.38770819f * b_ + 4.69891013f * a_
+ )))
+ );
+
+ float T = 0.11239642f + 1.f / (
+ +1.61320320f - 0.68124379f * b_
+ + a_ * (+0.40370612f + 0.90148123f * b_
+ + a_ * (-0.27087943f + 0.61223990f * b_
+ + a_ * (+0.00299215f - 0.45399568f * b_ - 0.14661872f * a_
+ )))
+ );
+
+ return vec2( S, T );
+}
+
+vec3 get_Cs(float L, float a_, float b_)
+{
+ vec2 cusp = find_cusp(a_, b_);
+
+ float C_max = find_gamut_intersection(a_, b_, L, 1.f, L, cusp);
+ vec2 ST_max = to_ST(cusp);
+
+ // Scale factor to compensate for the curved part of gamut shape:
+ float k = C_max / min((L * ST_max.x), (1.f - L) * ST_max.y);
+
+ float C_mid;
+ {
+ vec2 ST_mid = get_ST_mid(a_, b_);
+
+ // Use a soft minimum function, instead of a sharp triangle shape to get a smooth value for chroma.
+ float C_a = L * ST_mid.x;
+ float C_b = (1.f - L) * ST_mid.y;
+ C_mid = 0.9f * k * sqrt(sqrt(1.f / (1.f / (C_a * C_a * C_a * C_a) + 1.f / (C_b * C_b * C_b * C_b))));
+ }
+
+ float C_0;
+ {
+ // for C_0, the shape is independent of hue, so vec2 are constant. Values picked to roughly be the average values of vec2.
+ float C_a = L * 0.4f;
+ float C_b = (1.f - L) * 0.8f;
+
+ // Use a soft minimum function, instead of a sharp triangle shape to get a smooth value for chroma.
+ C_0 = sqrt(1.f / (1.f / (C_a * C_a) + 1.f / (C_b * C_b)));
+ }
+
+ return vec3( C_0, C_mid, C_max );
+}
+
+vec3 okhsl_to_srgb(vec3 hsl)
+{
+ float h = hsl.x;
+ float s = hsl.y;
+ float l = hsl.z;
+
+ if (l == 1.0f)
+ {
+ return vec3( 1.f, 1.f, 1.f );
+ }
+
+ else if (l == 0.f)
+ {
+ return vec3( 0.f, 0.f, 0.f );
+ }
+
+ float a_ = cos(2.f * M_PI * h);
+ float b_ = sin(2.f * M_PI * h);
+ float L = toe_inv(l);
+
+ vec3 cs = get_Cs(L, a_, b_);
+ float C_0 = cs.x;
+ float C_mid = cs.y;
+ float C_max = cs.z;
+
+ float mid = 0.8f;
+ float mid_inv = 1.25f;
+
+ float C, t, k_0, k_1, k_2;
+
+ if (s < mid)
+ {
+ t = mid_inv * s;
+
+ k_1 = mid * C_0;
+ k_2 = (1.f - k_1 / C_mid);
+
+ C = t * k_1 / (1.f - k_2 * t);
+ }
+ else
+ {
+ t = (s - mid)/ (1.f - mid);
+
+ k_0 = C_mid;
+ k_1 = (1.f - mid) * C_mid * C_mid * mid_inv * mid_inv / C_0;
+ k_2 = (1.f - (k_1) / (C_max - C_mid));
+
+ C = k_0 + t * k_1 / (1.f - k_2 * t);
+ }
+
+ vec3 rgb = oklab_to_linear_srgb(vec3( L, C * a_, C * b_ ));
+ return vec3(
+ srgb_transfer_function(rgb.r),
+ srgb_transfer_function(rgb.g),
+ srgb_transfer_function(rgb.b)
+ );
+}
+
+vec3 srgb_to_okhsl(vec3 rgb)
+{
+ vec3 lab = linear_srgb_to_oklab(vec3(
+ srgb_transfer_function_inv(rgb.r),
+ srgb_transfer_function_inv(rgb.g),
+ srgb_transfer_function_inv(rgb.b)
+ ));
+
+ float C = sqrt(lab.y * lab.y + lab.z * lab.z);
+ float a_ = lab.y / C;
+ float b_ = lab.z / C;
+
+ float L = lab.x;
+ float h = 0.5f + 0.5f * atan(-lab.z, -lab.y) / M_PI;
+
+ vec3 cs = get_Cs(L, a_, b_);
+ float C_0 = cs.x;
+ float C_mid = cs.y;
+ float C_max = cs.z;
+
+ // Inverse of the interpolation in okhsl_to_srgb:
+
+ float mid = 0.8f;
+ float mid_inv = 1.25f;
+
+ float s;
+ if (C < C_mid)
+ {
+ float k_1 = mid * C_0;
+ float k_2 = (1.f - k_1 / C_mid);
+
+ float t = C / (k_1 + k_2 * C);
+ s = t * mid;
+ }
+ else
+ {
+ float k_0 = C_mid;
+ float k_1 = (1.f - mid) * C_mid * C_mid * mid_inv * mid_inv / C_0;
+ float k_2 = (1.f - (k_1) / (C_max - C_mid));
+
+ float t = (C - k_0) / (k_1 + k_2 * (C - k_0));
+ s = mid + (1.f - mid) * t;
+ }
+
+ float l = toe(L);
+ return vec3( h, s, l );
+}
+
+
+vec3 okhsv_to_srgb(vec3 hsv)
+{
+ float h = hsv.x;
+ float s = hsv.y;
+ float v = hsv.z;
+
+ float a_ = cos(2.f * M_PI * h);
+ float b_ = sin(2.f * M_PI * h);
+
+ vec2 cusp = find_cusp(a_, b_);
+ vec2 ST_max = to_ST(cusp);
+ float S_max = ST_max.x;
+ float T_max = ST_max.y;
+ float S_0 = 0.5f;
+ float k = 1.f- S_0 / S_max;
+
+ // first we compute L and V as if the gamut is a perfect triangle:
+
+ // L, C when v==1:
+ float L_v = 1.f - s * S_0 / (S_0 + T_max - T_max * k * s);
+ float C_v = s * T_max * S_0 / (S_0 + T_max - T_max * k * s);
+
+ float L = v * L_v;
+ float C = v * C_v;
+
+ // then we compensate for both toe and the curved top part of the triangle:
+ float L_vt = toe_inv(L_v);
+ float C_vt = C_v * L_vt / L_v;
+
+ float L_new = toe_inv(L);
+ C = C * L_new / L;
+ L = L_new;
+
+ vec3 rgb_scale = oklab_to_linear_srgb(vec3( L_vt, a_ * C_vt, b_ * C_vt ));
+ float scale_L = cbrt(1.f / max(max(rgb_scale.r, rgb_scale.g), max(rgb_scale.b, 0.f)));
+
+ L = L * scale_L;
+ C = C * scale_L;
+
+ vec3 rgb = oklab_to_linear_srgb(vec3( L, C * a_, C * b_ ));
+ return vec3(
+ srgb_transfer_function(rgb.r),
+ srgb_transfer_function(rgb.g),
+ srgb_transfer_function(rgb.b)
+ );
+}
+)"
+R"(
+vec3 srgb_to_okhsv(vec3 rgb)
+{
+ vec3 lab = linear_srgb_to_oklab(vec3(
+ srgb_transfer_function_inv(rgb.r),
+ srgb_transfer_function_inv(rgb.g),
+ srgb_transfer_function_inv(rgb.b)
+ ));
+
+ float C = sqrt(lab.y * lab.y + lab.z * lab.z);
+ float a_ = lab.y / C;
+ float b_ = lab.z / C;
+
+ float L = lab.x;
+ float h = 0.5f + 0.5f * atan(-lab.z, -lab.y) / M_PI;
+
+ vec2 cusp = find_cusp(a_, b_);
+ vec2 ST_max = to_ST(cusp);
+ float S_max = ST_max.x;
+ float T_max = ST_max.y;
+ float S_0 = 0.5f;
+ float k = 1.f - S_0 / S_max;
+
+ // first we find L_v, C_v, L_vt and C_vt
+
+ float t = T_max / (C + L * T_max);
+ float L_v = t * L;
+ float C_v = t * C;
+
+ float L_vt = toe_inv(L_v);
+ float C_vt = C_v * L_vt / L_v;
+
+ // we can then use these to invert the step that compensates for the toe and the curved top part of the triangle:
+ vec3 rgb_scale = oklab_to_linear_srgb(vec3( L_vt, a_ * C_vt, b_ * C_vt ));
+ float scale_L = cbrt(1.f / max(max(rgb_scale.r, rgb_scale.g), max(rgb_scale.b, 0.f)));
+
+ L = L / scale_L;
+ C = C / scale_L;
+
+ C = C * toe(L) / L;
+ L = toe(L);
+
+ // we can now compute v and s:
+
+ float v = L / L_v;
+ float s = (S_0 + T_max) * C_v / ((T_max * S_0) + T_max * k * C_v);
+
+ return vec3 (h, s, v );
+})";
+
+#endif