1
0
Fork 0
mirror of https://github.com/Orama-Interactive/Pixelorama.git synced 2025-01-19 01:29:49 +00:00
Pixelorama/Scripts/Dialogs/ExportDialog.gd

579 lines
19 KiB
GDScript

extends AcceptDialog
enum ExportTab { Frame = 0, Spritesheet = 1, Animation = 2 }
var current_tab : int = ExportTab.Frame
# All canvases and their layers processed/blended into images
var processed_images = [] # Image[]
# Frame options
var frame_number := 0
# Spritesheet options
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 { MultipleFiles = 0, Animated = 1 }
var animation_type : int = AnimationType.MultipleFiles
var background_color : Color = Color.white
enum AnimationDirection { Forward = 0, Backwards = 1, PingPong = 2 }
var direction : int = AnimationDirection.Forward
# Options
var resize := 100
var interpolation := 0 # Image.Interpolation
# Export directory path and export file name
var directory_path := ""
var file_name := ""
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_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
signal resume_export_function()
var stop_export = false
var animated_preview_current_frame := 0
var animated_preview_frames = []
func _ready() -> void:
$VBoxContainer/Tabs.add_tab("Frame")
$VBoxContainer/Tabs.add_tab("Spritesheet")
$VBoxContainer/Tabs.add_tab("Animation")
if OS.get_name() == "Windows":
add_button("Cancel", true, "cancel")
$Popups/FileExistsAlert.add_button("Cancel Export", true, "cancel")
else:
add_button("Cancel", false, "cancel")
$Popups/FileExistsAlert.add_button("Cancel Export", false, "cancel")
# Disable GIF export for unsupported platforms
if not $GifExporter.is_platform_supported():
$VBoxContainer/AnimationOptions/AnimationType.selected = AnimationType.MultipleFiles
$VBoxContainer/AnimationOptions/AnimationType.disabled = true
func show_tab() -> void:
$VBoxContainer/FrameOptions.hide()
$VBoxContainer/SpritesheetOptions.hide()
$VBoxContainer/AnimationOptions.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_frame + 1
$VBoxContainer/FrameOptions/FrameNumber/FrameNumber.max_value = Global.canvases.size() + 1
$VBoxContainer/FrameOptions/FrameNumber/FrameNumber.value = frame_number
process_frame()
$VBoxContainer/FrameOptions.show()
ExportTab.Spritesheet:
file_format = FileFormat.PNG
$VBoxContainer/File/FileFormat.selected = FileFormat.PNG
$FrameTimer.stop()
if not was_exported:
orientation = Orientation.Rows
lines_count = int(ceil(sqrt(Global.canvases.size())))
$VBoxContainer/SpritesheetOptions/Orientation/Orientation.selected = orientation
$VBoxContainer/SpritesheetOptions/Orientation/LinesCount.max_value = Global.canvases.size()
$VBoxContainer/SpritesheetOptions/Orientation/LinesCount.value = lines_count
$VBoxContainer/SpritesheetOptions/Orientation/LinesCountLabel.text = "Columns:"
process_spritesheet()
$VBoxContainer/SpritesheetOptions.show()
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()
set_preview()
$VBoxContainer/Tabs.current_tab = 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 canvas = Global.canvases[frame_number - 1]
var image := Image.new()
image.create(canvas.size.x, canvas.size.y, false, Image.FORMAT_RGBA8)
blend_layers(image, canvas)
processed_images.clear()
processed_images.append(image)
func process_spritesheet() -> void:
# 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.canvas.size.x * spritesheet_columns
var height = Global.canvas.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 canvas in Global.canvases:
if orientation == Orientation.Rows:
if vv < spritesheet_columns:
origin.x = canvas.size.x * vv
vv += 1
else:
hh += 1
origin.x = 0
vv = 1
origin.y = canvas.size.y * hh
else:
if hh < spritesheet_rows:
origin.y = canvas.size.y * hh
hh += 1
else:
vv += 1
origin.y = 0
hh = 1
origin.x = canvas.size.x * vv
blend_layers(whole_image, canvas, origin)
processed_images.clear()
processed_images.append(whole_image)
func process_animation() -> void:
processed_images.clear()
for canvas in Global.canvases:
var image := Image.new()
image.create(canvas.size.x, canvas.size.y, false, Image.FORMAT_RGBA8)
blend_layers(image, canvas)
processed_images.append(image)
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])
else:
match animation_type:
AnimationType.MultipleFiles:
$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
add_animated_preview()
func add_image_preview(image: Image, canvas_number: int = -1) -> void:
var container = create_preview_container()
var preview = create_preview_rect()
preview.texture = ImageTexture.new()
preview.texture.create_from_image(image, 0)
container.add_child(preview)
if canvas_number != -1:
var label = Label.new()
label.align = Label.ALIGN_CENTER
label.text = String(canvas_number)
container.add_child(label)
$VBoxContainer/PreviewScroll/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_frames = []
for processed_image in processed_images:
var texture = ImageTexture.new()
texture.create_from_image(processed_image, 0)
animated_preview_frames.push_back(texture)
var container = create_preview_container()
container.name = "PreviewContainer"
var preview = create_preview_rect()
preview.name = "Preview"
preview.texture = animated_preview_frames[animated_preview_current_frame]
container.add_child(preview)
$VBoxContainer/PreviewScroll/Previews.add_child(container)
$FrameTimer.start()
func create_preview_container() -> VBoxContainer:
var container = VBoxContainer.new()
container.size_flags_horizontal = SIZE_EXPAND_FILL
container.size_flags_vertical = SIZE_EXPAND_FILL
container.rect_min_size = Vector2(0, 128)
return container
func create_preview_rect() -> TextureRect:
var preview = TextureRect.new()
preview.expand = true
preview.size_flags_horizontal = SIZE_EXPAND_FILL
preview.size_flags_vertical = SIZE_EXPAND_FILL
preview.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
return preview
func remove_previews() -> void:
for child in $VBoxContainer/PreviewScroll/Previews.get_children():
child.free()
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 export_path = create_export_path(true if (current_tab == ExportTab.Animation && animation_type == AnimationType.MultipleFiles) else false, i + 1)
# 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.PingPong:
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()):
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, canvas: Canvas, origin: Vector2 = Vector2(0, 0)) -> void:
image.lock()
var layer_i := 0
for layer in canvas.layers:
if Global.layers[layer_i][1]:
var layer_image : Image = layer[0]
layer_image.lock()
if layer[2] < 1: # If we have layer transparency
for xx in layer_image.get_size().x:
for yy in layer_image.get_size().y:
var pixel_color := layer_image.get_pixel(xx, yy)
var alpha : float = pixel_color.a * layer[2]
layer_image.set_pixel(xx, yy, Color(pixel_color.r, pixel_color.g, pixel_color.b, alpha))
canvas.blend_rect(image, layer_image, Rect2(canvas.position, canvas.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:
path += "_" + String(frame)
return directory_path.plus_file(path + file_format_string(file_format))
func frames_divided_by_spritesheet_lines() -> int:
return int(ceil(Global.canvases.size() / 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:
match animation_type:
AnimationType.MultipleFiles:
file_format = FileFormat.PNG
$VBoxContainer/File/FileFormat.selected = FileFormat.PNG
$FrameTimer.stop()
$VBoxContainer/AnimationOptions/AnimatedOptions.hide()
AnimationType.Animated:
file_format = FileFormat.GIF
$VBoxContainer/File/FileFormat.selected = FileFormat.GIF
$FrameTimer.wait_time = Global.animation_timer.wait_time
$VBoxContainer/AnimationOptions/AnimatedOptions.show()
func store_export_settings() -> void:
exported_tab = current_tab
exported_frame_number = frame_number
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.canvases.size() else Global.canvases.size()
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
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 directory_path.empty():
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
$VBoxContainer/File/FileLineEdit.text = file_name
$VBoxContainer/File/FileFormat.selected = file_format
show_tab()
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")
func _on_Tabs_tab_clicked(tab : int) -> void:
current_tab = tab
show_tab()
func _on_Frame_value_changed(value: float) -> void:
frame_number = value
process_frame()
set_preview()
func _on_Orientation_item_selected(id : int) -> void:
orientation = id
if orientation == Orientation.Rows:
$VBoxContainer/SpritesheetOptions/Orientation/LinesCountLabel.text = "Columns:"
else:
$VBoxContainer/SpritesheetOptions/Orientation/LinesCountLabel.text = "Rows:"
$VBoxContainer/SpritesheetOptions/Orientation/LinesCount.value = frames_divided_by_spritesheet_lines()
process_spritesheet()
set_preview()
func _on_LinesCount_value_changed(value : float) -> void:
lines_count = value
process_spritesheet()
set_preview()
func _on_AnimationType_item_selected(id : int) -> void:
animation_type = id
set_file_format_selector()
set_preview()
func _on_BackgroundColor_color_changed(color : Color) -> void:
background_color = color
func _on_Direction_item_selected(id : int) -> void:
direction = id
match id:
AnimationDirection.Forward:
animated_preview_current_frame = 0
AnimationDirection.Backwards:
animated_preview_current_frame = processed_images.size() - 1
AnimationDirection.PingPong:
animated_preview_current_frame = 0
pingpong_direction = AnimationDirection.Forward
func _on_Resize_value_changed(value : float) -> void:
resize = value
func _on_Interpolation_item_selected(id: int) -> void:
interpolation = id
func _on_ExportDialog_confirmed() -> void:
export_processed_images(false)
func _on_ExportDialog_custom_action(action : String) -> void:
if action == "cancel":
hide()
func _on_PathButton_pressed() -> void:
$Popups/PathDialog.popup_centered()
func _on_PathLineEdit_text_changed(new_text : String) -> void:
directory_path = new_text
func _on_FileLineEdit_text_changed(new_text : String) -> void:
file_name = new_text
func _on_FileDialog_dir_selected(dir : String) -> void:
$VBoxContainer/Path/PathLineEdit.text = dir
directory_path = dir
func _on_FileFormat_item_selected(id : int) -> void:
file_format = id
func _on_FileExistsAlert_confirmed() -> void:
# Overwrite existing file
$Popups/FileExistsAlert.dialog_text = file_exists_alert
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
emit_signal("resume_export_function")
$Popups/FileExistsAlert.hide()
var pingpong_direction = AnimationDirection.Forward
func _on_FrameTimer_timeout() -> void:
$VBoxContainer/PreviewScroll/Previews/PreviewContainer/Preview.texture = animated_preview_frames[animated_preview_current_frame]
match direction:
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:
if animated_preview_current_frame == 0:
animated_preview_current_frame = processed_images.size() - 1
else:
animated_preview_current_frame -= 1
AnimationDirection.PingPong:
match pingpong_direction:
AnimationDirection.Forward:
if animated_preview_current_frame == animated_preview_frames.size() - 1:
pingpong_direction = 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:
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
else:
animated_preview_current_frame -= 1
func _on_ExportDialog_popup_hide() -> void:
$FrameTimer.stop()