diff --git a/src/Autoload/Global.gd b/src/Autoload/Global.gd index 314b0b012..21a04d459 100644 --- a/src/Autoload/Global.gd +++ b/src/Autoload/Global.gd @@ -71,7 +71,7 @@ enum EffectsMenu { SHADER } ## Enumeration of items present in the Select Menu. -enum SelectMenu { SELECT_ALL, CLEAR_SELECTION, INVERT, TILE_MODE } +enum SelectMenu { SELECT_ALL, CLEAR_SELECTION, INVERT, TILE_MODE, MODIFY } ## Enumeration of items present in the Help Menu. enum HelpMenu { VIEW_SPLASH_SCREEN, diff --git a/src/Classes/SelectionMap.gd b/src/Classes/SelectionMap.gd index 649a73299..cfb9591ee 100644 --- a/src/Classes/SelectionMap.gd +++ b/src/Classes/SelectionMap.gd @@ -1,7 +1,8 @@ class_name SelectionMap extends Image -var invert_shader := preload("res://src/Shaders/Effects/Invert.gdshader") +const INVERT_SHADER := preload("res://src/Shaders/Effects/Invert.gdshader") +const OUTLINE_INLINE_SHADER := preload("res://src/Shaders/Effects/OutlineInline.gdshader") func is_pixel_selected(pixel: Vector2i, calculate_offset := true) -> bool: @@ -87,8 +88,7 @@ func clear() -> void: func invert() -> void: var params := {"red": true, "green": true, "blue": true, "alpha": true} var gen := ShaderImageEffect.new() - gen.generate_image(self, invert_shader, params, get_size()) - self.convert(Image.FORMAT_LA8) + gen.generate_image(self, INVERT_SHADER, params, get_size()) ## Returns a copy of itself that is cropped to [param size]. @@ -183,3 +183,24 @@ func resize_bitmap_values( if new_bitmap_size != size: crop(new_bitmap_size.x, new_bitmap_size.y) blit_rect(smaller_image, Rect2i(Vector2i.ZERO, new_bitmap_size), dst) + + +func expand(width: int, pattern: int) -> void: + var params := { + "color": Color(1, 1, 1, 1), + "width": width, + "pattern": pattern, + } + var gen := ShaderImageEffect.new() + gen.generate_image(self, OUTLINE_INLINE_SHADER, params, get_size()) + + +func shrink(width: int, pattern: int) -> void: + var params := { + "color": Color(0), + "inside": true, + "width": width, + "pattern": pattern, + } + var gen := ShaderImageEffect.new() + gen.generate_image(self, OUTLINE_INLINE_SHADER, params, get_size()) diff --git a/src/Classes/ShaderImageEffect.gd b/src/Classes/ShaderImageEffect.gd index 04604fb0f..3a38b75e7 100644 --- a/src/Classes/ShaderImageEffect.gd +++ b/src/Classes/ShaderImageEffect.gd @@ -54,7 +54,7 @@ func generate_image(img: Image, shader: Shader, params: Dictionary, size: Vector RenderingServer.free_rid(ci_rid) RenderingServer.free_rid(mat_rid) RenderingServer.free_rid(texture) - viewport_texture.convert(Image.FORMAT_RGBA8) + viewport_texture.convert(img.get_format()) img.copy_from(viewport_texture) if resized_width: img.crop(img.get_width() - 1, img.get_height()) diff --git a/src/Shaders/Effects/OutlineInline.gdshader b/src/Shaders/Effects/OutlineInline.gdshader index 0eaa038a3..88325aa77 100644 --- a/src/Shaders/Effects/OutlineInline.gdshader +++ b/src/Shaders/Effects/OutlineInline.gdshader @@ -44,7 +44,12 @@ void fragment() { if ((output.a > 0.0) == inside && has_contrary_neighbour(UV, TEXTURE_PIXEL_SIZE, TEXTURE)) { output.rgb = inside ? mix(output.rgb, color.rgb, color.a) : color.rgb; - output.a += (1.0 - output.a) * color.a; + if (is_zero_approx(color.a)) { + output.a = color.a; + } + else { + output.a += (1.0 - output.a) * color.a; + } } COLOR = mix(original_color, output, selection_color.a); diff --git a/src/UI/Dialogs/ModifySelection.gd b/src/UI/Dialogs/ModifySelection.gd new file mode 100644 index 000000000..d0195d083 --- /dev/null +++ b/src/UI/Dialogs/ModifySelection.gd @@ -0,0 +1,39 @@ +extends ConfirmationDialog + +enum Types { EXPAND, SHRINK } + +@export var type := Types.EXPAND: + set(value): + type = value + if type == Types.EXPAND: + title = "Expand Selection" + else: + title = "Shrink Selection" + +@onready var width_slider: ValueSlider = $GridContainer/WidthSlider +@onready var brush_option_button: OptionButton = $GridContainer/BrushOptionButton +@onready var selection_node := Global.canvas.selection + + +func _on_visibility_changed() -> void: + if not visible: + Global.dialog_open(false) + + +func _on_confirmed() -> void: + var project := Global.current_project + if !project.has_selection: + return + selection_node.transform_content_confirm() + var undo_data_tmp := selection_node.get_undo_data(false) + var width: int = width_slider.value + var brush := brush_option_button.selected + project.selection_map.crop(project.size.x, project.size.y) + if type == Types.EXPAND: + project.selection_map.expand(width, brush) + else: + project.selection_map.shrink(width, brush) + selection_node.big_bounding_rectangle = project.selection_map.get_used_rect() + project.selection_offset = Vector2.ZERO + selection_node.commit_undo("Modify Selection", undo_data_tmp) + selection_node.queue_redraw() diff --git a/src/UI/Dialogs/ModifySelection.tscn b/src/UI/Dialogs/ModifySelection.tscn new file mode 100644 index 000000000..27a38debd --- /dev/null +++ b/src/UI/Dialogs/ModifySelection.tscn @@ -0,0 +1,60 @@ +[gd_scene load_steps=3 format=3 uid="uid://wcbpnsm7gptu"] + +[ext_resource type="Script" path="res://src/UI/Nodes/ValueSlider.gd" id="1_3jelw"] +[ext_resource type="Script" path="res://src/UI/Dialogs/ModifySelection.gd" id="1_w6rs7"] + +[node name="ModifySelection" type="ConfirmationDialog"] +title = "Expand selection" +position = Vector2i(0, 36) +size = Vector2i(260, 130) +script = ExtResource("1_w6rs7") + +[node name="GridContainer" type="GridContainer" parent="."] +offset_left = 8.0 +offset_top = 8.0 +offset_right = 252.0 +offset_bottom = 81.0 +columns = 2 + +[node name="WidthLabel" type="Label" parent="GridContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +text = "Width:" + +[node name="WidthSlider" type="TextureProgressBar" parent="GridContainer"] +custom_minimum_size = Vector2(0, 24) +layout_mode = 2 +size_flags_horizontal = 3 +focus_mode = 2 +mouse_default_cursor_shape = 2 +theme_type_variation = &"ValueSlider" +min_value = 1.0 +max_value = 25.0 +value = 1.0 +allow_greater = true +nine_patch_stretch = true +stretch_margin_left = 3 +stretch_margin_top = 3 +stretch_margin_right = 3 +stretch_margin_bottom = 3 +script = ExtResource("1_3jelw") + +[node name="BrushLabel" type="Label" parent="GridContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +text = "Brush:" + +[node name="BrushOptionButton" type="OptionButton" parent="GridContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +mouse_default_cursor_shape = 2 +selected = 0 +item_count = 3 +popup/item_0/text = "Diamond" +popup/item_1/text = "Circle" +popup/item_1/id = 1 +popup/item_2/text = "Square" +popup/item_2/id = 2 + +[connection signal="confirmed" from="." to="." method="_on_confirmed"] +[connection signal="visibility_changed" from="." to="." method="_on_visibility_changed"] diff --git a/src/UI/TopMenuContainer/TopMenuContainer.gd b/src/UI/TopMenuContainer/TopMenuContainer.gd index b95524f00..a83257d54 100644 --- a/src/UI/TopMenuContainer/TopMenuContainer.gd +++ b/src/UI/TopMenuContainer/TopMenuContainer.gd @@ -17,6 +17,7 @@ var zen_mode := false var new_image_dialog := Dialog.new("res://src/UI/Dialogs/CreateNewImage.tscn") var project_properties_dialog := Dialog.new("res://src/UI/Dialogs/ProjectProperties.tscn") var preferences_dialog := Dialog.new("res://src/Preferences/PreferencesDialog.tscn") +var modify_selection := Dialog.new("res://src/UI/Dialogs/ModifySelection.tscn") var offset_image_dialog := Dialog.new("res://src/UI/Dialogs/ImageEffects/OffsetImage.tscn") var scale_image_dialog := Dialog.new("res://src/UI/Dialogs/ImageEffects/ScaleImage.tscn") var resize_canvas_dialog := Dialog.new("res://src/UI/Dialogs/ImageEffects/ResizeCanvas.tscn") @@ -54,6 +55,7 @@ var about_dialog := Dialog.new("res://src/UI/Dialogs/AboutDialog.tscn") @onready var greyscale_vision: ColorRect = main_ui.find_child("GreyscaleVision") @onready var tile_mode_submenu := PopupMenu.new() +@onready var selection_modify_submenu := PopupMenu.new() @onready var snap_to_submenu := PopupMenu.new() @onready var panels_submenu := PopupMenu.new() @onready var layouts_submenu := PopupMenu.new() @@ -440,17 +442,29 @@ func _setup_select_menu() -> void: "All": "select_all", "Clear": "clear_selection", "Invert": "invert_selection", - "Tile Mode": "" + "Tile Mode": "", + "Modify": "" } for i in select_menu_items.size(): var item: String = select_menu_items.keys()[i] if item == "Tile Mode": select_menu.add_check_item(item, i) + elif item == "Modify": + _setup_selection_modify_submenu(item) else: _set_menu_shortcut(select_menu_items[item], select_menu, i, item) select_menu.id_pressed.connect(select_menu_id_pressed) +func _setup_selection_modify_submenu(item: String) -> void: + selection_modify_submenu.set_name("selection_modify_submenu") + selection_modify_submenu.add_item("Expand") + selection_modify_submenu.add_item("Shrink") + selection_modify_submenu.id_pressed.connect(_selection_modify_submenu_id_pressed) + select_menu.add_child(selection_modify_submenu) + select_menu.add_submenu_item(item, selection_modify_submenu.get_name()) + + func _setup_help_menu() -> void: # Order as in Global.HelpMenu enum var help_menu_items := { @@ -667,6 +681,11 @@ func _tile_mode_submenu_id_pressed(id: Tiles.MODE) -> void: get_tree().current_scene.tile_mode_offsets_dialog.change_mask() +func _selection_modify_submenu_id_pressed(id: int) -> void: + modify_selection.popup() + modify_selection.node.type = id + + func _snap_to_submenu_id_pressed(id: int) -> void: if id == 0: Global.snap_to_rectangular_grid_boundary = !Global.snap_to_rectangular_grid_boundary