From 08064098b84c26f8bf8ac890b81bff79d176dcab Mon Sep 17 00:00:00 2001 From: Emmanouil Papadeas Date: Tue, 4 Jul 2023 03:29:58 +0300 Subject: [PATCH] Add an offset image effect Some functionality as the move tool, but with more precision, wrap around ability and of course animation. --- Translations/Translations.pot | 10 +++ src/Autoload/Global.gd | 1 + src/Classes/ImageEffect.gd | 4 +- src/Shaders/OffsetPixels.gdshader | 24 +++++++ src/UI/Dialogs/ImageEffects/ImageEffects.tscn | 7 +- src/UI/Dialogs/ImageEffects/OffsetImage.gd | 54 ++++++++++++++ src/UI/Dialogs/ImageEffects/OffsetImage.tscn | 71 +++++++++++++++++++ src/UI/TopMenuContainer/TopMenuContainer.gd | 4 ++ 8 files changed, 172 insertions(+), 3 deletions(-) create mode 100644 src/Shaders/OffsetPixels.gdshader create mode 100644 src/UI/Dialogs/ImageEffects/OffsetImage.gd create mode 100644 src/UI/Dialogs/ImageEffects/OffsetImage.tscn diff --git a/Translations/Translations.pot b/Translations/Translations.pot index 0be0fe957..5a6eb8660 100644 --- a/Translations/Translations.pot +++ b/Translations/Translations.pot @@ -148,6 +148,16 @@ msgstr "" msgid "Resize Canvas" msgstr "" +msgid "Offset Image" +msgstr "" + +msgid "Offset:" +msgstr "" + +#. Found in the Offset Image dialog. It's a checkbox that, if enabled, wraps around the image if pixels go out of canvas bounds. +msgid "Wrap around:" +msgstr "" + msgid "Centralize Image" msgstr "" diff --git a/src/Autoload/Global.gd b/src/Autoload/Global.gd index 04c2bbf1f..164ddc4d8 100644 --- a/src/Autoload/Global.gd +++ b/src/Autoload/Global.gd @@ -25,6 +25,7 @@ enum ViewMenu { enum WindowMenu { WINDOW_OPACITY, PANELS, LAYOUTS, MOVABLE_PANELS, ZEN_MODE, FULLSCREEN_MODE } enum ImageMenu { RESIZE_CANVAS, + OFFSET_IMAGE, SCALE_IMAGE, CROP_IMAGE, FLIP, diff --git a/src/Classes/ImageEffect.gd b/src/Classes/ImageEffect.gd index 78415c9ba..fad902363 100644 --- a/src/Classes/ImageEffect.gd +++ b/src/Classes/ImageEffect.gd @@ -1,7 +1,7 @@ class_name ImageEffect extends ConfirmationDialog -# Parent class for all image effects -# Methods that have "pass" are meant to be replaced by the inherited Scripts +## Parent class for all image effects +## Methods that have "pass" are meant to be replaced by the inherited scripts enum { SELECTED_CELS, FRAME, ALL_FRAMES, ALL_PROJECTS } diff --git a/src/Shaders/OffsetPixels.gdshader b/src/Shaders/OffsetPixels.gdshader new file mode 100644 index 000000000..15bc00214 --- /dev/null +++ b/src/Shaders/OffsetPixels.gdshader @@ -0,0 +1,24 @@ +shader_type canvas_item; + +uniform sampler2D selection; +uniform vec2 offset = vec2(0.0); // In pixels +uniform bool wrap_around = false; + +void fragment() { + vec4 original_color = texture(TEXTURE, UV); + vec4 selection_color = texture(selection, UV); + + vec2 uv = UV - (offset * TEXTURE_PIXEL_SIZE); // Offset the uv by an amount of pixels + if (wrap_around) { + uv = fract(uv); + } + + vec4 output = texture(TEXTURE, uv); + // Cut original selected content + original_color.a = 0.0 * step(0.5, selection_color.a) + original_color.a * step(selection_color.a, 0.5); + output.a = min(step(uv.x, 1.0) * step(0.0, uv.x), output.a); // Remove left and right edges + output.a = min(step(uv.y, 1.0) * step(0.0, uv.y), output.a); // Remove up and bottom edges + + selection_color = texture(selection, uv); + COLOR = mix(original_color, output, selection_color.a); +} diff --git a/src/UI/Dialogs/ImageEffects/ImageEffects.tscn b/src/UI/Dialogs/ImageEffects/ImageEffects.tscn index a9715a971..5c917c490 100644 --- a/src/UI/Dialogs/ImageEffects/ImageEffects.tscn +++ b/src/UI/Dialogs/ImageEffects/ImageEffects.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=14 format=2] +[gd_scene load_steps=15 format=2] [ext_resource path="res://src/UI/Dialogs/ImageEffects/FlipImageDialog.tscn" type="PackedScene" id=1] [ext_resource path="res://src/UI/Dialogs/ImageEffects/InvertColorsDialog.tscn" type="PackedScene" id=2] @@ -6,6 +6,7 @@ [ext_resource path="res://src/UI/Dialogs/ImageEffects/DropShadowDialog.tscn" type="PackedScene" id=4] [ext_resource path="res://src/UI/Dialogs/ImageEffects/GradientMapDialog.tscn" type="PackedScene" id=5] [ext_resource path="res://src/UI/Dialogs/ImageEffects/Posterize.tscn" type="PackedScene" id=6] +[ext_resource path="res://src/UI/Dialogs/ImageEffects/OffsetImage.tscn" type="PackedScene" id=7] [ext_resource path="res://src/UI/Dialogs/ImageEffects/ResizeCanvas.tscn" type="PackedScene" id=8] [ext_resource path="res://src/UI/Dialogs/ImageEffects/RotateImage.tscn" type="PackedScene" id=9] [ext_resource path="res://src/UI/Dialogs/ImageEffects/ShaderEffect.tscn" type="PackedScene" id=10] @@ -17,6 +18,10 @@ [node name="ImageEffects" type="Control"] mouse_filter = 2 +[node name="OffsetImage" parent="." instance=ExtResource( 7 )] +margin_right = 376.0 +margin_bottom = 370.0 + [node name="ScaleImage" parent="." instance=ExtResource( 14 )] margin_bottom = 127.0 diff --git a/src/UI/Dialogs/ImageEffects/OffsetImage.gd b/src/UI/Dialogs/ImageEffects/OffsetImage.gd new file mode 100644 index 000000000..e44ef8627 --- /dev/null +++ b/src/UI/Dialogs/ImageEffects/OffsetImage.gd @@ -0,0 +1,54 @@ +extends ImageEffect + +enum Animate { OFFSET_X, OFFSET_Y } + +var shader: Shader = preload("res://src/Shaders/OffsetPixels.gdshader") +var wrap_around := false + +onready var offset_sliders := $VBoxContainer/OffsetOptions/OffsetSliders as ValueSliderV2 + + +func _ready() -> void: + var sm := ShaderMaterial.new() + sm.shader = shader + preview.set_material(sm) + # Set as in the Animate enum + animate_panel.add_float_property( + "Offset X", $VBoxContainer/OffsetOptions/OffsetSliders.get_sliders()[0] + ) + animate_panel.add_float_property( + "Offset Y", $VBoxContainer/OffsetOptions/OffsetSliders.get_sliders()[1] + ) + + +func _about_to_show() -> void: + offset_sliders.min_value = -Global.current_project.size + offset_sliders.max_value = Global.current_project.size + ._about_to_show() + + +func commit_action(cel: Image, project: Project = Global.current_project) -> void: + var offset_x := animate_panel.get_animated_value(commit_idx, Animate.OFFSET_X) + var offset_y := animate_panel.get_animated_value(commit_idx, Animate.OFFSET_Y) + var offset := Vector2(offset_x, offset_y) + var selection_tex := ImageTexture.new() + if selection_checkbox.pressed and project.has_selection: + selection_tex.create_from_image(project.selection_map, 0) + + var params := {"offset": offset, "wrap_around": wrap_around, "selection": selection_tex} + if !confirmed: + for param in params: + preview.material.set_shader_param(param, params[param]) + else: + var gen := ShaderImageEffect.new() + gen.generate_image(cel, shader, params, project.size) + yield(gen, "done") + + +func _on_OffsetSliders_value_changed(_value: Vector2) -> void: + update_preview() + + +func _on_WrapCheckBox_toggled(button_pressed: bool) -> void: + wrap_around = button_pressed + update_preview() diff --git a/src/UI/Dialogs/ImageEffects/OffsetImage.tscn b/src/UI/Dialogs/ImageEffects/OffsetImage.tscn new file mode 100644 index 000000000..ced6a84a2 --- /dev/null +++ b/src/UI/Dialogs/ImageEffects/OffsetImage.tscn @@ -0,0 +1,71 @@ +[gd_scene load_steps=4 format=2] + +[ext_resource path="res://src/UI/Dialogs/ImageEffects/ImageEffectParent.tscn" type="PackedScene" id=1] +[ext_resource path="res://src/UI/Nodes/ValueSliderV2.tscn" type="PackedScene" id=2] +[ext_resource path="res://src/UI/Dialogs/ImageEffects/OffsetImage.gd" type="Script" id=3] + +[node name="OffsetImage" instance=ExtResource( 1 )] +window_title = "Offset Image" +script = ExtResource( 3 ) + +[node name="VBoxContainer" parent="." index="3"] +margin_bottom = 344.0 + +[node name="AspectRatioContainer" parent="VBoxContainer" index="1"] +margin_bottom = 224.0 + +[node name="Preview" parent="VBoxContainer/AspectRatioContainer" index="0"] +margin_left = 80.0 +margin_right = 280.0 +margin_bottom = 200.0 + +[node name="OffsetOptions" type="GridContainer" parent="VBoxContainer" index="2"] +margin_top = 228.0 +margin_right = 360.0 +margin_bottom = 308.0 +columns = 2 + +[node name="OffsetLabel" type="Label" parent="VBoxContainer/OffsetOptions" index="0"] +margin_top = 19.0 +margin_right = 178.0 +margin_bottom = 33.0 +size_flags_horizontal = 3 +text = "Offset:" + +[node name="OffsetSliders" parent="VBoxContainer/OffsetOptions" index="1" instance=ExtResource( 2 )] +margin_left = 182.0 +margin_right = 360.0 +size_flags_horizontal = 3 +min_value = Vector2( -64, -64 ) +max_value = Vector2( 64, 64 ) +allow_greater = true +allow_lesser = true +show_ratio = true +suffix_x = "px" +suffix_y = "px" + +[node name="WrapLabel" type="Label" parent="VBoxContainer/OffsetOptions" index="2"] +margin_top = 61.0 +margin_right = 178.0 +margin_bottom = 75.0 +size_flags_horizontal = 3 +text = "Wrap around:" + +[node name="WrapCheckBox" type="CheckBox" parent="VBoxContainer/OffsetOptions" index="3"] +margin_left = 182.0 +margin_top = 56.0 +margin_right = 360.0 +margin_bottom = 80.0 +mouse_default_cursor_shape = 2 +size_flags_horizontal = 3 +text = "On" + +[node name="OptionsContainer" parent="VBoxContainer" index="3"] +margin_top = 312.0 +margin_bottom = 336.0 + +[node name="AnimateDialog" parent="." index="4"] +margin_bottom = 344.0 + +[connection signal="value_changed" from="VBoxContainer/OffsetOptions/OffsetSliders" to="." method="_on_OffsetSliders_value_changed"] +[connection signal="toggled" from="VBoxContainer/OffsetOptions/WrapCheckBox" to="." method="_on_WrapCheckBox_toggled"] diff --git a/src/UI/TopMenuContainer/TopMenuContainer.gd b/src/UI/TopMenuContainer/TopMenuContainer.gd index 378e2b47d..5e96ee46d 100644 --- a/src/UI/TopMenuContainer/TopMenuContainer.gd +++ b/src/UI/TopMenuContainer/TopMenuContainer.gd @@ -273,6 +273,7 @@ func _setup_image_menu() -> void: # Order as in Global.ImageMenu enum var image_menu_items := [ "Resize Canvas", + "Offset Image", "Scale Image", "Crop Image", "Mirror Image", @@ -650,6 +651,9 @@ func image_menu_id_pressed(id: int) -> void: Global.ImageMenu.SCALE_IMAGE: _popup_dialog(Global.control.get_node("Dialogs/ImageEffects/ScaleImage")) + Global.ImageMenu.OFFSET_IMAGE: + _popup_dialog(Global.control.get_node("Dialogs/ImageEffects/OffsetImage")) + Global.ImageMenu.CENTRALIZE_IMAGE: DrawingAlgos.centralize()