diff --git a/src/Shaders/Rotation.shader b/src/Shaders/Rotation.shader index 855d65b95..319544b07 100644 --- a/src/Shaders/Rotation.shader +++ b/src/Shaders/Rotation.shader @@ -9,9 +9,9 @@ uniform vec2 selection_size; vec2 rotate(vec2 uv, vec2 pivot, float ratio) { // Scale and center image - uv.x -= 0.5; + uv.x -= pivot.x; uv.x *= ratio; - uv.x += 0.5; + uv.x += pivot.x; // Rotate image uv -= pivot; @@ -31,13 +31,10 @@ void fragment() { vec2 tex_size = 1.0 / TEXTURE_PIXEL_SIZE; // Texture size in real pixel coordinates vec2 pixelated_uv = floor(UV * tex_size) / (tex_size - 1.0); // Pixelate UV to fit resolution vec2 pivot = selection_pivot / tex_size; // Normalize pivot position - vec2 sel_size = selection_size / tex_size; // Normalize selection size float ratio = tex_size.x / tex_size.y; // Resolution ratio // Make a border to prevent stretching pixels on the edge vec2 border_uv = rotate(pixelated_uv, pivot, ratio); - border_uv -= pivot - sel_size / 2.0; // Move the border to selection position - border_uv /= sel_size; // Set border size to selection size // Center the border border_uv -= 0.5; diff --git a/src/UI/Dialogs/ImageEffects/RotateImage.gd b/src/UI/Dialogs/ImageEffects/RotateImage.gd index 596ca864a..225ee14bd 100644 --- a/src/UI/Dialogs/ImageEffects/RotateImage.gd +++ b/src/UI/Dialogs/ImageEffects/RotateImage.gd @@ -2,6 +2,7 @@ extends ImageEffect var live_preview: bool = true var shader: Shader = preload("res://src/Shaders/Rotation.shader") +var pivot := Vector2.ZERO onready var type_option_button: OptionButton = $VBoxContainer/HBoxContainer2/TypeOptionButton onready var angle_hslider: HSlider = $VBoxContainer/AngleOptions/AngleHSlider @@ -24,24 +25,44 @@ func set_nodes() -> void: func _about_to_show() -> void: + $VBoxContainer/Pivot/Options.visible = false + $VBoxContainer/Pivot/TogglePivot.pressed = false + decide_pivot() confirmed = false ._about_to_show() wait_apply_timer.wait_time = wait_time_spinbox.value / 1000.0 angle_hslider.value = 0 +func decide_pivot(): + var size := Global.current_project.size + pivot = Vector2(size.x / 2, size.y / 2) + + # Pivot correction in case of even size + if type_option_button.text != "Nearest neighbour (Shader)": + if int(size.x) % 2 == 0: + pivot.x -= 0.5 + if int(size.y) % 2 == 0: + pivot.y -= 0.5 + + if Global.current_project.has_selection and selection_checkbox.pressed: + var selection_rectangle: Rect2 = Global.current_project.get_selection_rectangle() + pivot = ( + selection_rectangle.position + + ((selection_rectangle.end - selection_rectangle.position) / 2) + ) + if type_option_button.text != "Nearest neighbour (Shader)": + # Pivot correction in case of even size + if int(selection_rectangle.end.x - selection_rectangle.position.x) % 2 == 0: + pivot.x -= 0.5 + if int(selection_rectangle.end.y - selection_rectangle.position.y) % 2 == 0: + pivot.y -= 0.5 + + func commit_action(_cel: Image, _project: Project = Global.current_project) -> void: var angle: float = deg2rad(angle_hslider.value) # warning-ignore:integer_division # warning-ignore:integer_division - var pivot = Vector2(_cel.get_width() / 2, _cel.get_height() / 2) - - # Pivot correction in case of even size - if type_option_button.text != "Nearest neighbour (Shader)": - if _cel.get_width() % 2 == 0: - pivot.x -= 0.5 - if _cel.get_height() % 2 == 0: - pivot.y -= 0.5 var selection_size := _cel.get_size() var selection_tex := ImageTexture.new() @@ -50,21 +71,12 @@ func commit_action(_cel: Image, _project: Project = Global.current_project) -> v image.copy_from(_cel) if _project.has_selection and selection_checkbox.pressed: var selection_rectangle: Rect2 = _project.get_selection_rectangle() - pivot = ( - selection_rectangle.position - + ((selection_rectangle.end - selection_rectangle.position) / 2) - ) selection_size = selection_rectangle.size var selection: Image = _project.bitmap_to_image(_project.selection_bitmap) selection_tex.create_from_image(selection, 0) if type_option_button.text != "Nearest neighbour (Shader)": - # Pivot correction in case of even size - if int(selection_rectangle.end.x - selection_rectangle.position.x) % 2 == 0: - pivot.x -= 0.5 - if int(selection_rectangle.end.y - selection_rectangle.position.y) % 2 == 0: - pivot.y -= 0.5 image.lock() _cel.lock() for x in _project.size.x: @@ -144,6 +156,8 @@ func _on_LiveCheckbox_toggled(button_pressed: bool) -> void: live_preview = button_pressed wait_time_spinbox.editable = !live_preview wait_time_spinbox.get_parent().visible = !live_preview + if !button_pressed: + rect_size.y += 1 # Reset rect_size of dialog func _on_quick_change_angle_pressed(change_type: String) -> void: @@ -166,3 +180,70 @@ func _on_quick_change_angle_pressed(change_type: String) -> void: elif new_angle >= 360: new_angle = new_angle - 360 angle_hslider.value = new_angle + + +func _on_TogglePivot_toggled(button_pressed: bool) -> void: + $VBoxContainer/Pivot/Options.visible = button_pressed + if button_pressed: + $VBoxContainer/Pivot/TogglePivot.text = "Pivot Options: (v)" + $VBoxContainer/Pivot/TogglePivot.focus_mode = 0 + $VBoxContainer/Pivot/Options/X/XPivot.value = pivot.x + $VBoxContainer/Pivot/Options/Y/YPivot.value = pivot.y + else: + $VBoxContainer/Pivot/TogglePivot.text = "Pivot Options: (>)" + $VBoxContainer/Pivot/TogglePivot.focus_mode = 0 + rect_size.y += 1 # Reset rect_size of dialog + + +func _on_Pivot_value_changed(value: float, is_x: bool) -> void: + if is_x: + pivot.x = value + else: + pivot.y = value + # Refresh the indicator + $VBoxContainer/AspectRatioContainer/Indicator.update() + if angle_hslider.value != 0: + update_preview() + + +func _on_Indicator_draw() -> void: + var pivot_indicator = $VBoxContainer/AspectRatioContainer/Indicator + var img_size := preview_image.get_size() + # find the scale using the larger measurement + var ratio = pivot_indicator.rect_size / img_size + # we need to set the scale according to the larger side + var conversion_scale: float + if img_size.x > img_size.y: + conversion_scale = ratio.x + else: + conversion_scale = ratio.y + var pivot_position = pivot * conversion_scale + pivot_indicator.draw_arc(pivot_position, 2, 0, 360, 360, Color.yellow, 0.5) + pivot_indicator.draw_arc(pivot_position, 6, 0, 360, 360, Color.white, 0.5) + pivot_indicator.draw_line( + pivot_position - Vector2.UP * 10, pivot_position - Vector2.DOWN * 10, Color.white, 0.5 + ) + pivot_indicator.draw_line( + pivot_position - Vector2.RIGHT * 10, pivot_position - Vector2.LEFT * 10, Color.white, 0.5 + ) + + +func _on_Indicator_gui_input(event: InputEvent) -> void: + if event is InputEventMouseMotion: + if event.pressure == 1.0: + var pivot_indicator = $VBoxContainer/AspectRatioContainer/Indicator + var x_pivot = $VBoxContainer/Pivot/Options/X/XPivot + var y_pivot = $VBoxContainer/Pivot/Options/Y/YPivot + var img_size := preview_image.get_size() + var mouse_pos = get_local_mouse_position() - pivot_indicator.rect_position + var ratio = img_size / pivot_indicator.rect_size + # we need to set the scale according to the larger side + var conversion_scale: float + if img_size.x > img_size.y: + conversion_scale = ratio.x + else: + conversion_scale = ratio.y + var new_pos = mouse_pos * conversion_scale + x_pivot.value = new_pos.x + y_pivot.value = new_pos.y + pivot_indicator.update() diff --git a/src/UI/Dialogs/ImageEffects/RotateImage.tscn b/src/UI/Dialogs/ImageEffects/RotateImage.tscn index c76b309fa..7955a8b20 100644 --- a/src/UI/Dialogs/ImageEffects/RotateImage.tscn +++ b/src/UI/Dialogs/ImageEffects/RotateImage.tscn @@ -4,9 +4,9 @@ [ext_resource path="res://src/UI/TransparentChecker.tscn" type="PackedScene" id=2] [node name="RotateImage" type="ConfirmationDialog"] -margin_right = 320.0 -margin_bottom = 380.0 -rect_min_size = Vector2( 320, 380 ) +margin_right = 342.0 +margin_bottom = 450.0 +rect_min_size = Vector2( 342, 450 ) window_title = "Rotate Image" resizable = true script = ExtResource( 1 ) @@ -20,14 +20,14 @@ margin_right = -8.0 margin_bottom = -36.0 [node name="AspectRatioContainer" type="AspectRatioContainer" parent="VBoxContainer"] -margin_right = 304.0 -margin_bottom = 204.0 +margin_right = 326.0 +margin_bottom = 226.0 size_flags_vertical = 3 [node name="Preview" type="TextureRect" parent="VBoxContainer/AspectRatioContainer"] margin_left = 50.0 -margin_right = 254.0 -margin_bottom = 204.0 +margin_right = 276.0 +margin_bottom = 226.0 rect_min_size = Vector2( 200, 200 ) expand = true stretch_mode = 5 @@ -39,15 +39,21 @@ anchor_bottom = 1.0 margin_right = 0.0 margin_bottom = 0.0 +[node name="Indicator" type="Control" parent="VBoxContainer/AspectRatioContainer"] +margin_left = 50.0 +margin_right = 276.0 +margin_bottom = 226.0 +mouse_default_cursor_shape = 2 + [node name="LiveSettings" type="HBoxContainer" parent="VBoxContainer"] -margin_top = 208.0 -margin_right = 304.0 -margin_bottom = 232.0 +margin_top = 230.0 +margin_right = 326.0 +margin_bottom = 254.0 alignment = 1 [node name="LiveCheckbox" type="CheckBox" parent="VBoxContainer/LiveSettings"] -margin_left = 98.0 -margin_right = 206.0 +margin_left = 109.0 +margin_right = 217.0 margin_bottom = 24.0 pressed = true text = "Live Preview" @@ -55,7 +61,7 @@ text = "Live Preview" [node name="WaitSettings" type="HBoxContainer" parent="VBoxContainer"] visible = false margin_top = 232.0 -margin_right = 332.0 +margin_right = 326.0 margin_bottom = 256.0 alignment = 1 __meta__ = { @@ -63,15 +69,14 @@ __meta__ = { } [node name="Label" type="Label" parent="VBoxContainer/WaitSettings"] -margin_left = 3.0 margin_top = 5.0 -margin_right = 251.0 +margin_right = 248.0 margin_bottom = 19.0 text = "Update preview delay (in milliseconds)" [node name="WaitTime" type="SpinBox" parent="VBoxContainer/WaitSettings"] -margin_left = 255.0 -margin_right = 329.0 +margin_left = 252.0 +margin_right = 326.0 margin_bottom = 24.0 min_value = 1.0 max_value = 1000.0 @@ -80,9 +85,9 @@ editable = false suffix = "msec" [node name="HBoxContainer2" type="HBoxContainer" parent="VBoxContainer"] -margin_top = 236.0 -margin_right = 304.0 -margin_bottom = 256.0 +margin_top = 258.0 +margin_right = 326.0 +margin_bottom = 278.0 [node name="Label" type="Label" parent="VBoxContainer/HBoxContainer2"] margin_top = 3.0 @@ -92,52 +97,109 @@ text = "Type:" [node name="TypeOptionButton" type="OptionButton" parent="VBoxContainer/HBoxContainer2"] margin_left = 38.0 -margin_right = 304.0 +margin_right = 326.0 margin_bottom = 20.0 mouse_default_cursor_shape = 2 size_flags_horizontal = 3 size_flags_vertical = 3 -[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer"] -margin_top = 260.0 -margin_right = 304.0 -margin_bottom = 280.0 +[node name="HSeparator" type="HSeparator" parent="VBoxContainer"] +margin_top = 282.0 +margin_right = 326.0 +margin_bottom = 286.0 + +[node name="Pivot" type="VBoxContainer" parent="VBoxContainer"] +margin_top = 290.0 +margin_right = 326.0 +margin_bottom = 310.0 + +[node name="TogglePivot" type="Button" parent="VBoxContainer/Pivot"] +margin_right = 326.0 +margin_bottom = 20.0 +size_flags_vertical = 2 +toggle_mode = true +text = "Pivot Options (>)" +flat = true +align = 0 + +[node name="Options" type="VBoxContainer" parent="VBoxContainer/Pivot"] +visible = false +margin_top = 24.0 +margin_right = 326.0 +margin_bottom = 76.0 +size_flags_horizontal = 3 alignment = 1 -[node name="Deduct90" type="Button" parent="VBoxContainer/HBoxContainer"] -margin_left = 65.0 -margin_right = 98.0 -margin_bottom = 20.0 -text = "-90" +[node name="X" type="HBoxContainer" parent="VBoxContainer/Pivot/Options"] +margin_right = 326.0 +margin_bottom = 24.0 -[node name="Deduct45" type="Button" parent="VBoxContainer/HBoxContainer"] -margin_left = 102.0 -margin_right = 135.0 -margin_bottom = 20.0 -text = "-45" +[node name="Label" type="Label" parent="VBoxContainer/Pivot/Options/X"] +margin_top = 5.0 +margin_right = 106.0 +margin_bottom = 19.0 +size_flags_horizontal = 3 +text = "X-axis" +align = 2 +valign = 1 -[node name="Zero" type="Button" parent="VBoxContainer/HBoxContainer"] -margin_left = 139.0 -margin_right = 159.0 -margin_bottom = 20.0 -text = "0" +[node name="XPivot" type="SpinBox" parent="VBoxContainer/Pivot/Options/X"] +margin_left = 110.0 +margin_right = 216.0 +margin_bottom = 24.0 +size_flags_horizontal = 3 +min_value = -10000.0 +max_value = 10000.0 +step = 0.5 +allow_greater = true +allow_lesser = true -[node name="Add45" type="Button" parent="VBoxContainer/HBoxContainer"] -margin_left = 163.0 -margin_right = 199.0 -margin_bottom = 20.0 -text = "+45" +[node name="Spacer" type="Control" parent="VBoxContainer/Pivot/Options/X"] +margin_left = 220.0 +margin_right = 326.0 +margin_bottom = 24.0 +size_flags_horizontal = 3 -[node name="Add90" type="Button" parent="VBoxContainer/HBoxContainer"] -margin_left = 203.0 -margin_right = 239.0 -margin_bottom = 20.0 -text = "+90" +[node name="Y" type="HBoxContainer" parent="VBoxContainer/Pivot/Options"] +margin_top = 28.0 +margin_right = 326.0 +margin_bottom = 52.0 + +[node name="Label" type="Label" parent="VBoxContainer/Pivot/Options/Y"] +margin_top = 5.0 +margin_right = 106.0 +margin_bottom = 19.0 +size_flags_horizontal = 3 +text = "Y-axis" +align = 2 +valign = 1 + +[node name="YPivot" type="SpinBox" parent="VBoxContainer/Pivot/Options/Y"] +margin_left = 110.0 +margin_right = 216.0 +margin_bottom = 24.0 +size_flags_horizontal = 3 +min_value = -10000.0 +max_value = 10000.0 +step = 0.5 +allow_greater = true +allow_lesser = true + +[node name="Spacer" type="Control" parent="VBoxContainer/Pivot/Options/Y"] +margin_left = 220.0 +margin_right = 326.0 +margin_bottom = 24.0 +size_flags_horizontal = 3 + +[node name="HSeparator2" type="HSeparator" parent="VBoxContainer"] +margin_top = 314.0 +margin_right = 326.0 +margin_bottom = 318.0 [node name="AngleOptions" type="HBoxContainer" parent="VBoxContainer"] -margin_top = 284.0 -margin_right = 304.0 -margin_bottom = 308.0 +margin_top = 322.0 +margin_right = 326.0 +margin_bottom = 346.0 [node name="Label" type="Label" parent="VBoxContainer/AngleOptions"] margin_top = 5.0 @@ -147,7 +209,7 @@ text = "Angle:" [node name="AngleHSlider" type="HSlider" parent="VBoxContainer/AngleOptions"] margin_left = 44.0 -margin_right = 226.0 +margin_right = 248.0 margin_bottom = 24.0 mouse_default_cursor_shape = 2 size_flags_horizontal = 3 @@ -158,17 +220,58 @@ __meta__ = { } [node name="AngleSpinBox" type="SpinBox" parent="VBoxContainer/AngleOptions"] -margin_left = 230.0 -margin_right = 304.0 +margin_left = 252.0 +margin_right = 326.0 margin_bottom = 24.0 mouse_default_cursor_shape = 2 max_value = 359.0 suffix = "°" +[node name="QuickRotations" type="HBoxContainer" parent="VBoxContainer"] +margin_top = 350.0 +margin_right = 326.0 +margin_bottom = 370.0 +alignment = 1 + +[node name="Deduct90" type="Button" parent="VBoxContainer/QuickRotations"] +margin_left = 76.0 +margin_right = 109.0 +margin_bottom = 20.0 +text = "-90" + +[node name="Deduct45" type="Button" parent="VBoxContainer/QuickRotations"] +margin_left = 113.0 +margin_right = 146.0 +margin_bottom = 20.0 +text = "-45" + +[node name="Zero" type="Button" parent="VBoxContainer/QuickRotations"] +margin_left = 150.0 +margin_right = 170.0 +margin_bottom = 20.0 +text = "0" + +[node name="Add45" type="Button" parent="VBoxContainer/QuickRotations"] +margin_left = 174.0 +margin_right = 210.0 +margin_bottom = 20.0 +text = "+45" + +[node name="Add90" type="Button" parent="VBoxContainer/QuickRotations"] +margin_left = 214.0 +margin_right = 250.0 +margin_bottom = 20.0 +text = "+90" + +[node name="HSeparator3" type="HSeparator" parent="VBoxContainer"] +margin_top = 374.0 +margin_right = 326.0 +margin_bottom = 378.0 + [node name="OptionsContainer" type="HBoxContainer" parent="VBoxContainer"] -margin_top = 312.0 -margin_right = 304.0 -margin_bottom = 336.0 +margin_top = 382.0 +margin_right = 326.0 +margin_bottom = 406.0 [node name="SelectionCheckBox" type="CheckBox" parent="VBoxContainer/OptionsContainer"] margin_right = 160.0 @@ -188,14 +291,19 @@ selected = 0 [node name="WaitApply" type="Timer" parent="."] +[connection signal="draw" from="VBoxContainer/AspectRatioContainer/Indicator" to="." method="_on_Indicator_draw"] +[connection signal="gui_input" from="VBoxContainer/AspectRatioContainer/Indicator" to="." method="_on_Indicator_gui_input"] [connection signal="toggled" from="VBoxContainer/LiveSettings/LiveCheckbox" to="." method="_on_LiveCheckbox_toggled"] [connection signal="value_changed" from="VBoxContainer/WaitSettings/WaitTime" to="." method="_on_WaitTime_value_changed"] [connection signal="item_selected" from="VBoxContainer/HBoxContainer2/TypeOptionButton" to="." method="_on_TypeOptionButton_item_selected"] -[connection signal="pressed" from="VBoxContainer/HBoxContainer/Deduct90" to="." method="_on_quick_change_angle_pressed" binds= [ "-90" ]] -[connection signal="pressed" from="VBoxContainer/HBoxContainer/Deduct45" to="." method="_on_quick_change_angle_pressed" binds= [ "-45" ]] -[connection signal="pressed" from="VBoxContainer/HBoxContainer/Zero" to="." method="_on_quick_change_angle_pressed" binds= [ "0" ]] -[connection signal="pressed" from="VBoxContainer/HBoxContainer/Add45" to="." method="_on_quick_change_angle_pressed" binds= [ "+45" ]] -[connection signal="pressed" from="VBoxContainer/HBoxContainer/Add90" to="." method="_on_quick_change_angle_pressed" binds= [ "+90" ]] +[connection signal="toggled" from="VBoxContainer/Pivot/TogglePivot" to="." method="_on_TogglePivot_toggled"] +[connection signal="value_changed" from="VBoxContainer/Pivot/Options/X/XPivot" to="." method="_on_Pivot_value_changed" binds= [ true ]] +[connection signal="value_changed" from="VBoxContainer/Pivot/Options/Y/YPivot" to="." method="_on_Pivot_value_changed" binds= [ false ]] [connection signal="value_changed" from="VBoxContainer/AngleOptions/AngleHSlider" to="." method="_on_HSlider_value_changed"] [connection signal="value_changed" from="VBoxContainer/AngleOptions/AngleSpinBox" to="." method="_on_SpinBox_value_changed"] +[connection signal="pressed" from="VBoxContainer/QuickRotations/Deduct90" to="." method="_on_quick_change_angle_pressed" binds= [ "-90" ]] +[connection signal="pressed" from="VBoxContainer/QuickRotations/Deduct45" to="." method="_on_quick_change_angle_pressed" binds= [ "-45" ]] +[connection signal="pressed" from="VBoxContainer/QuickRotations/Zero" to="." method="_on_quick_change_angle_pressed" binds= [ "0" ]] +[connection signal="pressed" from="VBoxContainer/QuickRotations/Add45" to="." method="_on_quick_change_angle_pressed" binds= [ "+45" ]] +[connection signal="pressed" from="VBoxContainer/QuickRotations/Add90" to="." method="_on_quick_change_angle_pressed" binds= [ "+90" ]] [connection signal="timeout" from="WaitApply" to="." method="_on_WaitApply_timeout"]