1/**************************************************************************/
2/* atlas_merging_dialog.cpp */
3/**************************************************************************/
4/* This file is part of: */
5/* GODOT ENGINE */
6/* https://godotengine.org */
7/**************************************************************************/
8/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10/* */
11/* Permission is hereby granted, free of charge, to any person obtaining */
12/* a copy of this software and associated documentation files (the */
13/* "Software"), to deal in the Software without restriction, including */
14/* without limitation the rights to use, copy, modify, merge, publish, */
15/* distribute, sublicense, and/or sell copies of the Software, and to */
16/* permit persons to whom the Software is furnished to do so, subject to */
17/* the following conditions: */
18/* */
19/* The above copyright notice and this permission notice shall be */
20/* included in all copies or substantial portions of the Software. */
21/* */
22/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29/**************************************************************************/
30
31#include "atlas_merging_dialog.h"
32
33#include "editor/editor_properties_vector.h"
34#include "editor/editor_scale.h"
35#include "editor/editor_undo_redo_manager.h"
36#include "editor/gui/editor_file_dialog.h"
37#include "scene/gui/control.h"
38#include "scene/gui/split_container.h"
39#include "scene/resources/image_texture.h"
40
41void AtlasMergingDialog::_property_changed(const StringName &p_property, const Variant &p_value, const String &p_field, bool p_changing) {
42 _set(p_property, p_value);
43}
44
45void AtlasMergingDialog::_generate_merged(Vector<Ref<TileSetAtlasSource>> p_atlas_sources, int p_max_columns) {
46 merged.instantiate();
47 merged_mapping.clear();
48
49 if (p_atlas_sources.size() >= 2) {
50 Ref<Image> output_image = Image::create_empty(1, 1, false, Image::FORMAT_RGBA8);
51
52 // Compute the new texture region size.
53 Vector2i new_texture_region_size;
54 for (int source_index = 0; source_index < p_atlas_sources.size(); source_index++) {
55 Ref<TileSetAtlasSource> atlas_source = p_atlas_sources[source_index];
56 new_texture_region_size = new_texture_region_size.max(atlas_source->get_texture_region_size());
57 }
58
59 // Generate the new texture.
60 Vector2i atlas_offset;
61 int line_height = 0;
62 for (int source_index = 0; source_index < p_atlas_sources.size(); source_index++) {
63 Ref<TileSetAtlasSource> atlas_source = p_atlas_sources[source_index];
64 Ref<Image> input_image = atlas_source->get_texture()->get_image();
65 if (input_image->get_format() != Image::FORMAT_RGBA8) {
66 input_image->convert(Image::FORMAT_RGBA8);
67 }
68 merged_mapping.push_back(HashMap<Vector2i, Vector2i>());
69
70 // Layout the tiles.
71 Vector2i atlas_size;
72
73 for (int tile_index = 0; tile_index < atlas_source->get_tiles_count(); tile_index++) {
74 Vector2i tile_id = atlas_source->get_tile_id(tile_index);
75 atlas_size = atlas_size.max(tile_id + atlas_source->get_tile_size_in_atlas(tile_id));
76
77 Rect2i new_tile_rect_in_atlas = Rect2i(atlas_offset + tile_id, atlas_source->get_tile_size_in_atlas(tile_id));
78
79 // Copy the texture.
80 for (int frame = 0; frame < atlas_source->get_tile_animation_frames_count(tile_id); frame++) {
81 Rect2i src_rect = atlas_source->get_tile_texture_region(tile_id, frame);
82 Vector2i new_position = new_tile_rect_in_atlas.position * new_texture_region_size;
83 if (frame > 0) {
84 new_position += src_rect.size * Vector2i(frame, 0);
85 atlas_size.x = MAX(frame + 1, atlas_size.x);
86 }
87 Rect2 dst_rect_wide = Rect2i(new_position, new_tile_rect_in_atlas.size * new_texture_region_size);
88 if (dst_rect_wide.get_end().x > output_image->get_width() || dst_rect_wide.get_end().y > output_image->get_height()) {
89 output_image->crop(MAX(dst_rect_wide.get_end().x, output_image->get_width()), MAX(dst_rect_wide.get_end().y, output_image->get_height()));
90 }
91 output_image->blit_rect(input_image, src_rect, dst_rect_wide.get_center() - src_rect.size / 2);
92 }
93
94 // Add to the mapping.
95 merged_mapping[source_index][tile_id] = new_tile_rect_in_atlas.position;
96 }
97
98 // Compute the atlas offset.
99 line_height = MAX(atlas_size.y, line_height);
100 atlas_offset.x += atlas_size.x;
101 if (atlas_offset.x >= p_max_columns) {
102 atlas_offset.x = 0;
103 atlas_offset.y += line_height;
104 line_height = 0;
105 }
106 }
107
108 merged->set_name(p_atlas_sources[0]->get_name());
109 merged->set_texture(ImageTexture::create_from_image(output_image));
110 merged->set_texture_region_size(new_texture_region_size);
111
112 // Copy the tiles to the merged TileSetAtlasSource.
113 for (int source_index = 0; source_index < p_atlas_sources.size(); source_index++) {
114 Ref<TileSetAtlasSource> atlas_source = p_atlas_sources[source_index];
115 for (KeyValue<Vector2i, Vector2i> tile_mapping : merged_mapping[source_index]) {
116 // Create tiles and alternatives, then copy their properties.
117 for (int alternative_index = 0; alternative_index < atlas_source->get_alternative_tiles_count(tile_mapping.key); alternative_index++) {
118 int alternative_id = atlas_source->get_alternative_tile_id(tile_mapping.key, alternative_index);
119 int changed_id = -1;
120 if (alternative_id == 0) {
121 merged->create_tile(tile_mapping.value, atlas_source->get_tile_size_in_atlas(tile_mapping.key));
122 int count = atlas_source->get_tile_animation_frames_count(tile_mapping.key);
123 merged->set_tile_animation_frames_count(tile_mapping.value, count);
124 for (int i = 0; i < count; i++) {
125 merged->set_tile_animation_frame_duration(tile_mapping.value, i, atlas_source->get_tile_animation_frame_duration(tile_mapping.key, i));
126 }
127 merged->set_tile_animation_speed(tile_mapping.value, atlas_source->get_tile_animation_speed(tile_mapping.key));
128 merged->set_tile_animation_mode(tile_mapping.value, atlas_source->get_tile_animation_mode(tile_mapping.key));
129 } else {
130 changed_id = merged->create_alternative_tile(tile_mapping.value, alternative_index);
131 }
132
133 // Copy the properties.
134 TileData *src_tile_data = atlas_source->get_tile_data(tile_mapping.key, alternative_id);
135 List<PropertyInfo> properties;
136 src_tile_data->get_property_list(&properties);
137
138 TileData *dst_tile_data = merged->get_tile_data(tile_mapping.value, changed_id == -1 ? alternative_id : changed_id);
139 for (PropertyInfo property : properties) {
140 if (!(property.usage & PROPERTY_USAGE_STORAGE)) {
141 continue;
142 }
143 Variant value = src_tile_data->get(property.name);
144 Variant default_value = ClassDB::class_get_default_property_value("TileData", property.name);
145 if (default_value.get_type() != Variant::NIL && bool(Variant::evaluate(Variant::OP_EQUAL, value, default_value))) {
146 continue;
147 }
148 dst_tile_data->set(property.name, value);
149 }
150 }
151 }
152 }
153 }
154}
155
156void AtlasMergingDialog::_update_texture() {
157 Vector<int> selected = atlas_merging_atlases_list->get_selected_items();
158 if (selected.size() >= 2) {
159 Vector<Ref<TileSetAtlasSource>> to_merge;
160 for (int i = 0; i < selected.size(); i++) {
161 int source_id = atlas_merging_atlases_list->get_item_metadata(selected[i]);
162 to_merge.push_back(tile_set->get_source(source_id));
163 }
164 _generate_merged(to_merge, next_line_after_column);
165 preview->set_texture(merged->get_texture());
166 preview->show();
167 select_2_atlases_label->hide();
168 get_ok_button()->set_disabled(false);
169 merge_button->set_disabled(false);
170 } else {
171 _generate_merged(Vector<Ref<TileSetAtlasSource>>(), next_line_after_column);
172 preview->set_texture(Ref<Texture2D>());
173 preview->hide();
174 select_2_atlases_label->show();
175 get_ok_button()->set_disabled(true);
176 merge_button->set_disabled(true);
177 }
178}
179
180void AtlasMergingDialog::_merge_confirmed(String p_path) {
181 ERR_FAIL_COND(!merged.is_valid());
182
183 Ref<ImageTexture> output_image_texture = merged->get_texture();
184 output_image_texture->get_image()->save_png(p_path);
185
186 ResourceLoader::import(p_path);
187
188 Ref<Texture2D> new_texture_resource = ResourceLoader::load(p_path, "Texture2D");
189 merged->set_texture(new_texture_resource);
190
191 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
192 undo_redo->create_action(TTR("Merge TileSetAtlasSource"));
193 int next_id = tile_set->get_next_source_id();
194 undo_redo->add_do_method(*tile_set, "add_source", merged, next_id);
195 undo_redo->add_undo_method(*tile_set, "remove_source", next_id);
196
197 if (delete_original_atlases) {
198 // Delete originals if needed.
199 Vector<int> selected = atlas_merging_atlases_list->get_selected_items();
200 for (int i = 0; i < selected.size(); i++) {
201 int source_id = atlas_merging_atlases_list->get_item_metadata(selected[i]);
202 Ref<TileSetAtlasSource> tas = tile_set->get_source(source_id);
203 undo_redo->add_do_method(*tile_set, "remove_source", source_id);
204 undo_redo->add_undo_method(*tile_set, "add_source", tas, source_id);
205
206 // Add the tile proxies.
207 for (int tile_index = 0; tile_index < tas->get_tiles_count(); tile_index++) {
208 Vector2i tile_id = tas->get_tile_id(tile_index);
209 undo_redo->add_do_method(*tile_set, "set_coords_level_tile_proxy", source_id, tile_id, next_id, merged_mapping[i][tile_id]);
210 if (tile_set->has_coords_level_tile_proxy(source_id, tile_id)) {
211 Array a = tile_set->get_coords_level_tile_proxy(source_id, tile_id);
212 undo_redo->add_undo_method(*tile_set, "set_coords_level_tile_proxy", a[0], a[1]);
213 } else {
214 undo_redo->add_undo_method(*tile_set, "remove_coords_level_tile_proxy", source_id, tile_id);
215 }
216 }
217 }
218 }
219 undo_redo->commit_action();
220 commited_actions_count++;
221
222 hide();
223}
224
225void AtlasMergingDialog::ok_pressed() {
226 delete_original_atlases = false;
227 editor_file_dialog->popup_file_dialog();
228}
229
230void AtlasMergingDialog::cancel_pressed() {
231 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
232 for (int i = 0; i < commited_actions_count; i++) {
233 undo_redo->undo();
234 }
235 commited_actions_count = 0;
236}
237
238void AtlasMergingDialog::custom_action(const String &p_action) {
239 if (p_action == "merge") {
240 delete_original_atlases = true;
241 editor_file_dialog->popup_file_dialog();
242 }
243}
244
245bool AtlasMergingDialog::_set(const StringName &p_name, const Variant &p_value) {
246 if (p_name == "next_line_after_column" && p_value.get_type() == Variant::INT) {
247 next_line_after_column = p_value;
248 _update_texture();
249 return true;
250 }
251 return false;
252}
253
254bool AtlasMergingDialog::_get(const StringName &p_name, Variant &r_ret) const {
255 if (p_name == "next_line_after_column") {
256 r_ret = next_line_after_column;
257 return true;
258 }
259 return false;
260}
261
262void AtlasMergingDialog::_notification(int p_what) {
263 switch (p_what) {
264 case NOTIFICATION_VISIBILITY_CHANGED: {
265 if (is_visible()) {
266 _update_texture();
267 }
268 } break;
269 }
270}
271
272void AtlasMergingDialog::update_tile_set(Ref<TileSet> p_tile_set) {
273 ERR_FAIL_COND(!p_tile_set.is_valid());
274 tile_set = p_tile_set;
275
276 atlas_merging_atlases_list->clear();
277 for (int i = 0; i < p_tile_set->get_source_count(); i++) {
278 int source_id = p_tile_set->get_source_id(i);
279 Ref<TileSetAtlasSource> atlas_source = p_tile_set->get_source(source_id);
280 if (atlas_source.is_valid()) {
281 Ref<Texture2D> texture = atlas_source->get_texture();
282 if (texture.is_valid()) {
283 String item_text = vformat(TTR("%s (ID: %d)"), texture->get_path().get_file(), source_id);
284 atlas_merging_atlases_list->add_item(item_text, texture);
285 atlas_merging_atlases_list->set_item_metadata(-1, source_id);
286 }
287 }
288 }
289
290 get_ok_button()->set_disabled(true);
291 merge_button->set_disabled(true);
292
293 commited_actions_count = 0;
294}
295
296AtlasMergingDialog::AtlasMergingDialog() {
297 // Atlas merging window.
298 set_title(TTR("Atlas Merging"));
299 set_hide_on_ok(false);
300
301 // Ok buttons
302 set_ok_button_text(TTR("Merge (Keep original Atlases)"));
303 get_ok_button()->set_disabled(true);
304 merge_button = add_button(TTR("Merge"), true, "merge");
305 merge_button->set_disabled(true);
306
307 HSplitContainer *atlas_merging_h_split_container = memnew(HSplitContainer);
308 atlas_merging_h_split_container->set_h_size_flags(Control::SIZE_EXPAND_FILL);
309 atlas_merging_h_split_container->set_v_size_flags(Control::SIZE_EXPAND_FILL);
310 add_child(atlas_merging_h_split_container);
311
312 // Atlas sources item list.
313 atlas_merging_atlases_list = memnew(ItemList);
314 atlas_merging_atlases_list->set_fixed_icon_size(Size2(60, 60) * EDSCALE);
315 atlas_merging_atlases_list->set_h_size_flags(Control::SIZE_EXPAND_FILL);
316 atlas_merging_atlases_list->set_v_size_flags(Control::SIZE_EXPAND_FILL);
317 atlas_merging_atlases_list->set_texture_filter(CanvasItem::TEXTURE_FILTER_NEAREST_WITH_MIPMAPS);
318 atlas_merging_atlases_list->set_custom_minimum_size(Size2(100, 200));
319 atlas_merging_atlases_list->set_select_mode(ItemList::SELECT_MULTI);
320 atlas_merging_atlases_list->connect("multi_selected", callable_mp(this, &AtlasMergingDialog::_update_texture).unbind(2));
321 atlas_merging_h_split_container->add_child(atlas_merging_atlases_list);
322
323 VBoxContainer *atlas_merging_right_panel = memnew(VBoxContainer);
324 atlas_merging_right_panel->set_h_size_flags(Control::SIZE_EXPAND_FILL);
325 atlas_merging_right_panel->set_texture_filter(CanvasItem::TEXTURE_FILTER_NEAREST_WITH_MIPMAPS);
326 atlas_merging_h_split_container->add_child(atlas_merging_right_panel);
327
328 // Settings.
329 Label *settings_label = memnew(Label);
330 settings_label->set_text(TTR("Settings:"));
331 atlas_merging_right_panel->add_child(settings_label);
332
333 columns_editor_property = memnew(EditorPropertyInteger);
334 columns_editor_property->set_label(TTR("Next Line After Column"));
335 columns_editor_property->set_object_and_property(this, "next_line_after_column");
336 columns_editor_property->update_property();
337 columns_editor_property->connect("property_changed", callable_mp(this, &AtlasMergingDialog::_property_changed));
338 atlas_merging_right_panel->add_child(columns_editor_property);
339
340 // Preview.
341 Label *preview_label = memnew(Label);
342 preview_label->set_text(TTR("Preview:"));
343 atlas_merging_right_panel->add_child(preview_label);
344
345 preview = memnew(TextureRect);
346 preview->set_h_size_flags(Control::SIZE_EXPAND_FILL);
347 preview->set_v_size_flags(Control::SIZE_EXPAND_FILL);
348 preview->set_expand_mode(TextureRect::EXPAND_IGNORE_SIZE);
349 preview->hide();
350 preview->set_stretch_mode(TextureRect::STRETCH_KEEP_ASPECT_CENTERED);
351 atlas_merging_right_panel->add_child(preview);
352
353 select_2_atlases_label = memnew(Label);
354 select_2_atlases_label->set_h_size_flags(Control::SIZE_EXPAND_FILL);
355 select_2_atlases_label->set_v_size_flags(Control::SIZE_EXPAND_FILL);
356 select_2_atlases_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);
357 select_2_atlases_label->set_vertical_alignment(VERTICAL_ALIGNMENT_CENTER);
358 select_2_atlases_label->set_text(TTR("Please select two atlases or more."));
359 atlas_merging_right_panel->add_child(select_2_atlases_label);
360
361 // The file dialog to choose the texture path.
362 editor_file_dialog = memnew(EditorFileDialog);
363 editor_file_dialog->set_file_mode(EditorFileDialog::FILE_MODE_SAVE_FILE);
364 editor_file_dialog->add_filter("*.png");
365 editor_file_dialog->connect("file_selected", callable_mp(this, &AtlasMergingDialog::_merge_confirmed));
366 add_child(editor_file_dialog);
367}
368