From 6f645d996dcef0526f47102dfa48804e43244700 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Nov=C3=A1k?= <42614907+novhack@users.noreply.github.com> Date: Fri, 31 Jul 2020 22:26:52 +0200 Subject: [PATCH] Refactor export dialog (#288) * Split export code from export dialog to Export.gd autoload Clean access to child nodes of export dialog * Fix export variables set in Main.gd * Fix more wrong variable assignments Co-authored-by: alexhayoo <65853178+alexhayoo@users.noreply.github.com> --- project.godot | 1 + src/Autoload/Export.gd | 315 ++++++++++++++++ src/Autoload/OpenSave.gd | 16 +- src/Main.gd | 6 +- src/UI/Dialogs/ExportDialog.gd | 649 ++++++++++----------------------- src/UI/TopMenuContainer.gd | 2 +- 6 files changed, 514 insertions(+), 475 deletions(-) create mode 100644 src/Autoload/Export.gd diff --git a/project.godot b/project.godot index be7d32ddf..a18285579 100644 --- a/project.godot +++ b/project.godot @@ -117,6 +117,7 @@ OpenSave="*res://src/Autoload/OpenSave.gd" DrawingAlgos="*res://src/Autoload/DrawingAlgos.gd" Tools="*res://src/Autoload/Tools.gd" Html5FileExchange="*res://src/Autoload/HTML5FileExchange.gd" +Export="*res://src/Autoload/Export.gd" [debug] diff --git a/src/Autoload/Export.gd b/src/Autoload/Export.gd new file mode 100644 index 000000000..0768a8da3 --- /dev/null +++ b/src/Autoload/Export.gd @@ -0,0 +1,315 @@ +extends Node + +enum ExportTab { FRAME = 0, SPRITESHEET = 1, ANIMATION = 2 } +var current_tab : int = ExportTab.FRAME + +# Frame options +var frame_number := 0 + +# All frames and their layers processed/blended into images +var processed_images = [] # Image[] + +# Spritesheet options +var frame_current_tag := 0 # Export only current frame tag +var number_of_frames := 1 +enum Orientation { ROWS = 0, COLUMNS = 1 } +var orientation : int = Orientation.ROWS +# How many rows/columns before new line is added +var lines_count := 1 + +# Animation options +enum AnimationType { MULTIPLE_FILES = 0, ANIMATED = 1 } +var animation_type : int = AnimationType.MULTIPLE_FILES +var background_color : Color = Color.white +enum AnimationDirection { FORWARD = 0, BACKWARDS = 1, PING_PONG = 2 } +var direction : int = AnimationDirection.FORWARD + +# Options +var resize := 100 +var interpolation := 0 # Image.Interpolation +var new_dir_for_each_frame_tag : bool = true # you don't need to store this after export + +# Export directory path and export file name +var directory_path := "" +var file_name := "untitled" +var file_format : int = FileFormat.PNG +enum FileFormat { PNG = 0, GIF = 1} + +# Store all settings after export, enables a quick re-export with same settings +var was_exported : bool = false +var exported_tab : int +var exported_frame_number : int +var exported_frame_current_tag : int +var exported_orientation : int +var exported_lines_count : int +var exported_animation_type : int +var exported_background_color : Color +var exported_direction : int +var exported_resize : int +var exported_interpolation : int +var exported_directory_path : String +var exported_file_name : String +var exported_file_format : int + +# Export coroutine signal +var stop_export = false + +var file_exists_alert = "File %s already exists. Overwrite?" + + +func process_frame() -> void: + var frame = Global.current_project.frames[frame_number - 1] + var image := Image.new() + image.create(Global.current_project.size.x, Global.current_project.size.y, false, Image.FORMAT_RGBA8) + blend_layers(image, frame) + processed_images.clear() + processed_images.append(image) + + +func process_spritesheet() -> void: + # Range of frames determined by tags + var frames := [] + if frame_current_tag > 0: + var frame_start = Global.current_project.animation_tags[frame_current_tag - 1].from + var frame_end = Global.current_project.animation_tags[frame_current_tag - 1].to + frames = Global.current_project.frames.slice(frame_start-1, frame_end-1, 1, true) + else: + frames = Global.current_project.frames + + # Then store the size of frames for other functions + number_of_frames = frames.size() + + # If rows mode selected calculate columns count and vice versa + var spritesheet_columns = lines_count if orientation == Orientation.ROWS else frames_divided_by_spritesheet_lines() + var spritesheet_rows = lines_count if orientation == Orientation.COLUMNS else frames_divided_by_spritesheet_lines() + + var width = Global.current_project.size.x * spritesheet_columns + var height = Global.current_project.size.y * spritesheet_rows + + var whole_image := Image.new() + whole_image.create(width, height, false, Image.FORMAT_RGBA8) + whole_image.lock() + var origin := Vector2.ZERO + var hh := 0 + var vv := 0 + + for frame in frames: + if orientation == Orientation.ROWS: + if vv < spritesheet_columns: + origin.x = Global.current_project.size.x * vv + vv += 1 + else: + hh += 1 + origin.x = 0 + vv = 1 + origin.y = Global.current_project.size.y * hh + else: + if hh < spritesheet_rows: + origin.y = Global.current_project.size.y * hh + hh += 1 + else: + vv += 1 + origin.y = 0 + hh = 1 + origin.x = Global.current_project.size.x * vv + blend_layers(whole_image, frame, origin) + + processed_images.clear() + processed_images.append(whole_image) + + +func process_animation() -> void: + processed_images.clear() + for frame in Global.current_project.frames: + var image := Image.new() + image.create(Global.current_project.size.x, Global.current_project.size.y, false, Image.FORMAT_RGBA8) + blend_layers(image, frame) + processed_images.append(image) + + +func export_processed_images(ignore_overwrites: bool, path_validation_alert_popup: AcceptDialog, file_exists_alert_popup: AcceptDialog, export_dialog: AcceptDialog ) -> bool: + # Stop export if directory path or file name are not valid + var dir = Directory.new() + if not dir.dir_exists(directory_path) or not file_name.is_valid_filename(): + path_validation_alert_popup.popup_centered() + return false + + # Check export paths + var export_paths = [] + for i in range(processed_images.size()): + stop_export = false + var multiple_files := true if (current_tab == ExportTab.ANIMATION && animation_type == AnimationType.MULTIPLE_FILES) else false + var export_path = create_export_path(multiple_files, i + 1) + # If user want to create new directory for each animation tag then check if directories exist and create them if not + if multiple_files and new_dir_for_each_frame_tag: + var frame_tag_directory := Directory.new() + if not frame_tag_directory.dir_exists(export_path.get_base_dir()): + frame_tag_directory.open(directory_path) + frame_tag_directory.make_dir(export_path.get_base_dir().get_file()) + # Check if the file already exists + var fileCheck = File.new() + if fileCheck.file_exists(export_path): + # Ask user if he want's to overwrite the file + if not was_exported or (was_exported and not ignore_overwrites): + # Overwrite existing file? + file_exists_alert_popup.dialog_text = file_exists_alert % export_path + file_exists_alert_popup.popup_centered() + # Stops the function until the user decides if he want's to overwrite + yield(export_dialog, "resume_export_function") + if stop_export: + # User decided to stop export + return + export_paths.append(export_path) + # Only get one export path if single file animated image is exported + if current_tab == ExportTab.ANIMATION && animation_type == AnimationType.ANIMATED: + break + + # Scale images that are to export + scale_processed_images() + + if current_tab == ExportTab.ANIMATION && animation_type == AnimationType.ANIMATED: + var frame_delay_in_ms = Global.animation_timer.wait_time * 100 + + $GifExporter.begin_export(export_paths[0], processed_images[0].get_width(), processed_images[0].get_height(), frame_delay_in_ms, 0) + match direction: + AnimationDirection.FORWARD: + for i in range(processed_images.size()): + $GifExporter.write_frame(processed_images[i], background_color, frame_delay_in_ms) + AnimationDirection.BACKWARDS: + for i in range(processed_images.size() - 1, -1, -1): + $GifExporter.write_frame(processed_images[i], background_color, frame_delay_in_ms) + AnimationDirection.PING_PONG: + for i in range(0, processed_images.size()): + $GifExporter.write_frame(processed_images[i], background_color, frame_delay_in_ms) + for i in range(processed_images.size() - 2, 0, -1): + $GifExporter.write_frame(processed_images[i], background_color, frame_delay_in_ms) + $GifExporter.end_export() + else: + for i in range(processed_images.size()): + if OS.get_name() == "HTML5": + Html5FileExchange.save_image(processed_images[i], export_paths[i].get_file()) + else: + var err = processed_images[i].save_png(export_paths[i]) + if err != OK: + OS.alert("Can't save file") + + # Store settings for quick export and when the dialog is opened again + was_exported = true + store_export_settings() + Global.file_menu.get_popup().set_item_text(5, tr("Export") + " %s" % (file_name + file_format_string(file_format))) + Global.notification_label("File(s) exported") + return true + + +func scale_processed_images() -> void: + for processed_image in processed_images: + if resize != 100: + processed_image.unlock() + processed_image.resize(processed_image.get_size().x * resize / 100, processed_image.get_size().y * resize / 100, interpolation) + + +func file_format_string(format_enum : int) -> String: + match format_enum: + 0: # PNG + return '.png' + 1: # GIF + return '.gif' + _: + return '' + + +func create_export_path(multifile: bool, frame: int = 0) -> String: + var path = file_name + # Only append frame number when there are multiple files exported + if multifile: + var frame_tag_and_start_id = get_proccessed_image_animation_tag_and_start_id(frame - 1) + # Check if exported frame is in frame tag + if frame_tag_and_start_id != null: + var frame_tag = frame_tag_and_start_id[0] + var start_id = frame_tag_and_start_id[1] + # Remove unallowed characters in frame tag directory + var regex := RegEx.new() + regex.compile("[^a-zA-Z0-9_]+") + var frame_tag_dir = regex.sub(frame_tag, "", true) + if new_dir_for_each_frame_tag: + # Add frame tag if frame has one + # (frame - start_id + 1) Makes frames id to start from 1 in each frame tag directory + path += "_" + frame_tag_dir + "_" + String(frame - start_id + 1) + return directory_path.plus_file(frame_tag_dir).plus_file(path + file_format_string(file_format)) + else: + # Add frame tag if frame has one + # (frame - start_id + 1) Makes frames id to start from 1 in each frame tag + path += "_" + frame_tag_dir + "_" + String(frame - start_id + 1) + else: + path += "_" + String(frame) + + return directory_path.plus_file(path + file_format_string(file_format)) + + +func get_proccessed_image_animation_tag_and_start_id(processed_image_id : int) -> Array: + var result_animation_tag_and_start_id = null + for animation_tag in Global.current_project.animation_tags: + # Check if processed image is in frame tag and assign frame tag and start id if yes + # Then stop + if (processed_image_id + 1) >= animation_tag.from and (processed_image_id + 1) <= animation_tag.to: + result_animation_tag_and_start_id = [animation_tag.name, animation_tag.from] + break + return result_animation_tag_and_start_id + + +# Blends canvas layers into passed image starting from the origin position +func blend_layers(image : Image, frame : Frame, origin : Vector2 = Vector2(0, 0)) -> void: + image.lock() + var layer_i := 0 + for cel in frame.cels: + if Global.current_project.layers[layer_i].visible: + var cel_image := Image.new() + cel_image.copy_from(cel.image) + cel_image.lock() + if cel.opacity < 1: # If we have cel transparency + for xx in cel_image.get_size().x: + for yy in cel_image.get_size().y: + var pixel_color := cel_image.get_pixel(xx, yy) + var alpha : float = pixel_color.a * cel.opacity + cel_image.set_pixel(xx, yy, Color(pixel_color.r, pixel_color.g, pixel_color.b, alpha)) + image.blend_rect(cel_image, Rect2(Global.canvas.location, Global.current_project.size), origin) + layer_i += 1 + image.unlock() + + +func frames_divided_by_spritesheet_lines() -> int: + return int(ceil(number_of_frames / float(lines_count))) + + +func store_export_settings() -> void: + exported_tab = current_tab + exported_frame_number = frame_number + exported_frame_current_tag = frame_current_tag + exported_orientation = orientation + exported_lines_count = lines_count + exported_animation_type = animation_type + exported_background_color = background_color + exported_direction = direction + exported_resize = resize + exported_interpolation = interpolation + exported_directory_path = directory_path + exported_file_name = file_name + exported_file_format = file_format + + +# Fill the dialog with previous export settings +func restore_previous_export_settings() -> void: + current_tab = exported_tab + frame_number = exported_frame_number if exported_frame_number <= Global.current_project.frames.size() else Global.current_project.frames.size() + frame_current_tag = exported_frame_current_tag if exported_frame_current_tag <= Global.current_project.animation_tags.size() else 0 + orientation = exported_orientation + lines_count = exported_lines_count + animation_type = exported_animation_type + background_color = exported_background_color + direction = exported_direction + resize = exported_resize + interpolation = exported_interpolation + directory_path = exported_directory_path + file_name = exported_file_name + file_format = exported_file_format + diff --git a/src/Autoload/OpenSave.gd b/src/Autoload/OpenSave.gd index 775692e91..33c6b57a5 100644 --- a/src/Autoload/OpenSave.gd +++ b/src/Autoload/OpenSave.gd @@ -112,9 +112,9 @@ func open_pxo_file(path : String, untitled_backup : bool = false) -> void: # Set last opened project path and save Global.config_cache.set_value("preferences", "last_project_path", path) Global.config_cache.save("user://cache.ini") - Global.export_dialog.file_name = path.get_file().trim_suffix(".pxo") - Global.export_dialog.directory_path = path.get_base_dir() - Global.export_dialog.was_exported = false + Export.file_name = path.get_file().trim_suffix(".pxo") + Export.directory_path = path.get_base_dir() + Export.was_exported = false Global.file_menu.get_popup().set_item_text(3, tr("Save") + " %s" % path.get_file()) Global.file_menu.get_popup().set_item_text(5, tr("Export")) @@ -311,9 +311,9 @@ func save_pxo_file(path : String, autosave : bool, use_zstd_compression := true, # Set last opened project path and save Global.config_cache.set_value("preferences", "last_project_path", path) Global.config_cache.save("user://cache.ini") - Global.export_dialog.file_name = path.get_file().trim_suffix(".pxo") - Global.export_dialog.directory_path = path.get_base_dir() - Global.export_dialog.was_exported = false + Export.file_name = path.get_file().trim_suffix(".pxo") + Export.directory_path = path.get_base_dir() + Export.was_exported = false Global.file_menu.get_popup().set_item_text(3, tr("Save") + " %s" % path.get_file()) else: @@ -446,8 +446,8 @@ func set_new_tab(project : Project, path : String) -> void: Global.window_title = Global.window_title + "(*)" var file_name := path.get_basename().get_file() var directory_path := path.get_basename().replace(file_name, "") - Global.export_dialog.directory_path = directory_path - Global.export_dialog.file_name = file_name + Export.directory_path = directory_path + Export.file_name = file_name func update_autosave() -> void: diff --git a/src/Main.gd b/src/Main.gd index 3a21ae761..072cea309 100644 --- a/src/Main.gd +++ b/src/Main.gd @@ -196,9 +196,9 @@ func _on_QuitDialog_confirmed() -> void: func _on_BackupConfirmation_confirmed(project_paths : Array, backup_paths : Array) -> void: OpenSave.reload_backup_file(project_paths, backup_paths) OpenSave.autosave_timer.start() - Global.export_dialog.file_name = OpenSave.current_save_paths[0].get_file().trim_suffix(".pxo") - Global.export_dialog.directory_path = OpenSave.current_save_paths[0].get_base_dir() - Global.export_dialog.was_exported = false + Export.file_name = OpenSave.current_save_paths[0].get_file().trim_suffix(".pxo") + Export.directory_path = OpenSave.current_save_paths[0].get_base_dir() + Export.was_exported = false Global.file_menu.get_popup().set_item_text(3, tr("Save") + " %s" % OpenSave.current_save_paths[0].get_file()) Global.file_menu.get_popup().set_item_text(5, tr("Export")) diff --git a/src/UI/Dialogs/ExportDialog.gd b/src/UI/Dialogs/ExportDialog.gd index 09a59f625..4c96f14a0 100644 --- a/src/UI/Dialogs/ExportDialog.gd +++ b/src/UI/Dialogs/ExportDialog.gd @@ -1,222 +1,133 @@ extends AcceptDialog -enum ExportTab { FRAME = 0, SPRITESHEET = 1, ANIMATION = 2 } -var current_tab : int = ExportTab.FRAME - -# All frames and their layers processed/blended into images -var processed_images = [] # Image[] - -# Frame options -var frame_number := 0 - -# Spritesheet options -var frame_current_tag := 0 # Export only current frame tag -var number_of_frames := 1 -enum Orientation { ROWS = 0, COLUMNS = 1 } -var orientation : int = Orientation.ROWS -# How many rows/columns before new line is added -var lines_count := 1 - -# Animation options -enum AnimationType { MULTIPLE_FILES = 0, ANIMATED = 1 } -var animation_type : int = AnimationType.MULTIPLE_FILES -var background_color : Color = Color.white -enum AnimationDirection { FORWARD = 0, BACKWARDS = 1, PING_PONG = 2 } -var direction : int = AnimationDirection.FORWARD - -# Options -var resize := 100 -var interpolation := 0 # Image.Interpolation -var new_dir_for_each_frame_tag : bool = true # you don't need to store this after export - -# Export directory path and export file name -var directory_path := "" -var file_name := "untitled" -var file_format : int = FileFormat.PNG -enum FileFormat { PNG = 0, GIF = 1} - -var file_exists_alert = "File %s already exists. Overwrite?" - -# Store all settings after export, enables a quick re-export with same settings -var was_exported : bool = false -var exported_tab : int -var exported_frame_number : int -var exported_frame_current_tag : int -var exported_orientation : int -var exported_lines_count : int -var exported_animation_type : int -var exported_background_color : Color -var exported_direction : int -var exported_resize : int -var exported_interpolation : int -var exported_directory_path : String -var exported_file_name : String -var exported_file_format : int - -# Export coroutine signal +# called when user resumes export after filename collision signal resume_export_function() -var stop_export = false var animated_preview_current_frame := 0 var animated_preview_frames = [] +onready var tabs = $VBoxContainer/Tabs +onready var popups = $Popups +onready var file_exists_alert_popup = $Popups/FileExistsAlert +onready var path_validation_alert_popup = $Popups/PathValidationAlert +onready var path_dialog_popup = $Popups/PathDialog + +onready var previews = $VBoxContainer/PreviewScroll/Previews + +onready var frame_options = $VBoxContainer/FrameOptions +onready var frame_options_frame_number = $VBoxContainer/FrameOptions/FrameNumber/FrameNumber + +onready var spritesheet_options = $VBoxContainer/SpritesheetOptions +onready var spritesheet_options_frames = $VBoxContainer/SpritesheetOptions/Frames/Frames +onready var spritesheet_options_orientation = $VBoxContainer/SpritesheetOptions/Orientation/Orientation +onready var spritesheet_options_lines_count = $VBoxContainer/SpritesheetOptions/Orientation/LinesCount +onready var spritesheet_options_lines_count_label = $VBoxContainer/SpritesheetOptions/Orientation/LinesCountLabel + +onready var animation_options = $VBoxContainer/AnimationOptions +onready var animation_options_animation_type = $VBoxContainer/AnimationOptions/AnimationType +onready var animation_options_animation_options = $VBoxContainer/AnimationOptions/AnimatedOptions +onready var animation_options_background_color = $VBoxContainer/AnimationOptions/AnimatedOptions/BackgroundColor +onready var animation_options_direction = $VBoxContainer/AnimationOptions/AnimatedOptions/Direction + +onready var frame_timer = $FrameTimer + +onready var options_resize = $VBoxContainer/Options/Resize +onready var options_interpolation = $VBoxContainer/Options/Interpolation +onready var path_container = $VBoxContainer/Path +onready var path_line_edit = $VBoxContainer/Path/PathLineEdit +onready var file_line_edit = $VBoxContainer/File/FileLineEdit +onready var file_file_format = $VBoxContainer/File/FileFormat + +onready var animation_options_multiple_animations_directories = $VBoxContainer/AnimationOptions/MultipleAnimationsDirectories + func _ready() -> void: - $VBoxContainer/Tabs.add_tab("Frame") - $VBoxContainer/Tabs.add_tab("Spritesheet") - $VBoxContainer/Tabs.add_tab("Animation") + tabs.add_tab("Frame") + tabs.add_tab("Spritesheet") + tabs.add_tab("Animation") if OS.get_name() == "Windows": add_button("Cancel", true, "cancel") - $Popups/FileExistsAlert.add_button("Cancel Export", true, "cancel") + file_exists_alert_popup.add_button("Cancel Export", true, "cancel") else: add_button("Cancel", false, "cancel") - $Popups/FileExistsAlert.add_button("Cancel Export", false, "cancel") + file_exists_alert_popup.add_button("Cancel Export", false, "cancel") # Disable GIF export for unsupported platforms if not $GifExporter.is_platform_supported(): - $VBoxContainer/AnimationOptions/AnimationType.selected = AnimationType.MULTIPLE_FILES - $VBoxContainer/AnimationOptions/AnimationType.disabled = true + animation_options_animation_type.selected = Export.AnimationType.MULTIPLE_FILES + animation_options_animation_type.disabled = true func show_tab() -> void: - $VBoxContainer/FrameOptions.hide() - $VBoxContainer/SpritesheetOptions.hide() - $VBoxContainer/AnimationOptions.hide() + frame_options.hide() + spritesheet_options.hide() + animation_options.hide() - match current_tab: - ExportTab.FRAME: - file_format = FileFormat.PNG - $VBoxContainer/File/FileFormat.selected = FileFormat.PNG - $FrameTimer.stop() - if not was_exported: - frame_number = Global.current_project.current_frame + 1 - $VBoxContainer/FrameOptions/FrameNumber/FrameNumber.max_value = Global.current_project.frames.size() + 1 - var prev_frame_number = $VBoxContainer/FrameOptions/FrameNumber/FrameNumber.value - $VBoxContainer/FrameOptions/FrameNumber/FrameNumber.value = frame_number - if prev_frame_number == frame_number: - process_frame() - $VBoxContainer/FrameOptions.show() - ExportTab.SPRITESHEET: + match Export.current_tab: + Export.ExportTab.FRAME: + Export.file_format = Export.FileFormat.PNG + file_file_format.selected = Export.FileFormat.PNG + frame_timer.stop() + if not Export.was_exported: + Export.frame_number = Global.current_project.current_frame + 1 + frame_options_frame_number.max_value = Global.current_project.frames.size() + 1 + var prev_frame_number = frame_options_frame_number.value + frame_options_frame_number.value = Export.frame_number + if prev_frame_number == Export.frame_number: + Export.process_frame() + frame_options.show() + Export.ExportTab.SPRITESHEET: create_frame_tag_list() - file_format = FileFormat.PNG - if not was_exported: - orientation = Orientation.ROWS - lines_count = int(ceil(sqrt(number_of_frames))) - process_spritesheet() - $VBoxContainer/File/FileFormat.selected = FileFormat.PNG - $VBoxContainer/SpritesheetOptions/Frames/Frames.select(frame_current_tag) - $FrameTimer.stop() - $VBoxContainer/SpritesheetOptions/Orientation/Orientation.selected = orientation - $VBoxContainer/SpritesheetOptions/Orientation/LinesCount.max_value = number_of_frames - $VBoxContainer/SpritesheetOptions/Orientation/LinesCount.value = lines_count - $VBoxContainer/SpritesheetOptions/Orientation/LinesCountLabel.text = "Columns:" - $VBoxContainer/SpritesheetOptions.show() - ExportTab.ANIMATION: + Export.file_format = Export.FileFormat.PNG + if not Export.was_exported: + Export.orientation = Export.Orientation.ROWS + Export.lines_count = int(ceil(sqrt(Export.number_of_frames))) + Export.process_spritesheet() + file_file_format.selected = Export.FileFormat.PNG + spritesheet_options_frames.select(Export.frame_current_tag) + frame_timer.stop() + spritesheet_options_orientation.selected = Export.orientation + spritesheet_options_lines_count.max_value = Export.number_of_frames + spritesheet_options_lines_count.value = Export.lines_count + spritesheet_options_lines_count_label.text = "Columns:" + spritesheet_options.show() + Export.ExportTab.ANIMATION: set_file_format_selector() - process_animation() - $VBoxContainer/AnimationOptions/AnimationType.selected = animation_type - $VBoxContainer/AnimationOptions/AnimatedOptions/BackgroundColor.color = background_color - $VBoxContainer/AnimationOptions/AnimatedOptions/Direction.selected = direction - $VBoxContainer/AnimationOptions.show() + Export.process_animation() + animation_options_animation_type.selected = Export.animation_type + animation_options_background_color.color = Export.background_color + animation_options_direction.selected = Export.direction + animation_options.show() set_preview() - $VBoxContainer/Tabs.current_tab = current_tab + tabs.current_tab = Export.current_tab func external_export() -> void: - restore_previous_export_settings() - match current_tab: - ExportTab.FRAME: - process_frame() - ExportTab.SPRITESHEET: - process_spritesheet() - ExportTab.ANIMATION: - process_animation() - export_processed_images(true) - - -func process_frame() -> void: - var frame = Global.current_project.frames[frame_number - 1] - var image := Image.new() - image.create(Global.current_project.size.x, Global.current_project.size.y, false, Image.FORMAT_RGBA8) - blend_layers(image, frame) - processed_images.clear() - processed_images.append(image) - - -func process_spritesheet() -> void: - # Range of frames determined by tags - var frames := [] - if frame_current_tag > 0: - var frame_start = Global.current_project.animation_tags[frame_current_tag - 1].from - var frame_end = Global.current_project.animation_tags[frame_current_tag - 1].to - frames = Global.current_project.frames.slice(frame_start-1, frame_end-1, 1, true) - else: - frames = Global.current_project.frames - - # Then store the size of frames for other functions - number_of_frames = frames.size() - - # If rows mode selected calculate columns count and vice versa - var spritesheet_columns = lines_count if orientation == Orientation.ROWS else frames_divided_by_spritesheet_lines() - var spritesheet_rows = lines_count if orientation == Orientation.COLUMNS else frames_divided_by_spritesheet_lines() - - var width = Global.current_project.size.x * spritesheet_columns - var height = Global.current_project.size.y * spritesheet_rows - - var whole_image := Image.new() - whole_image.create(width, height, false, Image.FORMAT_RGBA8) - whole_image.lock() - var origin := Vector2.ZERO - var hh := 0 - var vv := 0 - - for frame in frames: - if orientation == Orientation.ROWS: - if vv < spritesheet_columns: - origin.x = Global.current_project.size.x * vv - vv += 1 - else: - hh += 1 - origin.x = 0 - vv = 1 - origin.y = Global.current_project.size.y * hh - else: - if hh < spritesheet_rows: - origin.y = Global.current_project.size.y * hh - hh += 1 - else: - vv += 1 - origin.y = 0 - hh = 1 - origin.x = Global.current_project.size.x * vv - blend_layers(whole_image, frame, origin) - - processed_images.clear() - processed_images.append(whole_image) - - -func process_animation() -> void: - processed_images.clear() - for frame in Global.current_project.frames: - var image := Image.new() - image.create(Global.current_project.size.x, Global.current_project.size.y, false, Image.FORMAT_RGBA8) - blend_layers(image, frame) - processed_images.append(image) + Export.restore_previous_export_settings() + match Export.current_tab: + Export.ExportTab.FRAME: + Export.process_frame() + Export.ExportTab.SPRITESHEET: + Export.process_spritesheet() + Export.ExportTab.ANIMATION: + Export.process_animation() + if Export.export_processed_images(true, path_validation_alert_popup, file_exists_alert_popup, self): + hide() func set_preview() -> void: remove_previews() - if processed_images.size() == 1 and current_tab != ExportTab.ANIMATION: - $VBoxContainer/PreviewScroll/Previews.columns = 1 - add_image_preview(processed_images[0]) + if Export.processed_images.size() == 1 and Export.current_tab != Export.ExportTab.ANIMATION: + previews.columns = 1 + add_image_preview(Export.processed_images[0]) else: - match animation_type: - AnimationType.MULTIPLE_FILES: - $VBoxContainer/PreviewScroll/Previews.columns = ceil(sqrt(processed_images.size())) - for i in range(processed_images.size()): - add_image_preview(processed_images[i], i + 1) - AnimationType.ANIMATED: - $VBoxContainer/PreviewScroll/Previews.columns = 1 + match Export.animation_type: + Export.AnimationType.MULTIPLE_FILES: + previews.columns = ceil(sqrt(Export.processed_images.size())) + for i in range(Export.processed_images.size()): + add_image_preview(Export.processed_images[i], i + 1) + Export.AnimationType.ANIMATED: + previews.columns = 1 add_animated_preview() @@ -233,14 +144,14 @@ func add_image_preview(image: Image, canvas_number: int = -1) -> void: label.text = String(canvas_number) container.add_child(label) - $VBoxContainer/PreviewScroll/Previews.add_child(container) + previews.add_child(container) func add_animated_preview() -> void: - animated_preview_current_frame = processed_images.size() - 1 if direction == AnimationDirection.BACKWARDS else 0 + animated_preview_current_frame = Export.processed_images.size() - 1 if Export.direction == Export.AnimationDirection.BACKWARDS else 0 animated_preview_frames = [] - for processed_image in processed_images: + for processed_image in Export.processed_images: var texture = ImageTexture.new() texture.create_from_image(processed_image, 0) animated_preview_frames.push_back(texture) @@ -252,8 +163,8 @@ func add_animated_preview() -> void: preview.texture = animated_preview_frames[animated_preview_current_frame] container.add_child(preview) - $VBoxContainer/PreviewScroll/Previews.add_child(container) - $FrameTimer.start() + previews.add_child(container) + frame_timer.start() func create_preview_container() -> VBoxContainer: @@ -274,314 +185,126 @@ func create_preview_rect() -> TextureRect: func remove_previews() -> void: - for child in $VBoxContainer/PreviewScroll/Previews.get_children(): + for child in previews.get_children(): child.free() -func get_proccessed_image_animation_tag_and_start_id(processed_image_id : int) -> Array: - var result_animation_tag_and_start_id = null - for animation_tag in Global.current_project.animation_tags: - # Check if processed image is in frame tag and assign frame tag and start id if yes - # Then stop - if (processed_image_id + 1) >= animation_tag.from and (processed_image_id + 1) <= animation_tag.to: - result_animation_tag_and_start_id = [animation_tag.name, animation_tag.from] - break - return result_animation_tag_and_start_id - - -func export_processed_images(ignore_overwrites : bool) -> void: - # Stop export if directory path or file name are not valid - var dir = Directory.new() - if not dir.dir_exists(directory_path) or not file_name.is_valid_filename(): - $Popups/PathValidationAlert.popup_centered() - return - - # Check export paths - var export_paths = [] - for i in range(processed_images.size()): - stop_export = false - var multiple_files := true if (current_tab == ExportTab.ANIMATION && animation_type == AnimationType.MULTIPLE_FILES) else false - var export_path = create_export_path(multiple_files, i + 1) - # If user want to create new directory for each animation tag then check if directories exist and create them if not - if multiple_files and new_dir_for_each_frame_tag: - var frame_tag_directory := Directory.new() - if not frame_tag_directory.dir_exists(export_path.get_base_dir()): - frame_tag_directory.open(directory_path) - frame_tag_directory.make_dir(export_path.get_base_dir().get_file()) - # Check if the file already exists - var fileCheck = File.new() - if fileCheck.file_exists(export_path): - # Ask user if he want's to overwrite the file - if not was_exported or (was_exported and not ignore_overwrites): - # Overwrite existing file? - $Popups/FileExistsAlert.dialog_text = file_exists_alert % export_path - $Popups/FileExistsAlert.popup_centered() - # Stops the function until the user decides if he want's to overwrite - yield(self, "resume_export_function") - if stop_export: - # User decided to stop export - return - export_paths.append(export_path) - # Only get one export path if single file animated image is exported - if current_tab == ExportTab.ANIMATION && animation_type == AnimationType.ANIMATED: - break - - # Scale images that are to export - scale_processed_images() - - if current_tab == ExportTab.ANIMATION && animation_type == AnimationType.ANIMATED: - var frame_delay_in_ms = Global.animation_timer.wait_time * 100 - - $GifExporter.begin_export(export_paths[0], processed_images[0].get_width(), processed_images[0].get_height(), frame_delay_in_ms, 0) - match direction: - AnimationDirection.FORWARD: - for i in range(processed_images.size()): - $GifExporter.write_frame(processed_images[i], background_color, frame_delay_in_ms) - AnimationDirection.BACKWARDS: - for i in range(processed_images.size() - 1, -1, -1): - $GifExporter.write_frame(processed_images[i], background_color, frame_delay_in_ms) - AnimationDirection.PING_PONG: - for i in range(0, processed_images.size()): - $GifExporter.write_frame(processed_images[i], background_color, frame_delay_in_ms) - for i in range(processed_images.size() - 2, 0, -1): - $GifExporter.write_frame(processed_images[i], background_color, frame_delay_in_ms) - $GifExporter.end_export() - else: - for i in range(processed_images.size()): - if OS.get_name() == "HTML5": - Html5FileExchange.save_image(processed_images[i], export_paths[i].get_file()) - else: - var err = processed_images[i].save_png(export_paths[i]) - if err != OK: - OS.alert("Can't save file") - - # Store settings for quick export and when the dialog is opened again - was_exported = true - store_export_settings() - Global.file_menu.get_popup().set_item_text(5, tr("Export") + " %s" % (file_name + file_format_string(file_format))) - Global.notification_label("File(s) exported") - hide() - - -# Blends canvas layers into passed image starting from the origin position -func blend_layers(image : Image, frame : Frame, origin : Vector2 = Vector2(0, 0)) -> void: - image.lock() - var layer_i := 0 - for cel in frame.cels: - if Global.current_project.layers[layer_i].visible: - var cel_image := Image.new() - cel_image.copy_from(cel.image) - cel_image.lock() - if cel.opacity < 1: # If we have cel transparency - for xx in cel_image.get_size().x: - for yy in cel_image.get_size().y: - var pixel_color := cel_image.get_pixel(xx, yy) - var alpha : float = pixel_color.a * cel.opacity - cel_image.set_pixel(xx, yy, Color(pixel_color.r, pixel_color.g, pixel_color.b, alpha)) - image.blend_rect(cel_image, Rect2(Global.canvas.location, Global.current_project.size), origin) - layer_i += 1 - image.unlock() - - -func scale_processed_images() -> void: - for processed_image in processed_images: - if resize != 100: - processed_image.unlock() - processed_image.resize(processed_image.get_size().x * resize / 100, processed_image.get_size().y * resize / 100, interpolation) - - -func create_export_path(multifile: bool, frame: int = 0) -> String: - var path = file_name - # Only append frame number when there are multiple files exported - if multifile: - var frame_tag_and_start_id = get_proccessed_image_animation_tag_and_start_id(frame - 1) - # Check if exported frame is in frame tag - if frame_tag_and_start_id != null: - var frame_tag = frame_tag_and_start_id[0] - var start_id = frame_tag_and_start_id[1] - # Remove unallowed characters in frame tag directory - var regex := RegEx.new() - regex.compile("[^a-zA-Z0-9_]+") - var frame_tag_dir = regex.sub(frame_tag, "", true) - if new_dir_for_each_frame_tag: - # Add frame tag if frame has one - # (frame - start_id + 1) Makes frames id to start from 1 in each frame tag directory - path += "_" + frame_tag_dir + "_" + String(frame - start_id + 1) - return directory_path.plus_file(frame_tag_dir).plus_file(path + file_format_string(file_format)) - else: - # Add frame tag if frame has one - # (frame - start_id + 1) Makes frames id to start from 1 in each frame tag - path += "_" + frame_tag_dir + "_" + String(frame - start_id + 1) - else: - path += "_" + String(frame) - - return directory_path.plus_file(path + file_format_string(file_format)) - - -func frames_divided_by_spritesheet_lines() -> int: - return int(ceil(number_of_frames / float(lines_count))) - - -func file_format_string(format_enum : int) -> String: - match format_enum: - 0: # PNG - return '.png' - 1: # GIF - return '.gif' - _: - return '' - - func set_file_format_selector() -> void: - $VBoxContainer/AnimationOptions/MultipleAnimationsDirectories.visible = false - match animation_type: - AnimationType.MULTIPLE_FILES: - file_format = FileFormat.PNG - $VBoxContainer/File/FileFormat.selected = FileFormat.PNG - $FrameTimer.stop() - $VBoxContainer/AnimationOptions/AnimatedOptions.hide() - $VBoxContainer/AnimationOptions/MultipleAnimationsDirectories.pressed = new_dir_for_each_frame_tag - $VBoxContainer/AnimationOptions/MultipleAnimationsDirectories.visible = true - AnimationType.ANIMATED: - file_format = FileFormat.GIF - $VBoxContainer/File/FileFormat.selected = FileFormat.GIF - $FrameTimer.wait_time = Global.animation_timer.wait_time - $VBoxContainer/AnimationOptions/AnimatedOptions.show() + animation_options_multiple_animations_directories.visible = false + match Export.animation_type: + Export.AnimationType.MULTIPLE_FILES: + Export.file_format = Export.FileFormat.PNG + file_file_format.selected = Export.FileFormat.PNG + frame_timer.stop() + animation_options_animation_options.hide() + animation_options_multiple_animations_directories.pressed = Export.new_dir_for_each_frame_tag + animation_options_multiple_animations_directories.visible = true + Export.AnimationType.ANIMATED: + Export.file_format = Export.FileFormat.GIF + file_file_format.selected = Export.FileFormat.GIF + frame_timer.wait_time = Global.animation_timer.wait_time + animation_options_animation_options.show() func create_frame_tag_list() -> void: - var frame_container := $VBoxContainer/SpritesheetOptions/Frames/Frames # Clear existing tag list from entry if it exists - frame_container.clear() - frame_container.add_item("All Frames", 0) # Re-add removed 'All Frames' item + spritesheet_options_frames.clear() + spritesheet_options_frames.add_item("All Frames", 0) # Re-add removed 'All Frames' item # Repopulate list with current tag list for item in Global.current_project.animation_tags: - frame_container.add_item(item.name) - - -func store_export_settings() -> void: - exported_tab = current_tab - exported_frame_number = frame_number - exported_frame_current_tag = frame_current_tag - exported_orientation = orientation - exported_lines_count = lines_count - exported_animation_type = animation_type - exported_background_color = background_color - exported_direction = direction - exported_resize = resize - exported_interpolation = interpolation - exported_directory_path = directory_path - exported_file_name = file_name - exported_file_format = file_format - - -# Fill the dialog with previous export settings -func restore_previous_export_settings() -> void: - current_tab = exported_tab - frame_number = exported_frame_number if exported_frame_number <= Global.current_project.frames.size() else Global.current_project.frames.size() - frame_current_tag = exported_frame_current_tag if exported_frame_current_tag <= Global.current_project.animation_tags.size() else 0 - orientation = exported_orientation - lines_count = exported_lines_count - animation_type = exported_animation_type - background_color = exported_background_color - direction = exported_direction - resize = exported_resize - interpolation = exported_interpolation - directory_path = exported_directory_path - file_name = exported_file_name - file_format = exported_file_format + spritesheet_options_frames.add_item(item.name) func _on_ExportDialog_about_to_show() -> void: # If export already occured - fill the dialog with previous export settings - if was_exported: - restore_previous_export_settings() + if Export.was_exported: + Export.restore_previous_export_settings() # If we're on HTML5, don't let the user change the directory path if OS.get_name() == "HTML5": - $VBoxContainer/Path.visible = false - directory_path = "user://" + path_container.visible = false + Export.directory_path = "user://" - if directory_path.empty(): - directory_path = OS.get_system_dir(OS.SYSTEM_DIR_DESKTOP) + if Export.directory_path.empty(): + Export.directory_path = OS.get_system_dir(OS.SYSTEM_DIR_DESKTOP) # If export already occured - sets gui to show previous settings - $VBoxContainer/Options/Resize.value = resize - $VBoxContainer/Options/Interpolation.selected = interpolation - $VBoxContainer/Path/PathLineEdit.text = directory_path - $Popups/PathDialog.current_dir = directory_path - $VBoxContainer/File/FileLineEdit.text = file_name - $VBoxContainer/File/FileFormat.selected = file_format + options_resize.value = Export.resize + options_interpolation.selected = Export.interpolation + path_line_edit.text = Export.directory_path + path_dialog_popup.current_dir = Export.directory_path + file_line_edit.text = Export.file_name + file_file_format.selected = Export.file_format show_tab() - for child in $Popups.get_children(): # Set the theme for the popups + for child in popups.get_children(): # Set the theme for the popups child.theme = Global.control.theme - file_exists_alert = tr("File %s already exists. Overwrite?") # Update translation - #$VBoxContainer/Tabs.set_tab_title(0, "Frame") + Export.file_exists_alert = tr("File %s already exists. Overwrite?") # Update translation func _on_Tabs_tab_clicked(tab : int) -> void: - current_tab = tab + Export.current_tab = tab show_tab() func _on_Frame_value_changed(value: float) -> void: - frame_number = value - process_frame() + Export.frame_number = value + Export.process_frame() set_preview() func _on_Orientation_item_selected(id : int) -> void: - orientation = id - if orientation == Orientation.ROWS: - $VBoxContainer/SpritesheetOptions/Orientation/LinesCountLabel.text = "Columns:" + Export.orientation = id + if Export.orientation == Export.Orientation.ROWS: + spritesheet_options_lines_count_label.text = "Columns:" else: - $VBoxContainer/SpritesheetOptions/Orientation/LinesCountLabel.text = "Rows:" - $VBoxContainer/SpritesheetOptions/Orientation/LinesCount.value = frames_divided_by_spritesheet_lines() - process_spritesheet() + spritesheet_options_lines_count_label.text = "Rows:" + spritesheet_options_lines_count.value = Export.frames_divided_by_spritesheet_lines() + Export.process_spritesheet() set_preview() func _on_LinesCount_value_changed(value : float) -> void: - lines_count = value - process_spritesheet() + Export.lines_count = value + Export.process_spritesheet() set_preview() func _on_AnimationType_item_selected(id : int) -> void: - animation_type = id + Export.animation_type = id set_file_format_selector() set_preview() func _on_BackgroundColor_color_changed(color : Color) -> void: - background_color = color + Export.background_color = color func _on_Direction_item_selected(id : int) -> void: - direction = id + Export.direction = id match id: - AnimationDirection.FORWARD: + Export.AnimationDirection.FORWARD: animated_preview_current_frame = 0 - AnimationDirection.BACKWARDS: - animated_preview_current_frame = processed_images.size() - 1 - AnimationDirection.PING_PONG: + Export.AnimationDirection.BACKWARDS: + animated_preview_current_frame = Export.processed_images.size() - 1 + Export.AnimationDirection.PING_PONG: animated_preview_current_frame = 0 - pingpong_direction = AnimationDirection.FORWARD + pingpong_direction = Export.AnimationDirection.FORWARD func _on_Resize_value_changed(value : float) -> void: - resize = value + Export.resize = value func _on_Interpolation_item_selected(id: int) -> void: - interpolation = id + Export.interpolation = id func _on_ExportDialog_confirmed() -> void: - export_processed_images(false) + if Export.export_processed_images(false, path_validation_alert_popup, file_exists_alert_popup, self): + hide() func _on_ExportDialog_custom_action(action : String) -> void: @@ -590,90 +313,90 @@ func _on_ExportDialog_custom_action(action : String) -> void: func _on_PathButton_pressed() -> void: - $Popups/PathDialog.popup_centered() + path_dialog_popup.popup_centered() func _on_PathLineEdit_text_changed(new_text : String) -> void: - directory_path = new_text + Export.directory_path = new_text func _on_FileLineEdit_text_changed(new_text : String) -> void: - file_name = new_text + Export.file_name = new_text func _on_FileDialog_dir_selected(dir : String) -> void: - $VBoxContainer/Path/PathLineEdit.text = dir - directory_path = dir + Export.path_line_edit.text = dir + Export.directory_path = dir func _on_FileFormat_item_selected(id : int) -> void: - file_format = id + Export.file_format = id func _on_FileExistsAlert_confirmed() -> void: # Overwrite existing file - $Popups/FileExistsAlert.dialog_text = file_exists_alert - stop_export = false + file_exists_alert_popup.dialog_text = Export.file_exists_alert + Export.stop_export = false emit_signal("resume_export_function") func _on_FileExistsAlert_custom_action(action : String) -> void: if action == "cancel": # Cancel export - $Popups/FileExistsAlert.dialog_text = file_exists_alert - stop_export = true + file_exists_alert_popup.dialog_text = Export.file_exists_alert + Export.stop_export = true emit_signal("resume_export_function") - $Popups/FileExistsAlert.hide() + file_exists_alert_popup.hide() -var pingpong_direction = AnimationDirection.FORWARD +var pingpong_direction = Export.AnimationDirection.FORWARD func _on_FrameTimer_timeout() -> void: $VBoxContainer/PreviewScroll/Previews/PreviewContainer/Preview.texture = animated_preview_frames[animated_preview_current_frame] - match direction: - AnimationDirection.FORWARD: + match Export.direction: + Export.AnimationDirection.FORWARD: if animated_preview_current_frame == animated_preview_frames.size() - 1: animated_preview_current_frame = 0 else: animated_preview_current_frame += 1 - AnimationDirection.BACKWARDS: + Export.AnimationDirection.BACKWARDS: if animated_preview_current_frame == 0: - animated_preview_current_frame = processed_images.size() - 1 + animated_preview_current_frame = Export.processed_images.size() - 1 else: animated_preview_current_frame -= 1 - AnimationDirection.PING_PONG: + Export.AnimationDirection.PING_PONG: match pingpong_direction: - AnimationDirection.FORWARD: + Export.AnimationDirection.FORWARD: if animated_preview_current_frame == animated_preview_frames.size() - 1: - pingpong_direction = AnimationDirection.BACKWARDS + pingpong_direction = Export.AnimationDirection.BACKWARDS animated_preview_current_frame -= 1 if animated_preview_current_frame <= 0: animated_preview_current_frame = 0 else: animated_preview_current_frame += 1 - AnimationDirection.BACKWARDS: + Export.AnimationDirection.BACKWARDS: if animated_preview_current_frame == 0: animated_preview_current_frame += 1 if animated_preview_current_frame >= animated_preview_frames.size() - 1: animated_preview_current_frame = 0 - pingpong_direction = AnimationDirection.FORWARD + pingpong_direction = Export.AnimationDirection.FORWARD else: animated_preview_current_frame -= 1 func _on_ExportDialog_popup_hide() -> void: - $FrameTimer.stop() + frame_timer.stop() func _on_MultipleAnimationsDirectories_toggled(button_pressed : bool) -> void: - new_dir_for_each_frame_tag = button_pressed + Export.new_dir_for_each_frame_tag = button_pressed func _on_Frames_item_selected(id : int) -> void: - frame_current_tag = id - process_spritesheet() + Export.frame_current_tag = id + Export.process_spritesheet() set_preview() - $VBoxContainer/SpritesheetOptions/Orientation/LinesCount.max_value = number_of_frames - $VBoxContainer/SpritesheetOptions/Orientation/LinesCount.value = lines_count + spritesheet_options_lines_count.max_value = Export.number_of_frames + spritesheet_options_lines_count.value = Export.lines_count diff --git a/src/UI/TopMenuContainer.gd b/src/UI/TopMenuContainer.gd index 1ec9e1d28..752de4e1a 100644 --- a/src/UI/TopMenuContainer.gd +++ b/src/UI/TopMenuContainer.gd @@ -194,7 +194,7 @@ func save_project_file_as() -> void: func export_file() -> void: - if Global.export_dialog.was_exported == false: + if Export.was_exported == false: Global.export_dialog.popup_centered() Global.dialog_open(true) else: