summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--core/input/input_event.cpp9
-rw-r--r--core/input/input_event.h2
-rw-r--r--core/input/input_map.cpp4
-rw-r--r--doc/classes/Node.xml3
-rw-r--r--doc/classes/ProjectSettings.xml2
-rw-r--r--doc/classes/Viewport.xml2
-rw-r--r--editor/code_editor.cpp1
-rw-r--r--editor/debugger/editor_profiler.cpp6
-rw-r--r--editor/debugger/editor_visual_profiler.cpp6
-rw-r--r--editor/editor_help.cpp2
-rw-r--r--editor/filesystem_dock.cpp133
-rw-r--r--editor/filesystem_dock.h19
-rw-r--r--misc/extension_api_validation/4.0-stable.expected8
-rw-r--r--modules/gltf/doc_classes/GLTFSkeleton.xml2
-rw-r--r--platform/android/doc_classes/EditorExportPlatformAndroid.xml5
-rw-r--r--platform/android/export/export_plugin.cpp1
-rw-r--r--platform/ios/doc_classes/EditorExportPlatformIOS.xml50
-rw-r--r--platform/linuxbsd/x11/display_server_x11.cpp21
-rw-r--r--platform/macos/doc_classes/EditorExportPlatformMacOS.xml2
-rw-r--r--platform/macos/export/export_plugin.cpp2
-rw-r--r--platform/windows/doc_classes/EditorExportPlatformWindows.xml4
-rw-r--r--platform/windows/export/export_plugin.cpp11
-rw-r--r--scene/gui/popup_menu.cpp93
-rw-r--r--scene/gui/popup_menu.h4
-rw-r--r--scene/gui/slider.cpp63
-rw-r--r--scene/gui/slider.h4
-rw-r--r--scene/gui/spin_box.cpp3
-rw-r--r--scene/main/node.cpp8
-rw-r--r--scene/main/viewport.cpp2
-rw-r--r--scene/main/viewport.h2
-rw-r--r--scene/resources/gradient.cpp8
-rw-r--r--servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp2
-rw-r--r--thirdparty/README.md2
-rw-r--r--thirdparty/tinyexr/tinyexr.h406
34 files changed, 710 insertions, 182 deletions
diff --git a/core/input/input_event.cpp b/core/input/input_event.cpp
index 95be01bc65..6010c2a2b4 100644
--- a/core/input/input_event.cpp
+++ b/core/input/input_event.cpp
@@ -1109,6 +1109,15 @@ String InputEventJoypadMotion::to_string() {
return vformat("InputEventJoypadMotion: axis=%d, axis_value=%.2f", axis, axis_value);
}
+Ref<InputEventJoypadMotion> InputEventJoypadMotion::create_reference(JoyAxis p_axis, float p_value) {
+ Ref<InputEventJoypadMotion> ie;
+ ie.instantiate();
+ ie->set_axis(p_axis);
+ ie->set_axis_value(p_value);
+
+ return ie;
+}
+
void InputEventJoypadMotion::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_axis", "axis"), &InputEventJoypadMotion::set_axis);
ClassDB::bind_method(D_METHOD("get_axis"), &InputEventJoypadMotion::get_axis);
diff --git a/core/input/input_event.h b/core/input/input_event.h
index 59a87239bd..e9d4fb8325 100644
--- a/core/input/input_event.h
+++ b/core/input/input_event.h
@@ -319,6 +319,8 @@ public:
virtual String as_text() const override;
virtual String to_string() override;
+ static Ref<InputEventJoypadMotion> create_reference(JoyAxis p_axis, float p_value);
+
InputEventJoypadMotion() {}
};
diff --git a/core/input/input_map.cpp b/core/input/input_map.cpp
index 910778324c..ddfde0e7cd 100644
--- a/core/input/input_map.cpp
+++ b/core/input/input_map.cpp
@@ -399,21 +399,25 @@ const HashMap<String, List<Ref<InputEvent>>> &InputMap::get_builtins() {
inputs = List<Ref<InputEvent>>();
inputs.push_back(InputEventKey::create_reference(Key::LEFT));
inputs.push_back(InputEventJoypadButton::create_reference(JoyButton::DPAD_LEFT));
+ inputs.push_back(InputEventJoypadMotion::create_reference(JoyAxis::LEFT_X, -1.0));
default_builtin_cache.insert("ui_left", inputs);
inputs = List<Ref<InputEvent>>();
inputs.push_back(InputEventKey::create_reference(Key::RIGHT));
inputs.push_back(InputEventJoypadButton::create_reference(JoyButton::DPAD_RIGHT));
+ inputs.push_back(InputEventJoypadMotion::create_reference(JoyAxis::LEFT_X, 1.0));
default_builtin_cache.insert("ui_right", inputs);
inputs = List<Ref<InputEvent>>();
inputs.push_back(InputEventKey::create_reference(Key::UP));
inputs.push_back(InputEventJoypadButton::create_reference(JoyButton::DPAD_UP));
+ inputs.push_back(InputEventJoypadMotion::create_reference(JoyAxis::LEFT_Y, -1.0));
default_builtin_cache.insert("ui_up", inputs);
inputs = List<Ref<InputEvent>>();
inputs.push_back(InputEventKey::create_reference(Key::DOWN));
inputs.push_back(InputEventJoypadButton::create_reference(JoyButton::DPAD_DOWN));
+ inputs.push_back(InputEventJoypadMotion::create_reference(JoyAxis::LEFT_Y, 1.0));
default_builtin_cache.insert("ui_down", inputs);
inputs = List<Ref<InputEvent>>();
diff --git a/doc/classes/Node.xml b/doc/classes/Node.xml
index 4ea96f97e5..c6a3217e0e 100644
--- a/doc/classes/Node.xml
+++ b/doc/classes/Node.xml
@@ -654,7 +654,8 @@
<method name="queue_free">
<return type="void" />
<description>
- Queues a node for deletion at the end of the current frame. When deleted, all of its child nodes will be deleted as well. This method ensures it's safe to delete the node, contrary to [method Object.free]. Use [method Object.is_queued_for_deletion] to check whether a node will be deleted at the end of the frame.
+ Queues a node for deletion at the end of the current frame. When deleted, all of its child nodes will be deleted as well, and all references to the node and its children will become invalid, see [method Object.free].
+ It is safe to call [method queue_free] multiple times per frame on a node, and to [method Object.free] a node that is currently queued for deletion. Use [method Object.is_queued_for_deletion] to check whether a node will be deleted at the end of the frame.
</description>
</method>
<method name="remove_child">
diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml
index 360e35bfcf..8aebf5f637 100644
--- a/doc/classes/ProjectSettings.xml
+++ b/doc/classes/ProjectSettings.xml
@@ -268,7 +268,7 @@
The project's description, displayed as a tooltip in the Project Manager when hovering the project.
</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.
+ Icon used for the project, set when project loads. Exporters will also use this icon as a fallback if necessary.
</member>
<member name="application/config/macos_native_icon" type="String" setter="" getter="" default="&quot;&quot;">
Icon set in [code].icns[/code] format used on macOS to set the game's icon. This is done automatically on start by calling [method DisplayServer.set_native_icon].
diff --git a/doc/classes/Viewport.xml b/doc/classes/Viewport.xml
index d363a11550..66df40c18f 100644
--- a/doc/classes/Viewport.xml
+++ b/doc/classes/Viewport.xml
@@ -115,7 +115,7 @@
Returns the drag data from the GUI, that was previously returned by [method Control._get_drag_data].
</description>
</method>
- <method name="gui_get_focus_owner">
+ <method name="gui_get_focus_owner" qualifiers="const">
<return type="Control" />
<description>
Returns the [Control] having the focus within this viewport. If no [Control] has the focus, returns null.
diff --git a/editor/code_editor.cpp b/editor/code_editor.cpp
index fe45894c52..9bcfea2a7f 100644
--- a/editor/code_editor.cpp
+++ b/editor/code_editor.cpp
@@ -1962,6 +1962,7 @@ CodeTextEditor::CodeTextEditor() {
add_child(text_editor);
text_editor->set_v_size_flags(SIZE_EXPAND_FILL);
text_editor->set_structured_text_bidi_override(TextServer::STRUCTURED_TEXT_GDSCRIPT);
+ text_editor->set_draw_bookmarks_gutter(true);
int ot_mode = EDITOR_GET("interface/editor/code_font_contextual_ligatures");
Ref<FontVariation> fc = text_editor->get_theme_font(SNAME("font"));
diff --git a/editor/debugger/editor_profiler.cpp b/editor/debugger/editor_profiler.cpp
index b0d6135d52..c81518057a 100644
--- a/editor/debugger/editor_profiler.cpp
+++ b/editor/debugger/editor_profiler.cpp
@@ -660,15 +660,15 @@ EditorProfiler::EditorProfiler() {
variables->set_column_title(0, TTR("Name"));
variables->set_column_expand(0, true);
variables->set_column_clip_content(0, true);
- variables->set_column_expand_ratio(0, 60);
+ variables->set_column_custom_minimum_width(0, 60);
variables->set_column_title(1, TTR("Time"));
variables->set_column_expand(1, false);
variables->set_column_clip_content(1, true);
- variables->set_column_expand_ratio(1, 100);
+ variables->set_column_custom_minimum_width(1, 75 * EDSCALE);
variables->set_column_title(2, TTR("Calls"));
variables->set_column_expand(2, false);
variables->set_column_clip_content(2, true);
- variables->set_column_expand_ratio(2, 60);
+ variables->set_column_custom_minimum_width(2, 50 * EDSCALE);
variables->connect("item_edited", callable_mp(this, &EditorProfiler::_item_edited));
graph = memnew(TextureRect);
diff --git a/editor/debugger/editor_visual_profiler.cpp b/editor/debugger/editor_visual_profiler.cpp
index 1a06e85f90..2ecb029f1a 100644
--- a/editor/debugger/editor_visual_profiler.cpp
+++ b/editor/debugger/editor_visual_profiler.cpp
@@ -114,7 +114,7 @@ String EditorVisualProfiler::_get_time_as_text(float p_time) {
int dmode = display_mode->get_selected();
if (dmode == DISPLAY_FRAME_TIME) {
- return TS->format_number(rtos(p_time)) + " " + RTR("ms");
+ return TS->format_number(String::num(p_time, 2)) + " " + RTR("ms");
} else if (dmode == DISPLAY_FRAME_PERCENT) {
return TS->format_number(String::num(p_time * 100 / graph_limit, 2)) + " " + TS->percent_sign();
}
@@ -790,11 +790,11 @@ EditorVisualProfiler::EditorVisualProfiler() {
variables->set_column_title(1, TTR("CPU"));
variables->set_column_expand(1, false);
variables->set_column_clip_content(1, true);
- variables->set_column_custom_minimum_width(1, 60 * EDSCALE);
+ variables->set_column_custom_minimum_width(1, 75 * EDSCALE);
variables->set_column_title(2, TTR("GPU"));
variables->set_column_expand(2, false);
variables->set_column_clip_content(2, true);
- variables->set_column_custom_minimum_width(2, 60 * EDSCALE);
+ variables->set_column_custom_minimum_width(2, 75 * EDSCALE);
variables->connect("cell_selected", callable_mp(this, &EditorVisualProfiler::_item_selected));
graph = memnew(TextureRect);
diff --git a/editor/editor_help.cpp b/editor/editor_help.cpp
index 17aa763983..e709371ec6 100644
--- a/editor/editor_help.cpp
+++ b/editor/editor_help.cpp
@@ -65,7 +65,7 @@ protected:
ClassDB::bind_method(D_METHOD("get_classes"), &DocCache::get_classes);
ADD_PROPERTY(PropertyInfo(Variant::STRING, "version_hash"), "set_version_hash", "get_version_hash");
- ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "classes"), "set_classes", "get_classes");
+ ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "classes"), "set_classes", "get_classes");
}
public:
diff --git a/editor/filesystem_dock.cpp b/editor/filesystem_dock.cpp
index f21229edc8..b0351d1598 100644
--- a/editor/filesystem_dock.cpp
+++ b/editor/filesystem_dock.cpp
@@ -76,6 +76,94 @@ Control *FileSystemList::make_custom_tooltip(const String &p_text) const {
return FileSystemDock::get_singleton()->create_tooltip_for_path(get_item_metadata(idx));
}
+void FileSystemList::_line_editor_submit(String p_text) {
+ popup_editor->hide();
+
+ emit_signal(SNAME("item_edited"));
+ queue_redraw();
+}
+
+bool FileSystemList::edit_selected() {
+ ERR_FAIL_COND_V_MSG(!is_anything_selected(), false, "No item selected.");
+ int s = get_current();
+ ensure_current_is_visible();
+
+ Rect2 rect;
+ Rect2 popup_rect;
+ Vector2 ofs;
+
+ Vector2 icon_size = get_item_icon(s)->get_size();
+
+ // Handles the different icon modes (TOP/LEFT).
+ switch (get_icon_mode()) {
+ case ItemList::ICON_MODE_LEFT:
+ rect = get_item_rect(s, true);
+ ofs = Vector2(0, Math::floor((MAX(line_editor->get_minimum_size().height, rect.size.height) - rect.size.height) / 2));
+ popup_rect.position = get_screen_position() + rect.position - ofs;
+ popup_rect.size = rect.size;
+
+ // Adjust for icon position and size.
+ popup_rect.size.x -= icon_size.x;
+ popup_rect.position.x += icon_size.x;
+ break;
+ case ItemList::ICON_MODE_TOP:
+ rect = get_item_rect(s, false);
+ popup_rect.position = get_screen_position() + rect.position;
+ popup_rect.size = rect.size;
+
+ // Adjust for icon position and size.
+ popup_rect.size.y -= icon_size.y;
+ popup_rect.position.y += icon_size.y;
+ break;
+ }
+
+ popup_editor->set_position(popup_rect.position);
+ popup_editor->set_size(popup_rect.size);
+
+ String name = get_item_text(s);
+ line_editor->set_text(name);
+ line_editor->select(0, name.rfind("."));
+
+ popup_editor->popup();
+ popup_editor->child_controls_changed();
+ line_editor->grab_focus();
+ return true;
+}
+
+String FileSystemList::get_edit_text() {
+ return line_editor->get_text();
+}
+
+void FileSystemList::_text_editor_popup_modal_close() {
+ if (Input::get_singleton()->is_key_pressed(Key::ESCAPE) ||
+ Input::get_singleton()->is_key_pressed(Key::KP_ENTER) ||
+ Input::get_singleton()->is_key_pressed(Key::ENTER)) {
+ return;
+ }
+
+ _line_editor_submit(line_editor->get_text());
+}
+
+void FileSystemList::_bind_methods() {
+ ADD_SIGNAL(MethodInfo("item_edited"));
+}
+
+FileSystemList::FileSystemList() {
+ popup_editor = memnew(Popup);
+ add_child(popup_editor);
+
+ popup_editor_vb = memnew(VBoxContainer);
+ popup_editor_vb->add_theme_constant_override("separation", 0);
+ popup_editor_vb->set_anchors_and_offsets_preset(PRESET_FULL_RECT);
+ popup_editor->add_child(popup_editor_vb);
+
+ line_editor = memnew(LineEdit);
+ line_editor->set_v_size_flags(SIZE_EXPAND_FILL);
+ popup_editor_vb->add_child(line_editor);
+ line_editor->connect("text_submitted", callable_mp(this, &FileSystemList::_line_editor_submit));
+ popup_editor->connect("popup_hide", callable_mp(this, &FileSystemList::_text_editor_popup_modal_close));
+}
+
FileSystemDock *FileSystemDock::singleton = nullptr;
Ref<Texture2D> FileSystemDock::_get_tree_item_icon(bool p_is_valid, String p_file_type) {
@@ -1581,13 +1669,15 @@ void FileSystemDock::_folder_removed(String p_folder) {
}
void FileSystemDock::_rename_operation_confirm() {
- if (!tree->is_anything_selected()) {
- return;
- }
+ String new_name;
TreeItem *s = tree->get_selected();
int col_index = tree->get_selected_column();
- String new_name = s->get_text(col_index);
- new_name = new_name.strip_edges();
+
+ if (tree->has_focus()) {
+ new_name = s->get_text(col_index).strip_edges();
+ } else if (files->has_focus()) {
+ new_name = files->get_edit_text().strip_edges();
+ }
String old_name = to_rename.is_file ? to_rename.path.get_file() : to_rename.path.left(-1).get_file();
bool rename_error = false;
@@ -1607,10 +1697,12 @@ void FileSystemDock::_rename_operation_confirm() {
}
}
- // Restores Tree to restore original names.
- if (rename_error) {
+ // Restore original name.
+ if (rename_error && tree->has_focus()) {
s->set_text(col_index, old_name);
return;
+ } else if (rename_error && files->has_focus()) {
+ return;
}
String old_path = to_rename.path.ends_with("/") ? to_rename.path.left(-1) : to_rename.path;
@@ -2032,21 +2124,27 @@ void FileSystemDock::_file_option(int p_option, const Vector<String> &p_selected
} break;
case FILE_RENAME: {
- if (tree->is_anything_selected() && !p_selected.is_empty()) {
+ if (!p_selected.is_empty()) {
// Set to_rename variable for callback execution.
to_rename.path = p_selected[0];
to_rename.is_file = !to_rename.path.ends_with("/");
+ if (to_rename.path == "res://") {
+ break;
+ }
- // Edit node in Tree.
- tree->grab_focus();
- tree->edit_selected(true);
+ if (tree->has_focus()) {
+ // Edit node in Tree.
+ tree->edit_selected(true);
- if (to_rename.is_file) {
- String name = to_rename.path.get_file();
- tree->set_editor_selection(0, name.rfind("."));
- } else {
- String name = to_rename.path.left(-1).get_file(); // Removes the "/" suffix for folders.
- tree->set_editor_selection(0, name.length());
+ if (to_rename.is_file) {
+ String name = to_rename.path.get_file();
+ tree->set_editor_selection(0, name.rfind("."));
+ } else {
+ String name = to_rename.path.left(-1).get_file(); // Removes the "/" suffix for folders.
+ tree->set_editor_selection(0, name.length());
+ }
+ } else if (files->has_focus()) {
+ files->edit_selected();
}
}
} break;
@@ -3329,6 +3427,7 @@ FileSystemDock::FileSystemDock() {
files->connect("gui_input", callable_mp(this, &FileSystemDock::_file_list_gui_input));
files->connect("multi_selected", callable_mp(this, &FileSystemDock::_file_multi_selected));
files->connect("empty_clicked", callable_mp(this, &FileSystemDock::_file_list_empty_clicked));
+ files->connect("item_edited", callable_mp(this, &FileSystemDock::_rename_operation_confirm));
files->set_custom_minimum_size(Size2(0, 15 * EDSCALE));
files->set_allow_rmb_select(true);
file_list_vb->add_child(files);
diff --git a/editor/filesystem_dock.h b/editor/filesystem_dock.h
index 480e0db84d..0af3193aa5 100644
--- a/editor/filesystem_dock.h
+++ b/editor/filesystem_dock.h
@@ -57,7 +57,24 @@ class FileSystemTree : public Tree {
};
class FileSystemList : public ItemList {
- virtual Control *make_custom_tooltip(const String &p_text) const;
+ GDCLASS(FileSystemList, ItemList);
+
+ VBoxContainer *popup_editor_vb = nullptr;
+ Popup *popup_editor = nullptr;
+ LineEdit *line_editor = nullptr;
+
+ virtual Control *make_custom_tooltip(const String &p_text) const override;
+ void _line_editor_submit(String p_text);
+ void _text_editor_popup_modal_close();
+
+protected:
+ static void _bind_methods();
+
+public:
+ bool edit_selected();
+ String get_edit_text();
+
+ FileSystemList();
};
class FileSystemDock : public VBoxContainer {
diff --git a/misc/extension_api_validation/4.0-stable.expected b/misc/extension_api_validation/4.0-stable.expected
index 88d41160ce..2dad9359d4 100644
--- a/misc/extension_api_validation/4.0-stable.expected
+++ b/misc/extension_api_validation/4.0-stable.expected
@@ -6,6 +6,14 @@ should instead be used to justify these changes and describe how users should wo
========================================================================================================================
+GH-77757
+--------
+Validate extension JSON: Error: Field 'classes/Viewport/methods/gui_get_focus_owner': is_const changed value in new API, from false to true.
+Validate extension JSON: Error: Hash changed for 'classes/Viewport/methods/gui_get_focus_owner', from 31757941 to A5E188F5. This means that the function has changed and no compatibility function was provided.
+
+This method does not affect the state of Viewport so it should be const.
+
+
GH-74736
--------
Validate extension JSON: Error: Field 'classes/MenuBar/properties/start_index': type changed value in new API, from "bool" to "int".
diff --git a/modules/gltf/doc_classes/GLTFSkeleton.xml b/modules/gltf/doc_classes/GLTFSkeleton.xml
index eb06249e45..8073db3ce9 100644
--- a/modules/gltf/doc_classes/GLTFSkeleton.xml
+++ b/modules/gltf/doc_classes/GLTFSkeleton.xml
@@ -21,6 +21,7 @@
<method name="get_godot_bone_node">
<return type="Dictionary" />
<description>
+ Returns a [Dictionary] that maps skeleton bone indices to the indices of GLTF nodes. This property is unused during import, and only set during export. In a GLTF file, a bone is a node, so Godot converts skeleton bones to GLTF nodes.
</description>
</method>
<method name="get_godot_skeleton">
@@ -37,6 +38,7 @@
<return type="void" />
<param index="0" name="godot_bone_node" type="Dictionary" />
<description>
+ Sets a [Dictionary] that maps skeleton bone indices to the indices of GLTF nodes. This property is unused during import, and only set during export. In a GLTF file, a bone is a node, so Godot converts skeleton bones to GLTF nodes.
</description>
</method>
<method name="set_unique_names">
diff --git a/platform/android/doc_classes/EditorExportPlatformAndroid.xml b/platform/android/doc_classes/EditorExportPlatformAndroid.xml
index 0652ba5c0e..9d68cb78f6 100644
--- a/platform/android/doc_classes/EditorExportPlatformAndroid.xml
+++ b/platform/android/doc_classes/EditorExportPlatformAndroid.xml
@@ -59,14 +59,17 @@
<member name="keystore/debug" type="String" setter="" getter="">
Path of the debug keystore file.
Can be overridden with the environment variable [code]GODOT_ANDROID_KEYSTORE_DEBUG_PATH[/code].
+ Fallbacks to [code]EditorSettings.export/android/debug_keystore[/code] if empty.
</member>
<member name="keystore/debug_password" type="String" setter="" getter="">
Password for the debug keystore file.
Can be overridden with the environment variable [code]GODOT_ANDROID_KEYSTORE_DEBUG_PASSWORD[/code].
+ Fallbacks to [code]EditorSettings.export/android/debug_keystore_pass[/code] if both it and [member keystore/debug] are empty.
</member>
<member name="keystore/debug_user" type="String" setter="" getter="">
User name for the debug keystore file.
Can be overridden with the environment variable [code]GODOT_ANDROID_KEYSTORE_DEBUG_USER[/code].
+ Fallbacks to [code]EditorSettings.export/android/debug_keystore_user[/code] if both it and [member keystore/debug] are empty.
</member>
<member name="keystore/release" type="String" setter="" getter="">
Path of the release keystore file.
@@ -87,7 +90,7 @@
Foreground layer of the application adaptive icon file.
</member>
<member name="launcher_icons/main_192x192" type="String" setter="" getter="">
- Application icon file. If left empty, project icon is used instead.
+ Application icon file. If left empty, it will fallback to [member ProjectSettings.application/config/icon].
</member>
<member name="package/app_category" type="int" setter="" getter="">
Application category for the Play Store.
diff --git a/platform/android/export/export_plugin.cpp b/platform/android/export/export_plugin.cpp
index d8dd453faf..71b8a2b09b 100644
--- a/platform/android/export/export_plugin.cpp
+++ b/platform/android/export/export_plugin.cpp
@@ -2542,7 +2542,6 @@ Error EditorExportPlatformAndroid::sign_apk(const Ref<EditorExportPreset> &p_pre
if (ep.step(vformat(TTR("Signing debug %s..."), export_label), 104)) {
return ERR_SKIP;
}
-
} else {
keystore = release_keystore;
password = release_password;
diff --git a/platform/ios/doc_classes/EditorExportPlatformIOS.xml b/platform/ios/doc_classes/EditorExportPlatformIOS.xml
index 381884067b..b37868e543 100644
--- a/platform/ios/doc_classes/EditorExportPlatformIOS.xml
+++ b/platform/ios/doc_classes/EditorExportPlatformIOS.xml
@@ -69,73 +69,73 @@
Path to the custom export template. If left empty, default template is used.
</member>
<member name="icons/app_store_1024x1024" type="String" setter="" getter="">
- App Store application icon file. If left empty, project icon is used instead. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url].
+ App Store application icon file. If left empty, it will fallback to [member ProjectSettings.application/config/icon]. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url].
</member>
<member name="icons/ipad_76x76" type="String" setter="" getter="">
- Home screen application icon file on iPad (1x DPI). If left empty, project icon is used instead. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url].
+ Home screen application icon file on iPad (1x DPI). If left empty, it will fallback to [member ProjectSettings.application/config/icon]. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url].
</member>
<member name="icons/ipad_152x152" type="String" setter="" getter="">
- Home screen application icon file on iPad (2x DPI). If left empty, project icon is used instead. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url].
+ Home screen application icon file on iPad (2x DPI). If left empty, it will fallback to [member ProjectSettings.application/config/icon]. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url].
</member>
<member name="icons/ipad_167x167" type="String" setter="" getter="">
- Home screen application icon file on iPad (3x DPI). If left empty, project icon is used instead. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url].
+ Home screen application icon file on iPad (3x DPI). If left empty, it will fallback to [member ProjectSettings.application/config/icon]. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url].
</member>
<member name="icons/iphone_120x120" type="String" setter="" getter="">
- Home screen application icon file on iPhone (2x DPI). If left empty, project icon is used instead. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url].
+ Home screen application icon file on iPhone (2x DPI). If left empty, it will fallback to [member ProjectSettings.application/config/icon]. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url].
</member>
<member name="icons/iphone_180x180" type="String" setter="" getter="">
- Home screen application icon file on iPhone (3x DPI). If left empty, project icon is used instead. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url].
+ Home screen application icon file on iPhone (3x DPI). If left empty, it will fallback to [member ProjectSettings.application/config/icon]. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url].
</member>
<member name="icons/notification_40x40" type="String" setter="" getter="">
- Notification icon file on iPad and iPhone (2x DPI). If left empty, project icon is used instead. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url].
+ Notification icon file on iPad and iPhone (2x DPI). If left empty, it will fallback to [member ProjectSettings.application/config/icon]. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url].
</member>
<member name="icons/notification_60x60" type="String" setter="" getter="">
- Notification icon file on iPhone (3x DPI). If left empty, project icon is used instead. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url].
+ Notification icon file on iPhone (3x DPI). If left empty, it will fallback to [member ProjectSettings.application/config/icon]. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url].
</member>
<member name="icons/settings_58x58" type="String" setter="" getter="">
- Application settings icon file on iPad and iPhone (2x DPI). If left empty, project icon is used instead. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url].
+ Application settings icon file on iPad and iPhone (2x DPI). If left empty, it will fallback to [member ProjectSettings.application/config/icon]. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url].
</member>
<member name="icons/settings_87x87" type="String" setter="" getter="">
- Application settings icon file on iPhone (3x DPI). If left empty, project icon is used instead. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url].
+ Application settings icon file on iPhone (3x DPI). If left empty, it will fallback to [member ProjectSettings.application/config/icon]. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url].
</member>
<member name="icons/spotlight_40x40" type="String" setter="" getter="">
- Spotlight icon file on iPad (1x DPI). If left empty, project icon is used instead. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url].
+ Spotlight icon file on iPad (1x DPI). If left empty, it will fallback to [member ProjectSettings.application/config/icon]. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url].
</member>
<member name="icons/spotlight_80x80" type="String" setter="" getter="">
- Spotlight icon file on iPad and iPhone (2x DPI). If left empty, project icon is used instead. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url].
+ Spotlight icon file on iPad and iPhone (2x DPI). If left empty, it will fallback to [member ProjectSettings.application/config/icon]. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url].
</member>
<member name="landscape_launch_screens/ipad_1024x768" type="String" setter="" getter="">
- Application launch screen image file, if left empty project splash screen is used instead.
+ Application launch screen image file. If left empty, it will fallback to [member ProjectSettings.application/boot_splash/image].
</member>
<member name="landscape_launch_screens/ipad_2048x1536" type="String" setter="" getter="">
- Application launch screen image file, if left empty project splash screen is used instead.
+ Application launch screen image file. If left empty, it will fallback to [member ProjectSettings.application/boot_splash/image].
</member>
<member name="landscape_launch_screens/iphone_2208x1242" type="String" setter="" getter="">
- Application launch screen image file, if left empty project splash screen is used instead.
+ Application launch screen image file. If left empty, it will fallback to [member ProjectSettings.application/boot_splash/image].
</member>
<member name="landscape_launch_screens/iphone_2436x1125" type="String" setter="" getter="">
- Application launch screen image file, if left empty project splash screen is used instead.
+ Application launch screen image file. If left empty, it will fallback to [member ProjectSettings.application/boot_splash/image].
</member>
<member name="portrait_launch_screens/ipad_768x1024" type="String" setter="" getter="">
- Application launch screen image file, if left empty project splash screen is used instead.
+ Application launch screen image file. If left empty, it will fallback to [member ProjectSettings.application/boot_splash/image].
</member>
<member name="portrait_launch_screens/ipad_1536x2048" type="String" setter="" getter="">
- Application launch screen image file, if left empty project splash screen is used instead.
+ Application launch screen image file. If left empty, it will fallback to [member ProjectSettings.application/boot_splash/image].
</member>
<member name="portrait_launch_screens/iphone_640x960" type="String" setter="" getter="">
- Application launch screen image file, if left empty project splash screen is used instead.
+ Application launch screen image file. If left empty, it will fallback to [member ProjectSettings.application/boot_splash/image].
</member>
<member name="portrait_launch_screens/iphone_640x1136" type="String" setter="" getter="">
- Application launch screen image file, if left empty project splash screen is used instead.
+ Application launch screen image file. If left empty, it will fallback to [member ProjectSettings.application/boot_splash/image].
</member>
<member name="portrait_launch_screens/iphone_750x1334" type="String" setter="" getter="">
- Application launch screen image file, if left empty project splash screen is used instead.
+ Application launch screen image file. If left empty, it will fallback to [member ProjectSettings.application/boot_splash/image].
</member>
<member name="portrait_launch_screens/iphone_1125x2436" type="String" setter="" getter="">
- Application launch screen image file, if left empty project splash screen is used instead.
+ Application launch screen image file. If left empty, it will fallback to [member ProjectSettings.application/boot_splash/image].
</member>
<member name="portrait_launch_screens/iphone_1242x2208" type="String" setter="" getter="">
- Application launch screen image file, if left empty project splash screen is used instead.
+ Application launch screen image file. If left empty, it will fallback to [member ProjectSettings.application/boot_splash/image].
</member>
<member name="privacy/camera_usage_description" type="String" setter="" getter="">
A message displayed when requesting access to the device's camera (in English).
@@ -159,10 +159,10 @@
A custom background color of the storyboard launch screen.
</member>
<member name="storyboard/custom_image@2x" type="String" setter="" getter="">
- Application launch screen image file (2x DPI), if left empty project splash screen is used instead.
+ Application launch screen image file (2x DPI). If left empty, it will fallback to [member ProjectSettings.application/boot_splash/image].
</member>
<member name="storyboard/custom_image@3x" type="String" setter="" getter="">
- Application launch screen image file (3x DPI), if left empty project splash screen is used instead.
+ Application launch screen image file (3x DPI). If left empty, it will fallback to [member ProjectSettings.application/boot_splash/image].
</member>
<member name="storyboard/image_scale_mode" type="int" setter="" getter="">
Launch screen image scaling mode.
diff --git a/platform/linuxbsd/x11/display_server_x11.cpp b/platform/linuxbsd/x11/display_server_x11.cpp
index 507acfcf34..fa35340ebb 100644
--- a/platform/linuxbsd/x11/display_server_x11.cpp
+++ b/platform/linuxbsd/x11/display_server_x11.cpp
@@ -855,12 +855,30 @@ Size2i DisplayServerX11::screen_get_size(int p_screen) const {
return _screen_get_rect(p_screen).size;
}
+// A Handler to avoid crashing on non-fatal X errors by default.
+//
+// The original X11 error formatter `_XPrintDefaultError` is defined here:
+// https://gitlab.freedesktop.org/xorg/lib/libx11/-/blob/e45ca7b41dcd3ace7681d6897505f85d374640f2/src/XlibInt.c#L1322
+// It is not exposed through the API, accesses X11 internals,
+// and is much more complex, so this is a less complete simplified error X11 printer.
+int default_window_error_handler(Display *display, XErrorEvent *error) {
+ static char message[1024];
+ XGetErrorText(display, error->error_code, message, sizeof(message));
+
+ ERR_PRINT(vformat("Unhandled XServer error: %s"
+ "\n Major opcode of failed request: %d"
+ "\n Serial number of failed request: %d"
+ "\n Current serial number in output stream: %d",
+ String::utf8(message), error->request_code, error->minor_code, error->serial));
+ return 0;
+}
+
bool g_bad_window = false;
int bad_window_error_handler(Display *display, XErrorEvent *error) {
if (error->error_code == BadWindow) {
g_bad_window = true;
} else {
- ERR_PRINT("Unhandled XServer error code: " + itos(error->error_code));
+ return default_window_error_handler(display, error);
}
return 0;
}
@@ -5930,6 +5948,7 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode
portal_desktop = memnew(FreeDesktopPortalDesktop);
#endif
+ XSetErrorHandler(&default_window_error_handler);
r_error = OK;
}
diff --git a/platform/macos/doc_classes/EditorExportPlatformMacOS.xml b/platform/macos/doc_classes/EditorExportPlatformMacOS.xml
index 64e1efde26..d030995ee7 100644
--- a/platform/macos/doc_classes/EditorExportPlatformMacOS.xml
+++ b/platform/macos/doc_classes/EditorExportPlatformMacOS.xml
@@ -23,7 +23,7 @@
Copyright notice for the bundle visible to the user (localized).
</member>
<member name="application/icon" type="String" setter="" getter="">
- Application icon file. If left empty, project icon is used instead.
+ Application icon file. If left empty, it will fallback to [member ProjectSettings.application/config/macos_native_icon], and then to [member ProjectSettings.application/config/icon].
</member>
<member name="application/icon_interpolation" type="int" setter="" getter="">
Interpolation method used to resize application icon.
diff --git a/platform/macos/export/export_plugin.cpp b/platform/macos/export/export_plugin.cpp
index bef204cd49..f0fa5f2d36 100644
--- a/platform/macos/export/export_plugin.cpp
+++ b/platform/macos/export/export_plugin.cpp
@@ -1677,6 +1677,8 @@ Error EditorExportPlatformMacOS::export_project(const Ref<EditorExportPreset> &p
String icon_path;
if (p_preset->get("application/icon") != "") {
icon_path = p_preset->get("application/icon");
+ } else if (GLOBAL_GET("application/config/macos_native_icon") != "") {
+ icon_path = GLOBAL_GET("application/config/macos_native_icon");
} else {
icon_path = GLOBAL_GET("application/config/icon");
}
diff --git a/platform/windows/doc_classes/EditorExportPlatformWindows.xml b/platform/windows/doc_classes/EditorExportPlatformWindows.xml
index ec2b105f58..4bdeed19ad 100644
--- a/platform/windows/doc_classes/EditorExportPlatformWindows.xml
+++ b/platform/windows/doc_classes/EditorExportPlatformWindows.xml
@@ -13,7 +13,7 @@
Company that produced the application. Required. See [url=https://learn.microsoft.com/en-us/windows/win32/menurc/stringfileinfo-block]StringFileInfo[/url].
</member>
<member name="application/console_wrapper_icon" type="String" setter="" getter="">
- Console wrapper icon file. If left empty, application icon is used instead.
+ Console wrapper icon file. If left empty, it will fallback to [member application/icon], then to [member ProjectSettings.application/config/windows_native_icon], and lastly, [member ProjectSettings.application/config/icon].
</member>
<member name="application/copyright" type="String" setter="" getter="">
Copyright notice for the bundle visible to the user. Optional. See [url=https://learn.microsoft.com/en-us/windows/win32/menurc/stringfileinfo-block]StringFileInfo[/url].
@@ -25,7 +25,7 @@
Version number of the file. Required. See [url=https://learn.microsoft.com/en-us/windows/win32/menurc/stringfileinfo-block]StringFileInfo[/url].
</member>
<member name="application/icon" type="String" setter="" getter="">
- Application icon file. If left empty, project icon is used instead.
+ Application icon file. If left empty, it will fallback to [member ProjectSettings.application/config/windows_native_icon], and then to [member ProjectSettings.application/config/icon].
</member>
<member name="application/icon_interpolation" type="int" setter="" getter="">
Interpolation method used to resize application icon.
diff --git a/platform/windows/export/export_plugin.cpp b/platform/windows/export/export_plugin.cpp
index ca390236fb..2ac41af3a2 100644
--- a/platform/windows/export/export_plugin.cpp
+++ b/platform/windows/export/export_plugin.cpp
@@ -399,7 +399,16 @@ Error EditorExportPlatformWindows::_rcedit_add_data(const Ref<EditorExportPreset
}
#endif
- String icon_path = ProjectSettings::get_singleton()->globalize_path(p_preset->get("application/icon"));
+ String icon_path;
+ if (p_preset->get("application/icon") != "") {
+ icon_path = p_preset->get("application/icon");
+ } else if (GLOBAL_GET("application/config/windows_native_icon") != "") {
+ icon_path = GLOBAL_GET("application/config/windows_native_icon");
+ } else {
+ icon_path = GLOBAL_GET("application/config/icon");
+ }
+ icon_path = ProjectSettings::get_singleton()->globalize_path(icon_path);
+
if (p_console_icon) {
String console_icon_path = ProjectSettings::get_singleton()->globalize_path(p_preset->get("application/console_wrapper_icon"));
if (!console_icon_path.is_empty() && FileAccess::exists(console_icon_path)) {
diff --git a/scene/gui/popup_menu.cpp b/scene/gui/popup_menu.cpp
index 3776c30de6..f6797b7f64 100644
--- a/scene/gui/popup_menu.cpp
+++ b/scene/gui/popup_menu.cpp
@@ -295,7 +295,18 @@ void PopupMenu::gui_input(const Ref<InputEvent> &p_event) {
ERR_FAIL_COND(p_event.is_null());
if (!items.is_empty()) {
+ Input *input = Input::get_singleton();
+ Ref<InputEventJoypadMotion> joypadmotion_event = p_event;
+ Ref<InputEventJoypadButton> joypadbutton_event = p_event;
+ bool is_joypad_event = (joypadmotion_event.is_valid() || joypadbutton_event.is_valid());
+
if (p_event->is_action("ui_down", true) && p_event->is_pressed()) {
+ if (is_joypad_event) {
+ if (!input->is_action_just_pressed("ui_down", true)) {
+ return;
+ }
+ set_process_internal(true);
+ }
int search_from = mouse_over + 1;
if (search_from >= items.size()) {
search_from = 0;
@@ -328,6 +339,12 @@ void PopupMenu::gui_input(const Ref<InputEvent> &p_event) {
}
}
} else if (p_event->is_action("ui_up", true) && p_event->is_pressed()) {
+ if (is_joypad_event) {
+ if (!input->is_action_just_pressed("ui_up", true)) {
+ return;
+ }
+ set_process_internal(true);
+ }
int search_from = mouse_over - 1;
if (search_from < 0) {
search_from = items.size() - 1;
@@ -907,6 +924,82 @@ void PopupMenu::_notification(int p_what) {
} break;
case NOTIFICATION_INTERNAL_PROCESS: {
+ Input *input = Input::get_singleton();
+
+ if (input->is_action_just_released("ui_up") || input->is_action_just_released("ui_down")) {
+ gamepad_event_delay_ms = DEFAULT_GAMEPAD_EVENT_DELAY_MS;
+ set_process_internal(false);
+ return;
+ }
+ gamepad_event_delay_ms -= get_process_delta_time();
+ if (gamepad_event_delay_ms <= 0) {
+ if (input->is_action_pressed("ui_down")) {
+ gamepad_event_delay_ms = GAMEPAD_EVENT_REPEAT_RATE_MS + gamepad_event_delay_ms;
+ int search_from = mouse_over + 1;
+ if (search_from >= items.size()) {
+ search_from = 0;
+ }
+
+ bool match_found = false;
+ for (int i = search_from; i < items.size(); i++) {
+ if (!items[i].separator && !items[i].disabled) {
+ mouse_over = i;
+ emit_signal(SNAME("id_focused"), i);
+ scroll_to_item(i);
+ control->queue_redraw();
+ match_found = true;
+ break;
+ }
+ }
+
+ if (!match_found) {
+ // If the last item is not selectable, try re-searching from the start.
+ for (int i = 0; i < search_from; i++) {
+ if (!items[i].separator && !items[i].disabled) {
+ mouse_over = i;
+ emit_signal(SNAME("id_focused"), i);
+ scroll_to_item(i);
+ control->queue_redraw();
+ break;
+ }
+ }
+ }
+ }
+
+ if (input->is_action_pressed("ui_up")) {
+ gamepad_event_delay_ms = GAMEPAD_EVENT_REPEAT_RATE_MS + gamepad_event_delay_ms;
+ int search_from = mouse_over - 1;
+ if (search_from < 0) {
+ search_from = items.size() - 1;
+ }
+
+ bool match_found = false;
+ for (int i = search_from; i >= 0; i--) {
+ if (!items[i].separator && !items[i].disabled) {
+ mouse_over = i;
+ emit_signal(SNAME("id_focused"), i);
+ scroll_to_item(i);
+ control->queue_redraw();
+ match_found = true;
+ break;
+ }
+ }
+
+ if (!match_found) {
+ // If the first item is not selectable, try re-searching from the end.
+ for (int i = items.size() - 1; i >= search_from; i--) {
+ if (!items[i].separator && !items[i].disabled) {
+ mouse_over = i;
+ emit_signal(SNAME("id_focused"), i);
+ scroll_to_item(i);
+ control->queue_redraw();
+ break;
+ }
+ }
+ }
+ }
+ }
+
// Only used when using operating system windows.
if (!activated_by_keyboard && !is_embedded() && autohide_areas.size()) {
Point2 mouse_pos = DisplayServer::get_singleton()->mouse_get_position();
diff --git a/scene/gui/popup_menu.h b/scene/gui/popup_menu.h
index 642fc7d7dc..892559ba3c 100644
--- a/scene/gui/popup_menu.h
+++ b/scene/gui/popup_menu.h
@@ -134,6 +134,10 @@ class PopupMenu : public Popup {
ScrollContainer *scroll_container = nullptr;
Control *control = nullptr;
+ const float DEFAULT_GAMEPAD_EVENT_DELAY_MS = 0.5;
+ const float GAMEPAD_EVENT_REPEAT_RATE_MS = 1.0 / 20;
+ float gamepad_event_delay_ms = DEFAULT_GAMEPAD_EVENT_DELAY_MS;
+
struct ThemeCache {
Ref<StyleBox> panel_style;
Ref<StyleBox> hover_style;
diff --git a/scene/gui/slider.cpp b/scene/gui/slider.cpp
index 6e3f1775bc..e2fb7efb19 100644
--- a/scene/gui/slider.cpp
+++ b/scene/gui/slider.cpp
@@ -112,30 +112,58 @@ void Slider::gui_input(const Ref<InputEvent> &p_event) {
}
}
+ Input *input = Input::get_singleton();
+ Ref<InputEventJoypadMotion> joypadmotion_event = p_event;
+ Ref<InputEventJoypadButton> joypadbutton_event = p_event;
+ bool is_joypad_event = (joypadmotion_event.is_valid() || joypadbutton_event.is_valid());
+
if (!mm.is_valid() && !mb.is_valid()) {
if (p_event->is_action_pressed("ui_left", true)) {
if (orientation != HORIZONTAL) {
return;
}
+ if (is_joypad_event) {
+ if (!input->is_action_just_pressed("ui_left", true)) {
+ return;
+ }
+ set_process_internal(true);
+ }
set_value(get_value() - (custom_step >= 0 ? custom_step : get_step()));
accept_event();
} else if (p_event->is_action_pressed("ui_right", true)) {
if (orientation != HORIZONTAL) {
return;
}
+ if (is_joypad_event) {
+ if (!input->is_action_just_pressed("ui_right", true)) {
+ return;
+ }
+ set_process_internal(true);
+ }
set_value(get_value() + (custom_step >= 0 ? custom_step : get_step()));
accept_event();
} else if (p_event->is_action_pressed("ui_up", true)) {
if (orientation != VERTICAL) {
return;
}
-
+ if (is_joypad_event) {
+ if (!input->is_action_just_pressed("ui_up", true)) {
+ return;
+ }
+ set_process_internal(true);
+ }
set_value(get_value() + (custom_step >= 0 ? custom_step : get_step()));
accept_event();
} else if (p_event->is_action_pressed("ui_down", true)) {
if (orientation != VERTICAL) {
return;
}
+ if (is_joypad_event) {
+ if (!input->is_action_just_pressed("ui_down", true)) {
+ return;
+ }
+ set_process_internal(true);
+ }
set_value(get_value() - (custom_step >= 0 ? custom_step : get_step()));
accept_event();
} else if (p_event->is_action("ui_home", true) && p_event->is_pressed()) {
@@ -166,6 +194,39 @@ void Slider::_update_theme_item_cache() {
void Slider::_notification(int p_what) {
switch (p_what) {
+ case NOTIFICATION_INTERNAL_PROCESS: {
+ Input *input = Input::get_singleton();
+
+ if (input->is_action_just_released("ui_left") || input->is_action_just_released("ui_right") || input->is_action_just_released("ui_up") || input->is_action_just_released("ui_down")) {
+ gamepad_event_delay_ms = DEFAULT_GAMEPAD_EVENT_DELAY_MS;
+ set_process_internal(false);
+ return;
+ }
+
+ gamepad_event_delay_ms -= get_process_delta_time();
+ if (gamepad_event_delay_ms <= 0) {
+ gamepad_event_delay_ms = GAMEPAD_EVENT_REPEAT_RATE_MS + gamepad_event_delay_ms;
+ if (orientation == HORIZONTAL) {
+ if (input->is_action_pressed("ui_left")) {
+ set_value(get_value() - (custom_step >= 0 ? custom_step : get_step()));
+ }
+
+ if (input->is_action_pressed("ui_right")) {
+ set_value(get_value() + (custom_step >= 0 ? custom_step : get_step()));
+ }
+ } else if (orientation == VERTICAL) {
+ if (input->is_action_pressed("ui_down")) {
+ set_value(get_value() - (custom_step >= 0 ? custom_step : get_step()));
+ }
+
+ if (input->is_action_pressed("ui_up")) {
+ set_value(get_value() + (custom_step >= 0 ? custom_step : get_step()));
+ }
+ }
+ }
+
+ } break;
+
case NOTIFICATION_THEME_CHANGED: {
update_minimum_size();
queue_redraw();
diff --git a/scene/gui/slider.h b/scene/gui/slider.h
index 684445f2b3..6d513b8b27 100644
--- a/scene/gui/slider.h
+++ b/scene/gui/slider.h
@@ -49,6 +49,10 @@ class Slider : public Range {
bool editable = true;
bool scrollable = true;
+ const float DEFAULT_GAMEPAD_EVENT_DELAY_MS = 0.5;
+ const float GAMEPAD_EVENT_REPEAT_RATE_MS = 1.0 / 20;
+ float gamepad_event_delay_ms = DEFAULT_GAMEPAD_EVENT_DELAY_MS;
+
struct ThemeCache {
Ref<StyleBox> slider_style;
Ref<StyleBox> grabber_area_style;
diff --git a/scene/gui/spin_box.cpp b/scene/gui/spin_box.cpp
index a0c8f7c91f..4f4754add5 100644
--- a/scene/gui/spin_box.cpp
+++ b/scene/gui/spin_box.cpp
@@ -202,7 +202,8 @@ void SpinBox::_line_edit_focus_enter() {
void SpinBox::_line_edit_focus_exit() {
// Discontinue because the focus_exit was caused by left-clicking the arrows.
- if (get_viewport()->gui_get_focus_owner() == get_line_edit()) {
+ const Viewport *viewport = get_viewport();
+ if (!viewport || viewport->gui_get_focus_owner() == get_line_edit()) {
return;
}
// Discontinue because the focus_exit was caused by right-click context menu.
diff --git a/scene/main/node.cpp b/scene/main/node.cpp
index 264a152392..ad7e445b5c 100644
--- a/scene/main/node.cpp
+++ b/scene/main/node.cpp
@@ -216,14 +216,6 @@ void Node::_notification(int p_notification) {
memdelete(child);
}
} break;
-
- case NOTIFICATION_CHILD_ORDER_CHANGED: {
- // The order, in which canvas items are drawn gets rearranged.
- // This makes it necessary to update mouse cursor and send according mouse_enter/mouse_exit signals for Control nodes.
- if (get_viewport()) {
- get_viewport()->update_mouse_cursor_state();
- }
- } break;
}
}
diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp
index e7970b212e..8fcf9e84c4 100644
--- a/scene/main/viewport.cpp
+++ b/scene/main/viewport.cpp
@@ -3156,7 +3156,7 @@ void Viewport::gui_release_focus() {
}
}
-Control *Viewport::gui_get_focus_owner() {
+Control *Viewport::gui_get_focus_owner() const {
ERR_READ_THREAD_GUARD_V(nullptr);
return gui.key_focus;
}
diff --git a/scene/main/viewport.h b/scene/main/viewport.h
index 63cddddbcb..1cb32d4509 100644
--- a/scene/main/viewport.h
+++ b/scene/main/viewport.h
@@ -599,7 +599,7 @@ public:
int gui_get_canvas_sort_index();
void gui_release_focus();
- Control *gui_get_focus_owner();
+ Control *gui_get_focus_owner() const;
PackedStringArray get_configuration_warnings() const override;
diff --git a/scene/resources/gradient.cpp b/scene/resources/gradient.cpp
index ec4b756a85..afaeb0b52d 100644
--- a/scene/resources/gradient.cpp
+++ b/scene/resources/gradient.cpp
@@ -114,6 +114,10 @@ Vector<Color> Gradient::get_colors() const {
}
void Gradient::set_interpolation_mode(Gradient::InterpolationMode p_interp_mode) {
+ if (p_interp_mode == interpolation_mode) {
+ return;
+ }
+
interpolation_mode = p_interp_mode;
emit_signal(CoreStringNames::get_singleton()->changed);
notify_property_list_changed();
@@ -124,6 +128,10 @@ Gradient::InterpolationMode Gradient::get_interpolation_mode() {
}
void Gradient::set_interpolation_color_space(Gradient::ColorSpace p_color_space) {
+ if (p_color_space == interpolation_color_space) {
+ return;
+ }
+
interpolation_color_space = p_color_space;
emit_signal(CoreStringNames::get_singleton()->changed);
}
diff --git a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp
index bac788d0e9..eea891792a 100644
--- a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp
+++ b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp
@@ -1699,6 +1699,8 @@ void RenderForwardClustered::_render_scene(RenderDataRD *p_render_data, const Co
scene_state.used_normal_texture) {
depth_pass_mode = PASS_MODE_DEPTH_NORMAL_ROUGHNESS;
}
+ } else if (get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_NORMAL_BUFFER || scene_state.used_normal_texture) {
+ depth_pass_mode = PASS_MODE_DEPTH_NORMAL_ROUGHNESS;
}
switch (depth_pass_mode) {
diff --git a/thirdparty/README.md b/thirdparty/README.md
index dc62e16d3b..e36d4599ff 100644
--- a/thirdparty/README.md
+++ b/thirdparty/README.md
@@ -688,7 +688,7 @@ comments and a patch is provided in the squish/ folder.
## tinyexr
- Upstream: https://github.com/syoyo/tinyexr
-- Version: 1.0.2 (02310c77e5156c36fedf6cf810c4071e3f83906f, 2023)
+- Version: 1.0.4 (7c92b8cd86a378ba5cb7b6d39a336457728dfb82, 2023)
- License: BSD-3-Clause
Files extracted from upstream source:
diff --git a/thirdparty/tinyexr/tinyexr.h b/thirdparty/tinyexr/tinyexr.h
index 7482853bcb..3613aaa874 100644
--- a/thirdparty/tinyexr/tinyexr.h
+++ b/thirdparty/tinyexr/tinyexr.h
@@ -619,7 +619,6 @@ extern int LoadEXRFromMemory(float **out_rgba, int *width, int *height,
#endif
#include <algorithm>
-#include <cassert>
#include <cstdio>
#include <cstdlib>
#include <cstring>
@@ -684,6 +683,27 @@ extern "C" unsigned char *stbi_zlib_compress(unsigned char *data, int data_len,
#endif
+// cond: conditional expression
+// msg: std::string
+// err: std::string*
+#define TINYEXR_CHECK_AND_RETURN_MSG(cond, msg, err) do { \
+ if (!(cond)) { \
+ if (!err) { \
+ std::ostringstream ss_e; \
+ ss_e << __func__ << "():" << __LINE__ << msg << "\n"; \
+ (*err) += ss_e.str(); \
+ } \
+ return false;\
+ } \
+ } while(0)
+
+// no error message.
+#define TINYEXR_CHECK_AND_RETURN_C(cond, retcode) do { \
+ if (!(cond)) { \
+ return retcode; \
+ } \
+ } while(0)
+
namespace tinyexr {
#if __cplusplus > 199711L
@@ -1558,7 +1578,7 @@ static int rleUncompress(int inLength, int maxLength, const signed char in[],
// End of RLE code from OpenEXR -----------------------------------
-static void CompressRle(unsigned char *dst,
+static bool CompressRle(unsigned char *dst,
tinyexr::tinyexr_uint64 &compressedSize,
const unsigned char *src, unsigned long src_size) {
std::vector<unsigned char> tmpBuf(src_size);
@@ -1613,7 +1633,7 @@ static void CompressRle(unsigned char *dst,
int outSize = rleCompress(static_cast<int>(src_size),
reinterpret_cast<const char *>(&tmpBuf.at(0)),
reinterpret_cast<signed char *>(dst));
- assert(outSize > 0);
+ TINYEXR_CHECK_AND_RETURN_C(outSize > 0, false);
compressedSize = static_cast<tinyexr::tinyexr_uint64>(outSize);
@@ -1623,6 +1643,8 @@ static void CompressRle(unsigned char *dst,
compressedSize = src_size;
memcpy(dst, src, src_size);
}
+
+ return true;
}
static bool DecompressRle(unsigned char *dst,
@@ -2162,7 +2184,7 @@ struct FHeapCompare {
bool operator()(long long *a, long long *b) { return *a > *b; }
};
-static void hufBuildEncTable(
+static bool hufBuildEncTable(
long long *frq, // io: input frequencies [HUF_ENCSIZE], output table
int *im, // o: min frq index
int *iM) // o: max frq index
@@ -2290,7 +2312,7 @@ static void hufBuildEncTable(
for (int j = m;; j = hlink[j]) {
scode[j]++;
- assert(scode[j] <= 58);
+ TINYEXR_CHECK_AND_RETURN_C(scode[j] <= 58, false);
if (hlink[j] == j) {
//
@@ -2309,7 +2331,7 @@ static void hufBuildEncTable(
for (int j = mm;; j = hlink[j]) {
scode[j]++;
- assert(scode[j] <= 58);
+ TINYEXR_CHECK_AND_RETURN_C(scode[j] <= 58, false);
if (hlink[j] == j) break;
}
@@ -2323,6 +2345,8 @@ static void hufBuildEncTable(
hufCanonicalCodeTable(scode.data());
memcpy(frq, scode.data(), sizeof(long long) * HUF_ENCSIZE);
+
+ return true;
}
//
@@ -3034,7 +3058,6 @@ static bool CompressPiz(unsigned char *outPtr, unsigned int *outSize,
#if !TINYEXR_LITTLE_ENDIAN
// @todo { PIZ compression on BigEndian architecture. }
- assert(0);
return false;
#endif
@@ -3160,7 +3183,6 @@ static bool DecompressPiz(unsigned char *outPtr, const unsigned char *inPtr,
#if !TINYEXR_LITTLE_ENDIAN
// @todo { PIZ compression on BigEndian architecture. }
- assert(0);
return false;
#endif
@@ -3200,7 +3222,13 @@ static bool DecompressPiz(unsigned char *outPtr, const unsigned char *inPtr,
ptr += maxNonZero - minNonZero + 1;
readLen += maxNonZero - minNonZero + 1;
} else {
- return false;
+ // Issue 194
+ if ((minNonZero == (BITMAP_SIZE - 1)) && (maxNonZero == 0)) {
+ // OK. all pixels are zero. And no need to read `bitmap` data.
+ } else {
+ // invalid minNonZero/maxNonZero combination.
+ return false;
+ }
}
std::vector<unsigned short> lut(USHORT_RANGE);
@@ -3211,12 +3239,12 @@ static bool DecompressPiz(unsigned char *outPtr, const unsigned char *inPtr,
// Huffman decoding
//
- int length;
-
if ((readLen + 4) > inLen) {
return false;
}
+ int length=0;
+
// length = *(reinterpret_cast<const int *>(ptr));
tinyexr::cpy4(&length, reinterpret_cast<const int *>(ptr));
ptr += sizeof(int);
@@ -3396,8 +3424,8 @@ static bool DecompressZfp(float *dst, int dst_width, int dst_num_lines,
zfp_stream *zfp = NULL;
zfp_field *field = NULL;
- assert((dst_width % 4) == 0);
- assert((dst_num_lines % 4) == 0);
+ TINYEXR_CHECK_AND_RETURN_C((dst_width % 4) == 0, false);
+ TINYEXR_CHECK_AND_RETURN_C((dst_num_lines % 4) == 0, false);
if ((size_t(dst_width) & 3U) || (size_t(dst_num_lines) & 3U)) {
return false;
@@ -3418,7 +3446,7 @@ static bool DecompressZfp(float *dst, int dst_width, int dst_num_lines,
} else if (param.type == TINYEXR_ZFP_COMPRESSIONTYPE_ACCURACY) {
zfp_stream_set_accuracy(zfp, param.tolerance);
} else {
- assert(0);
+ return false;
}
size_t buf_size = zfp_stream_maximum_size(zfp, field);
@@ -3462,8 +3490,8 @@ static bool CompressZfp(std::vector<unsigned char> *outBuf,
zfp_stream *zfp = NULL;
zfp_field *field = NULL;
- assert((width % 4) == 0);
- assert((num_lines % 4) == 0);
+ TINYEXR_CHECK_AND_RETURN_C((width % 4) == 0, false);
+ TINYEXR_CHECK_AND_RETURN_C((num_lines % 4) == 0, false);
if ((size_t(width) & 3U) || (size_t(num_lines) & 3U)) {
return false;
@@ -3483,7 +3511,7 @@ static bool CompressZfp(std::vector<unsigned char> *outBuf,
} else if (param.type == TINYEXR_ZFP_COMPRESSIONTYPE_ACCURACY) {
zfp_stream_set_accuracy(zfp, param.tolerance);
} else {
- assert(0);
+ return false;
}
size_t buf_size = zfp_stream_maximum_size(zfp, field);
@@ -3620,7 +3648,7 @@ static bool DecodePixelData(/* out */ unsigned char **out_images,
}
}
} else if (channels[c].pixel_type == TINYEXR_PIXELTYPE_UINT) {
- assert(requested_pixel_types[c] == TINYEXR_PIXELTYPE_UINT);
+ TINYEXR_CHECK_AND_RETURN_C(requested_pixel_types[c] == TINYEXR_PIXELTYPE_UINT, false);
for (size_t v = 0; v < static_cast<size_t>(num_lines); v++) {
const unsigned int *line_ptr = reinterpret_cast<unsigned int *>(
@@ -3649,7 +3677,7 @@ static bool DecodePixelData(/* out */ unsigned char **out_images,
}
}
} else if (channels[c].pixel_type == TINYEXR_PIXELTYPE_FLOAT) {
- assert(requested_pixel_types[c] == TINYEXR_PIXELTYPE_FLOAT);
+ TINYEXR_CHECK_AND_RETURN_C(requested_pixel_types[c] == TINYEXR_PIXELTYPE_FLOAT, false);
for (size_t v = 0; v < static_cast<size_t>(num_lines); v++) {
const float *line_ptr = reinterpret_cast<float *>(&outBuf.at(
v * pixel_data_size * static_cast<size_t>(width) +
@@ -3676,11 +3704,10 @@ static bool DecodePixelData(/* out */ unsigned char **out_images,
}
}
} else {
- assert(0);
+ return false;
}
}
#else
- assert(0 && "PIZ is disabled in this build");
return false;
#endif
@@ -3692,7 +3719,7 @@ static bool DecodePixelData(/* out */ unsigned char **out_images,
pixel_data_size);
unsigned long dstLen = static_cast<unsigned long>(outBuf.size());
- assert(dstLen > 0);
+ TINYEXR_CHECK_AND_RETURN_C(dstLen > 0, false);
if (!tinyexr::DecompressZip(
reinterpret_cast<unsigned char *>(&outBuf.at(0)), &dstLen, data_ptr,
static_cast<unsigned long>(data_len))) {
@@ -3759,7 +3786,7 @@ static bool DecodePixelData(/* out */ unsigned char **out_images,
}
}
} else if (channels[c].pixel_type == TINYEXR_PIXELTYPE_UINT) {
- assert(requested_pixel_types[c] == TINYEXR_PIXELTYPE_UINT);
+ TINYEXR_CHECK_AND_RETURN_C(requested_pixel_types[c] == TINYEXR_PIXELTYPE_UINT, false);
for (size_t v = 0; v < static_cast<size_t>(num_lines); v++) {
const unsigned int *line_ptr = reinterpret_cast<unsigned int *>(
@@ -3788,7 +3815,7 @@ static bool DecodePixelData(/* out */ unsigned char **out_images,
}
}
} else if (channels[c].pixel_type == TINYEXR_PIXELTYPE_FLOAT) {
- assert(requested_pixel_types[c] == TINYEXR_PIXELTYPE_FLOAT);
+ TINYEXR_CHECK_AND_RETURN_C(requested_pixel_types[c] == TINYEXR_PIXELTYPE_FLOAT, false);
for (size_t v = 0; v < static_cast<size_t>(num_lines); v++) {
const float *line_ptr = reinterpret_cast<float *>(
&outBuf.at(v * pixel_data_size * static_cast<size_t>(width) +
@@ -3815,7 +3842,6 @@ static bool DecodePixelData(/* out */ unsigned char **out_images,
}
}
} else {
- assert(0);
return false;
}
}
@@ -3893,7 +3919,7 @@ static bool DecodePixelData(/* out */ unsigned char **out_images,
}
}
} else if (channels[c].pixel_type == TINYEXR_PIXELTYPE_UINT) {
- assert(requested_pixel_types[c] == TINYEXR_PIXELTYPE_UINT);
+ TINYEXR_CHECK_AND_RETURN_C(requested_pixel_types[c] == TINYEXR_PIXELTYPE_UINT, false);
for (size_t v = 0; v < static_cast<size_t>(num_lines); v++) {
const unsigned int *line_ptr = reinterpret_cast<unsigned int *>(
@@ -3922,7 +3948,7 @@ static bool DecodePixelData(/* out */ unsigned char **out_images,
}
}
} else if (channels[c].pixel_type == TINYEXR_PIXELTYPE_FLOAT) {
- assert(requested_pixel_types[c] == TINYEXR_PIXELTYPE_FLOAT);
+ TINYEXR_CHECK_AND_RETURN_C(requested_pixel_types[c] == TINYEXR_PIXELTYPE_FLOAT, false);
for (size_t v = 0; v < static_cast<size_t>(num_lines); v++) {
const float *line_ptr = reinterpret_cast<float *>(
&outBuf.at(v * pixel_data_size * static_cast<size_t>(width) +
@@ -3949,7 +3975,6 @@ static bool DecodePixelData(/* out */ unsigned char **out_images,
}
}
} else {
- assert(0);
return false;
}
}
@@ -3960,7 +3985,6 @@ static bool DecodePixelData(/* out */ unsigned char **out_images,
if (!tinyexr::FindZFPCompressionParam(&zfp_compression_param, attributes,
int(num_attributes), &e)) {
// This code path should not be reachable.
- assert(0);
return false;
}
@@ -3970,7 +3994,7 @@ static bool DecodePixelData(/* out */ unsigned char **out_images,
pixel_data_size);
unsigned long dstLen = outBuf.size();
- assert(dstLen > 0);
+ TINYEXR_CHECK_AND_RETURN_C(dstLen > 0, false);
tinyexr::DecompressZfp(reinterpret_cast<float *>(&outBuf.at(0)), width,
num_lines, num_channels, data_ptr,
static_cast<unsigned long>(data_len),
@@ -3987,9 +4011,9 @@ static bool DecodePixelData(/* out */ unsigned char **out_images,
// pixel sample data for channel n for scanline 1
// ...
for (size_t c = 0; c < static_cast<size_t>(num_channels); c++) {
- assert(channels[c].pixel_type == TINYEXR_PIXELTYPE_FLOAT);
+ TINYEXR_CHECK_AND_RETURN_C(channels[c].pixel_type == TINYEXR_PIXELTYPE_FLOAT, false);
if (channels[c].pixel_type == TINYEXR_PIXELTYPE_FLOAT) {
- assert(requested_pixel_types[c] == TINYEXR_PIXELTYPE_FLOAT);
+ TINYEXR_CHECK_AND_RETURN_C(requested_pixel_types[c] == TINYEXR_PIXELTYPE_FLOAT, false);
for (size_t v = 0; v < static_cast<size_t>(num_lines); v++) {
const float *line_ptr = reinterpret_cast<float *>(
&outBuf.at(v * pixel_data_size * static_cast<size_t>(width) +
@@ -4015,7 +4039,6 @@ static bool DecodePixelData(/* out */ unsigned char **out_images,
}
}
} else {
- assert(0);
return false;
}
}
@@ -4023,7 +4046,6 @@ static bool DecodePixelData(/* out */ unsigned char **out_images,
(void)attributes;
(void)num_attributes;
(void)num_channels;
- assert(0);
return false;
#endif
} else if (compression_type == TINYEXR_COMPRESSIONTYPE_NONE) {
@@ -4084,7 +4106,6 @@ static bool DecodePixelData(/* out */ unsigned char **out_images,
outLine[u] = f32.f;
}
} else {
- assert(0);
return false;
}
} else if (channels[c].pixel_type == TINYEXR_PIXELTYPE_FLOAT) {
@@ -4245,7 +4266,9 @@ static unsigned char **AllocateImage(int num_channels,
images[c] = reinterpret_cast<unsigned char *>(
static_cast<float *>(malloc(sizeof(float) * data_len)));
} else {
- assert(0);
+ images[c] = NULL; // just in case.
+ valid = false;
+ break;
}
} else if (channels[c].pixel_type == TINYEXR_PIXELTYPE_FLOAT) {
// pixel_data_size += sizeof(float);
@@ -4400,7 +4423,6 @@ static int ParseEXRHeader(HeaderInfo *info, bool *empty_header,
return TINYEXR_ERROR_INVALID_DATA;
}
- assert(data.size() == 9);
memcpy(&x_size, &data.at(0), sizeof(int));
memcpy(&y_size, &data.at(4), sizeof(int));
tile_mode = data[8];
@@ -4787,6 +4809,7 @@ struct OffsetData {
int num_y_levels;
};
+// -1 = error
static int LevelIndex(int lx, int ly, int tile_level_mode, int num_x_levels) {
switch (tile_level_mode) {
case TINYEXR_TILE_ONE_LEVEL:
@@ -4799,13 +4822,15 @@ static int LevelIndex(int lx, int ly, int tile_level_mode, int num_x_levels) {
return lx + ly * num_x_levels;
default:
- assert(false);
+ return -1;
}
return 0;
}
static int LevelSize(int toplevel_size, int level, int tile_rounding_mode) {
- assert(level >= 0);
+ if (level < 0) {
+ return -1;
+ }
int b = static_cast<int>(1u << static_cast<unsigned int>(level));
int level_size = toplevel_size / b;
@@ -4826,9 +4851,13 @@ static int DecodeTiledLevel(EXRImage* exr_image, const EXRHeader* exr_header,
int level_index = LevelIndex(exr_image->level_x, exr_image->level_y, exr_header->tile_level_mode, offset_data.num_x_levels);
int num_y_tiles = int(offset_data.offsets[size_t(level_index)].size());
- assert(num_y_tiles);
+ if (num_y_tiles < 1) {
+ return TINYEXR_ERROR_INVALID_DATA;
+ }
int num_x_tiles = int(offset_data.offsets[size_t(level_index)][0].size());
- assert(num_x_tiles);
+ if (num_x_tiles < 1) {
+ return TINYEXR_ERROR_INVALID_DATA;
+ }
int num_tiles = num_x_tiles * num_y_tiles;
int err_code = TINYEXR_SUCCESS;
@@ -5026,10 +5055,24 @@ static int DecodeChunk(EXRImage *exr_image, const EXRHeader *exr_header,
return TINYEXR_ERROR_INVALID_DATA;
}
- int data_width =
- exr_header->data_window.max_x - exr_header->data_window.min_x + 1;
- int data_height =
- exr_header->data_window.max_y - exr_header->data_window.min_y + 1;
+ tinyexr_int64 data_width =
+ static_cast<tinyexr_int64>(exr_header->data_window.max_x) - static_cast<tinyexr_int64>(exr_header->data_window.min_x) + static_cast<tinyexr_int64>(1);
+ tinyexr_int64 data_height =
+ static_cast<tinyexr_int64>(exr_header->data_window.max_y) - static_cast<tinyexr_int64>(exr_header->data_window.min_y) + static_cast<tinyexr_int64>(1);
+
+ if (data_width <= 0) {
+ if (err) {
+ (*err) += "Invalid data window width.\n";
+ }
+ return TINYEXR_ERROR_INVALID_DATA;
+ }
+
+ if (data_height <= 0) {
+ if (err) {
+ (*err) += "Invalid data window height.\n";
+ }
+ return TINYEXR_ERROR_INVALID_DATA;
+ }
// Do not allow too large data_width and data_height. header invalid?
{
@@ -5109,8 +5152,17 @@ static int DecodeChunk(EXRImage *exr_image, const EXRHeader *exr_header,
}
level_image->width =
LevelSize(exr_header->data_window.max_x - exr_header->data_window.min_x + 1, level, exr_header->tile_rounding_mode);
+ if (level_image->width < 1) {
+ return TINYEXR_ERROR_INVALID_DATA;
+ }
+
level_image->height =
LevelSize(exr_header->data_window.max_y - exr_header->data_window.min_y + 1, level, exr_header->tile_rounding_mode);
+
+ if (level_image->height < 1) {
+ return TINYEXR_ERROR_INVALID_DATA;
+ }
+
level_image->level_x = level;
level_image->level_y = level;
@@ -5136,8 +5188,16 @@ static int DecodeChunk(EXRImage *exr_image, const EXRHeader *exr_header,
level_image->width =
LevelSize(exr_header->data_window.max_x - exr_header->data_window.min_x + 1, level_x, exr_header->tile_rounding_mode);
+ if (level_image->width < 1) {
+ return TINYEXR_ERROR_INVALID_DATA;
+ }
+
level_image->height =
LevelSize(exr_header->data_window.max_y - exr_header->data_window.min_y + 1, level_y, exr_header->tile_rounding_mode);
+ if (level_image->height < 1) {
+ return TINYEXR_ERROR_INVALID_DATA;
+ }
+
level_image->level_x = level_x;
level_image->level_y = level_y;
@@ -5171,7 +5231,7 @@ static int DecodeChunk(EXRImage *exr_image, const EXRHeader *exr_header,
bool alloc_success = false;
exr_image->images = tinyexr::AllocateImage(
num_channels, exr_header->channels, exr_header->requested_pixel_types,
- data_width, data_height, &alloc_success);
+ int(data_width), int(data_height), &alloc_success);
if (!alloc_success) {
if (err) {
@@ -5271,7 +5331,7 @@ static int DecodeChunk(EXRImage *exr_image, const EXRHeader *exr_header,
exr_image->images, exr_header->requested_pixel_types,
data_ptr, static_cast<size_t>(data_len),
exr_header->compression_type, exr_header->line_order,
- data_width, data_height, data_width, y, line_no,
+ int(data_width), int(data_height), int(data_width), y, line_no,
num_lines, static_cast<size_t>(pixel_data_size),
static_cast<size_t>(
exr_header->num_custom_attributes),
@@ -5323,8 +5383,8 @@ static int DecodeChunk(EXRImage *exr_image, const EXRHeader *exr_header,
{
exr_image->num_channels = num_channels;
- exr_image->width = data_width;
- exr_image->height = data_height;
+ exr_image->width = int(data_width);
+ exr_image->height = int(data_height);
}
return TINYEXR_SUCCESS;
@@ -5333,8 +5393,12 @@ static int DecodeChunk(EXRImage *exr_image, const EXRHeader *exr_header,
static bool ReconstructLineOffsets(
std::vector<tinyexr::tinyexr_uint64> *offsets, size_t n,
const unsigned char *head, const unsigned char *marker, const size_t size) {
- assert(head < marker);
- assert(offsets->size() == n);
+ if (head >= marker) {
+ return false;
+ }
+ if (offsets->size() != n) {
+ return false;
+ }
for (size_t i = 0; i < n; i++) {
size_t offset = static_cast<size_t>(marker - head);
@@ -5430,7 +5494,7 @@ static int CalculateNumXLevels(const EXRHeader* exr_header) {
default:
- assert(false);
+ return -1;
}
return num;
@@ -5468,25 +5532,29 @@ static int CalculateNumYLevels(const EXRHeader* exr_header) {
default:
- assert(false);
+ return -1;
}
return num;
}
-static void CalculateNumTiles(std::vector<int>& numTiles,
+static bool CalculateNumTiles(std::vector<int>& numTiles,
int toplevel_size,
int size,
int tile_rounding_mode) {
for (unsigned i = 0; i < numTiles.size(); i++) {
int l = LevelSize(toplevel_size, int(i), tile_rounding_mode);
- assert(l <= std::numeric_limits<int>::max() - size + 1);
+ if (l < 0) {
+ return false;
+ }
+ TINYEXR_CHECK_AND_RETURN_C(l <= std::numeric_limits<int>::max() - size + 1, false);
numTiles[i] = (l + size - 1) / size;
}
+ return true;
}
-static void PrecalculateTileInfo(std::vector<int>& num_x_tiles,
+static bool PrecalculateTileInfo(std::vector<int>& num_x_tiles,
std::vector<int>& num_y_tiles,
const EXRHeader* exr_header) {
int min_x = exr_header->data_window.min_x;
@@ -5495,20 +5563,35 @@ static void PrecalculateTileInfo(std::vector<int>& num_x_tiles,
int max_y = exr_header->data_window.max_y;
int num_x_levels = CalculateNumXLevels(exr_header);
+
+ if (num_x_levels < 0) {
+ return false;
+ }
+
int num_y_levels = CalculateNumYLevels(exr_header);
+ if (num_y_levels < 0) {
+ return false;
+ }
+
num_x_tiles.resize(size_t(num_x_levels));
num_y_tiles.resize(size_t(num_y_levels));
- CalculateNumTiles(num_x_tiles,
+ if (!CalculateNumTiles(num_x_tiles,
max_x - min_x + 1,
exr_header->tile_size_x,
- exr_header->tile_rounding_mode);
+ exr_header->tile_rounding_mode)) {
+ return false;
+ }
- CalculateNumTiles(num_y_tiles,
+ if (!CalculateNumTiles(num_y_tiles,
max_y - min_y + 1,
exr_header->tile_size_y,
- exr_header->tile_rounding_mode);
+ exr_header->tile_rounding_mode)) {
+ return false;
+ }
+
+ return true;
}
static void InitSingleResolutionOffsets(OffsetData& offset_data, size_t num_blocks) {
@@ -5520,6 +5603,7 @@ static void InitSingleResolutionOffsets(OffsetData& offset_data, size_t num_bloc
}
// Return sum of tile blocks.
+// 0 = error
static int InitTileOffsets(OffsetData& offset_data,
const EXRHeader* exr_header,
const std::vector<int>& num_x_tiles,
@@ -5530,7 +5614,7 @@ static int InitTileOffsets(OffsetData& offset_data,
switch (exr_header->tile_level_mode) {
case TINYEXR_TILE_ONE_LEVEL:
case TINYEXR_TILE_MIPMAP_LEVELS:
- assert(offset_data.num_x_levels == offset_data.num_y_levels);
+ TINYEXR_CHECK_AND_RETURN_C(offset_data.num_x_levels == offset_data.num_y_levels, 0);
offset_data.offsets.resize(size_t(offset_data.num_x_levels));
for (unsigned int l = 0; l < offset_data.offsets.size(); ++l) {
@@ -5561,7 +5645,7 @@ static int InitTileOffsets(OffsetData& offset_data,
break;
default:
- assert(false);
+ return 0;
}
return num_tile_blocks;
}
@@ -5629,7 +5713,7 @@ static bool isValidTile(const EXRHeader* exr_header,
return false;
}
-static void ReconstructTileOffsets(OffsetData& offset_data,
+static bool ReconstructTileOffsets(OffsetData& offset_data,
const EXRHeader* exr_header,
const unsigned char* head, const unsigned char* marker, const size_t /*size*/,
bool isMultiPartFile,
@@ -5689,14 +5773,19 @@ static void ReconstructTileOffsets(OffsetData& offset_data,
}
if (!isValidTile(exr_header, offset_data,
- tileX, tileY, levelX, levelY))
- return;
+ tileX, tileY, levelX, levelY)) {
+ return false;
+ }
int level_idx = LevelIndex(levelX, levelY, exr_header->tile_level_mode, numXLevels);
+ if (level_idx < 0) {
+ return false;
+ }
offset_data.offsets[size_t(level_idx)][size_t(tileY)][size_t(tileX)] = tileOffset;
}
}
}
+ return true;
}
// marker output is also
@@ -5754,8 +5843,12 @@ static int DecodeEXRImage(EXRImage *exr_image, const EXRHeader *exr_header,
tinyexr::SetErrorMessage("Invalid data width value", err);
return TINYEXR_ERROR_INVALID_DATA;
}
- int data_width =
- exr_header->data_window.max_x - exr_header->data_window.min_x + 1;
+ tinyexr_int64 data_width =
+ static_cast<tinyexr_int64>(exr_header->data_window.max_x) - static_cast<tinyexr_int64>(exr_header->data_window.min_x) + static_cast<tinyexr_int64>(1);
+ if (data_width <= 0) {
+ tinyexr::SetErrorMessage("Invalid data window width value", err);
+ return TINYEXR_ERROR_INVALID_DATA;
+ }
if (exr_header->data_window.max_y < exr_header->data_window.min_y ||
exr_header->data_window.max_y - exr_header->data_window.min_y ==
@@ -5763,8 +5856,13 @@ static int DecodeEXRImage(EXRImage *exr_image, const EXRHeader *exr_header,
tinyexr::SetErrorMessage("Invalid data height value", err);
return TINYEXR_ERROR_INVALID_DATA;
}
- int data_height =
- exr_header->data_window.max_y - exr_header->data_window.min_y + 1;
+ tinyexr_int64 data_height =
+ static_cast<tinyexr_int64>(exr_header->data_window.max_y) - static_cast<tinyexr_int64>(exr_header->data_window.min_y) + static_cast<tinyexr_int64>(1);
+
+ if (data_height <= 0) {
+ tinyexr::SetErrorMessage("Invalid data window height value", err);
+ return TINYEXR_ERROR_INVALID_DATA;
+ }
// Do not allow too large data_width and data_height. header invalid?
{
@@ -5797,7 +5895,10 @@ static int DecodeEXRImage(EXRImage *exr_image, const EXRHeader *exr_header,
if (exr_header->tiled) {
{
std::vector<int> num_x_tiles, num_y_tiles;
- PrecalculateTileInfo(num_x_tiles, num_y_tiles, exr_header);
+ if (!PrecalculateTileInfo(num_x_tiles, num_y_tiles, exr_header)) {
+ tinyexr::SetErrorMessage("Failed to precalculate tile info.", err);
+ return TINYEXR_ERROR_INVALID_DATA;
+ }
num_blocks = size_t(InitTileOffsets(offset_data, exr_header, num_x_tiles, num_y_tiles));
if (exr_header->chunk_count > 0) {
if (exr_header->chunk_count != static_cast<int>(num_blocks)) {
@@ -5810,9 +5911,13 @@ static int DecodeEXRImage(EXRImage *exr_image, const EXRHeader *exr_header,
int ret = ReadOffsets(offset_data, head, marker, size, err);
if (ret != TINYEXR_SUCCESS) return ret;
if (IsAnyOffsetsAreInvalid(offset_data)) {
- ReconstructTileOffsets(offset_data, exr_header,
+ if (!ReconstructTileOffsets(offset_data, exr_header,
head, marker, size,
- exr_header->multipart, exr_header->non_image);
+ exr_header->multipart, exr_header->non_image)) {
+
+ tinyexr::SetErrorMessage("Invalid Tile Offsets data.", err);
+ return TINYEXR_ERROR_INVALID_DATA;
+ }
}
} else if (exr_header->chunk_count > 0) {
// Use `chunkCount` attribute.
@@ -6638,6 +6743,7 @@ struct MemoryMappedFile {
data = reinterpret_cast<unsigned char *>(
mmap(0, size, PROT_READ, MAP_SHARED, posix_descriptor, 0));
if (data == MAP_FAILED) {
+ data = nullptr;
return;
}
#else
@@ -6662,20 +6768,26 @@ struct MemoryMappedFile {
size = static_cast<size_t>(ftell_result);
if (fseek(fp, 0, SEEK_SET) != 0) {
fclose(fp);
+ size = 0;
return;
}
data = reinterpret_cast<unsigned char *>(malloc(size));
if (!data) {
+ size = 0;
fclose(fp);
return;
}
size_t read_bytes = fread(data, 1, size, fp);
- assert(read_bytes == size);
+ if (read_bytes != size) {
+ // TODO: Try to read data until reading `size` bytes.
+ fclose(fp);
+ size = 0;
+ data = nullptr;
+ return;
+ }
fclose(fp);
- (void)read_bytes;
#endif
- assert(valid());
}
// MemoryMappedFile's destructor closes all its handles.
@@ -6966,9 +7078,14 @@ static bool EncodePixelData(/* out */ std::vector<unsigned char>& out_data,
tinyexr::tinyexr_uint64 outSize = block.size();
- tinyexr::CompressRle(&block.at(0), outSize,
+ if (!tinyexr::CompressRle(&block.at(0), outSize,
reinterpret_cast<const unsigned char *>(&buf.at(0)),
- static_cast<unsigned long>(buf.size()));
+ static_cast<unsigned long>(buf.size()))) {
+ if (err) {
+ (*err) += "RLE compresssion failed.\n";
+ }
+ return false;
+ }
// 4 byte: scan line
// 4 byte: data size
@@ -6985,9 +7102,14 @@ static bool EncodePixelData(/* out */ std::vector<unsigned char>& out_data,
std::vector<unsigned char> block(bufLen);
unsigned int outSize = static_cast<unsigned int>(block.size());
- CompressPiz(&block.at(0), &outSize,
+ if (!CompressPiz(&block.at(0), &outSize,
reinterpret_cast<const unsigned char *>(&buf.at(0)),
- buf.size(), channels, width, num_lines);
+ buf.size(), channels, width, num_lines)) {
+ if (err) {
+ (*err) += "PIZ compresssion failed.\n";
+ }
+ return false;
+ }
// 4 byte: scan line
// 4 byte: data size
@@ -7041,14 +7163,19 @@ static int EncodeTiledLevel(const EXRImage* level_image, const EXRHeader* exr_he
const void* compression_param, // must be set if zfp compression is enabled
std::string* err) {
int num_tiles = num_x_tiles * num_y_tiles;
- assert(num_tiles == level_image->num_tiles);
+ if (num_tiles != level_image->num_tiles) {
+ if (err) {
+ (*err) += "Invalid number of tiles in argument.\n";
+ }
+ return TINYEXR_ERROR_INVALID_ARGUMENT;
+ }
if ((exr_header->tile_size_x > level_image->width || exr_header->tile_size_y > level_image->height) &&
level_image->level_x == 0 && level_image->level_y == 0) {
if (err) {
(*err) += "Failed to encode tile data.\n";
- }
- return TINYEXR_ERROR_INVALID_DATA;
+ }
+ return TINYEXR_ERROR_INVALID_DATA;
}
@@ -7111,7 +7238,11 @@ static int EncodeTiledLevel(const EXRImage* level_image, const EXRHeader* exr_he
invalid_data = true;
continue;
}
- assert(data_list[data_idx].size() > data_header_size);
+ if (data_list[data_idx].size() <= data_header_size) {
+ invalid_data = true;
+ continue;
+ }
+
int data_len = static_cast<int>(data_list[data_idx].size() - data_header_size);
//tileX, tileY, levelX, levelY // pixel_data_size(int)
memcpy(&data_list[data_idx][0], &x_tile, sizeof(int));
@@ -7191,7 +7322,10 @@ static int EncodeChunk(const EXRImage* exr_image, const EXRHeader* exr_header,
pixel_data_size += sizeof(unsigned int);
channel_offset += sizeof(unsigned int);
} else {
- assert(0);
+ if (err) {
+ (*err) += "Invalid requested_pixel_type.\n";
+ }
+ return TINYEXR_ERROR_INVALID_DATA;
}
}
}
@@ -7236,6 +7370,13 @@ static int EncodeChunk(const EXRImage* exr_image, const EXRHeader* exr_header,
int level_index_from_image = LevelIndex(level_image->level_x, level_image->level_y,
exr_header->tile_level_mode, offset_data.num_x_levels);
+ if (level_index_from_image < 0) {
+ if (err) {
+ (*err) += "Invalid tile level mode\n";
+ }
+ return TINYEXR_ERROR_INVALID_DATA;
+ }
+
if (level_index_from_image != level_index) {
if (err) {
(*err) += "Incorrect level ordering in tiled image\n";
@@ -7243,9 +7384,20 @@ static int EncodeChunk(const EXRImage* exr_image, const EXRHeader* exr_header,
return TINYEXR_ERROR_INVALID_DATA;
}
int num_y_tiles = int(offset_data.offsets[level_index].size());
- assert(num_y_tiles);
+ if (num_y_tiles <= 0) {
+ if (err) {
+ (*err) += "Invalid Y tile size\n";
+ }
+ return TINYEXR_ERROR_INVALID_DATA;
+ }
+
int num_x_tiles = int(offset_data.offsets[level_index][0].size());
- assert(num_x_tiles);
+ if (num_x_tiles <= 0) {
+ if (err) {
+ (*err) += "Invalid X tile size\n";
+ }
+ return TINYEXR_ERROR_INVALID_DATA;
+ }
std::string e;
int ret = EncodeTiledLevel(level_image,
@@ -7276,7 +7428,7 @@ static int EncodeChunk(const EXRImage* exr_image, const EXRHeader* exr_header,
}
level_image = level_image->next_level;
}
- assert(static_cast<int>(block_idx) == num_blocks);
+ TINYEXR_CHECK_AND_RETURN_C(static_cast<int>(block_idx) == num_blocks, TINYEXR_ERROR_INVALID_DATA);
total_size = offset;
} else { // scanlines
std::vector<tinyexr::tinyexr_uint64>& offsets = offset_data.offsets[0][0];
@@ -7329,7 +7481,10 @@ static int EncodeChunk(const EXRImage* exr_image, const EXRHeader* exr_header,
invalid_data = true;
continue; // "break" cannot be used with OpenMP
}
- assert(data_list[i].size() > data_header_size);
+ if (data_list[i].size() <= data_header_size) {
+ invalid_data = true;
+ continue; // "break" cannot be used with OpenMP
+ }
int data_len = static_cast<int>(data_list[i].size() - data_header_size);
memcpy(&data_list[i][0], &start_y, sizeof(int));
memcpy(&data_list[i][4], &data_len, sizeof(int));
@@ -7455,9 +7610,20 @@ static size_t SaveEXRNPartImageToMemory(const EXRImage* exr_images,
} else {
{
std::vector<int> num_x_tiles, num_y_tiles;
- PrecalculateTileInfo(num_x_tiles, num_y_tiles, exr_headers[i]);
- chunk_count[i] =
- InitTileOffsets(offset_data[i], exr_headers[i], num_x_tiles, num_y_tiles);
+ if (!PrecalculateTileInfo(num_x_tiles, num_y_tiles, exr_headers[i])) {
+ SetErrorMessage("Failed to precalculate Tile info",
+ err);
+ return TINYEXR_ERROR_INVALID_DATA;
+ }
+ int ntiles = InitTileOffsets(offset_data[i], exr_headers[i], num_x_tiles, num_y_tiles);
+ if (ntiles > 0) {
+ chunk_count[i] = ntiles;
+ } else {
+ SetErrorMessage("Failed to compute Tile offsets",
+ err);
+ return TINYEXR_ERROR_INVALID_DATA;
+
+ }
total_chunk_count += chunk_count[i];
}
}
@@ -7657,7 +7823,7 @@ static size_t SaveEXRNPartImageToMemory(const EXRImage* exr_images,
// Allocating required memory
if (total_size == 0) { // something went wrong
tinyexr::SetErrorMessage("Output memory size is zero", err);
- return 0;
+ return TINYEXR_ERROR_INVALID_DATA;
}
(*memory_out) = static_cast<unsigned char*>(malloc(size_t(total_size)));
@@ -7676,7 +7842,11 @@ static size_t SaveEXRNPartImageToMemory(const EXRImage* exr_images,
for (size_t j = 0; j < offset_data[i].offsets[level_index].size(); ++j) {
size_t num_bytes = sizeof(tinyexr_uint64) * offset_data[i].offsets[level_index][j].size();
sum += num_bytes;
- assert(sum <= total_size);
+ if (sum > total_size) {
+ tinyexr::SetErrorMessage("Invalid offset bytes in Tiled Part image.", err);
+ return TINYEXR_ERROR_INVALID_DATA;
+ }
+
memcpy(memory_ptr,
reinterpret_cast<unsigned char*>(&offset_data[i].offsets[level_index][j][0]),
num_bytes);
@@ -7687,7 +7857,10 @@ static size_t SaveEXRNPartImageToMemory(const EXRImage* exr_images,
} else {
size_t num_bytes = sizeof(tinyexr::tinyexr_uint64) * static_cast<size_t>(chunk_count[i]);
sum += num_bytes;
- assert(sum <= total_size);
+ if (sum > total_size) {
+ tinyexr::SetErrorMessage("Invalid offset bytes in Part image.", err);
+ return TINYEXR_ERROR_INVALID_DATA;
+ }
std::vector<tinyexr::tinyexr_uint64>& offsets = offset_data[i].offsets[0][0];
memcpy(memory_ptr, reinterpret_cast<unsigned char*>(&offsets[0]), num_bytes);
memory_ptr += num_bytes;
@@ -7699,19 +7872,30 @@ static size_t SaveEXRNPartImageToMemory(const EXRImage* exr_images,
for (size_t j = 0; j < static_cast<size_t>(chunk_count[i]); ++j) {
if (num_parts > 1) {
sum += 4;
- assert(sum <= total_size);
+ if (sum > total_size) {
+ tinyexr::SetErrorMessage("Buffer overrun in reading Part image chunk data.", err);
+ return TINYEXR_ERROR_INVALID_DATA;
+ }
unsigned int part_number = i;
swap4(&part_number);
memcpy(memory_ptr, &part_number, 4);
memory_ptr += 4;
}
sum += data_lists[i][j].size();
- assert(sum <= total_size);
+ if (sum > total_size) {
+ tinyexr::SetErrorMessage("Buffer overrun in reading Part image chunk data.", err);
+ return TINYEXR_ERROR_INVALID_DATA;
+ }
memcpy(memory_ptr, &data_lists[i][j][0], data_lists[i][j].size());
memory_ptr += data_lists[i][j].size();
}
}
- assert(sum == total_size);
+
+ if (sum != total_size) {
+ tinyexr::SetErrorMessage("Corrupted Part image chunk data.", err);
+ return TINYEXR_ERROR_INVALID_DATA;
+ }
+
return size_t(total_size); // OK
}
@@ -8004,11 +8188,11 @@ int LoadDeepEXR(DeepImage *deep_image, const char *filename, const char **err) {
}
}
- assert(dx >= 0);
- assert(dy >= 0);
- assert(dw >= 0);
- assert(dh >= 0);
- assert(num_channels >= 1);
+ TINYEXR_CHECK_AND_RETURN_C(dx >= 0, TINYEXR_ERROR_INVALID_DATA);
+ TINYEXR_CHECK_AND_RETURN_C(dy >= 0, TINYEXR_ERROR_INVALID_DATA);
+ TINYEXR_CHECK_AND_RETURN_C(dw >= 0, TINYEXR_ERROR_INVALID_DATA);
+ TINYEXR_CHECK_AND_RETURN_C(dh >= 0, TINYEXR_ERROR_INVALID_DATA);
+ TINYEXR_CHECK_AND_RETURN_C(num_channels >= 1, TINYEXR_ERROR_INVALID_DATA);
int data_width = dw - dx + 1;
int data_height = dh - dy + 1;
@@ -8106,7 +8290,7 @@ int LoadDeepEXR(DeepImage *deep_image, const char *filename, const char **err) {
return false;
}
- assert(dstLen == pixelOffsetTable.size() * sizeof(int));
+ TINYEXR_CHECK_AND_RETURN_C(dstLen == pixelOffsetTable.size() * sizeof(int), TINYEXR_ERROR_INVALID_DATA);
for (size_t i = 0; i < static_cast<size_t>(data_width); i++) {
deep_image->offset_table[y][i] = pixelOffsetTable[i];
}
@@ -8125,7 +8309,7 @@ int LoadDeepEXR(DeepImage *deep_image, const char *filename, const char **err) {
static_cast<unsigned long>(packedSampleDataSize))) {
return false;
}
- assert(dstLen == static_cast<unsigned long>(unpackedSampleDataSize));
+ TINYEXR_CHECK_AND_RETURN_C(dstLen == static_cast<unsigned long>(unpackedSampleDataSize), TINYEXR_ERROR_INVALID_DATA);
}
}
@@ -8144,16 +8328,17 @@ int LoadDeepEXR(DeepImage *deep_image, const char *filename, const char **err) {
TINYEXR_PIXELTYPE_FLOAT) { // float
channel_offset += 4;
} else {
- assert(0);
+ tinyexr::SetErrorMessage("Invalid pixel_type in chnnels.", err);
+ return TINYEXR_ERROR_INVALID_DATA;
}
}
sampleSize = channel_offset;
}
- assert(sampleSize >= 2);
+ TINYEXR_CHECK_AND_RETURN_C(sampleSize >= 2, TINYEXR_ERROR_INVALID_DATA);
- assert(static_cast<size_t>(
+ TINYEXR_CHECK_AND_RETURN_C(static_cast<size_t>(
pixelOffsetTable[static_cast<size_t>(data_width - 1)] *
- sampleSize) == sample_data.size());
+ sampleSize) == sample_data.size(), TINYEXR_ERROR_INVALID_DATA);
int samples_per_line = static_cast<int>(sample_data.size()) / sampleSize;
//
@@ -8656,7 +8841,10 @@ int LoadEXRMultipartImageFromMemory(EXRImage *exr_images,
} else {
{
std::vector<int> num_x_tiles, num_y_tiles;
- tinyexr::PrecalculateTileInfo(num_x_tiles, num_y_tiles, exr_headers[i]);
+ if (!tinyexr::PrecalculateTileInfo(num_x_tiles, num_y_tiles, exr_headers[i])) {
+ tinyexr::SetErrorMessage("Invalid tile info.", err);
+ return TINYEXR_ERROR_INVALID_DATA;
+ }
int num_blocks = InitTileOffsets(offset_data, exr_headers[i], num_x_tiles, num_y_tiles);
if (num_blocks != exr_headers[i]->chunk_count) {
tinyexr::SetErrorMessage("Invalid offset table size.", err);