diff --git a/assets/graphics/blue_themes/tools/lasso.png b/assets/graphics/blue_themes/tools/lasso.png new file mode 100644 index 000000000..e7082ca3f Binary files /dev/null and b/assets/graphics/blue_themes/tools/lasso.png differ diff --git a/assets/graphics/blue_themes/tools/lasso.png.import b/assets/graphics/blue_themes/tools/lasso.png.import new file mode 100644 index 000000000..dc8375bcb --- /dev/null +++ b/assets/graphics/blue_themes/tools/lasso.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="StreamTexture" +path="res://.import/lasso.png-2d7bb4691ebff786d4d5b222e66bbf1a.stex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/graphics/blue_themes/tools/lasso.png" +dest_files=[ "res://.import/lasso.png-2d7bb4691ebff786d4d5b222e66bbf1a.stex" ] + +[params] + +compress/mode=0 +compress/lossy_quality=0.7 +compress/hdr_mode=0 +compress/bptc_ldr=0 +compress/normal_map=0 +flags/repeat=0 +flags/filter=false +flags/mipmaps=false +flags/anisotropic=false +flags/srgb=2 +process/fix_alpha_border=true +process/premult_alpha=false +process/HDR_as_SRGB=false +process/invert_color=false +stream=false +size_limit=0 +detect_3d=true +svg/scale=1.0 diff --git a/assets/graphics/cursor_icons/lasso_cursor.png b/assets/graphics/cursor_icons/lasso_cursor.png new file mode 100644 index 000000000..93f456f99 Binary files /dev/null and b/assets/graphics/cursor_icons/lasso_cursor.png differ diff --git a/assets/graphics/cursor_icons/lasso_cursor.png.import b/assets/graphics/cursor_icons/lasso_cursor.png.import new file mode 100644 index 000000000..fee164a83 --- /dev/null +++ b/assets/graphics/cursor_icons/lasso_cursor.png.import @@ -0,0 +1,13 @@ +[remap] + +importer="image" +type="Image" +path="res://.import/lasso_cursor.png-f521b7296434da45c8ee9811a0a897a8.image" + +[deps] + +source_file="res://assets/graphics/cursor_icons/lasso_cursor.png" +dest_files=[ "res://.import/lasso_cursor.png-f521b7296434da45c8ee9811a0a897a8.image" ] + +[params] + diff --git a/assets/graphics/dark_themes/tools/lasso.png b/assets/graphics/dark_themes/tools/lasso.png new file mode 100644 index 000000000..e7082ca3f Binary files /dev/null and b/assets/graphics/dark_themes/tools/lasso.png differ diff --git a/assets/graphics/dark_themes/tools/lasso.png.import b/assets/graphics/dark_themes/tools/lasso.png.import new file mode 100644 index 000000000..17f5d5d76 --- /dev/null +++ b/assets/graphics/dark_themes/tools/lasso.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="StreamTexture" +path="res://.import/lasso.png-c0e22b291252cddaf44d885fa1a874e9.stex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/graphics/dark_themes/tools/lasso.png" +dest_files=[ "res://.import/lasso.png-c0e22b291252cddaf44d885fa1a874e9.stex" ] + +[params] + +compress/mode=0 +compress/lossy_quality=0.7 +compress/hdr_mode=0 +compress/bptc_ldr=0 +compress/normal_map=0 +flags/repeat=0 +flags/filter=false +flags/mipmaps=false +flags/anisotropic=false +flags/srgb=2 +process/fix_alpha_border=true +process/premult_alpha=false +process/HDR_as_SRGB=false +process/invert_color=false +stream=false +size_limit=0 +detect_3d=true +svg/scale=1.0 diff --git a/assets/graphics/light_themes/tools/lasso.png b/assets/graphics/light_themes/tools/lasso.png new file mode 100644 index 000000000..193a931c4 Binary files /dev/null and b/assets/graphics/light_themes/tools/lasso.png differ diff --git a/assets/graphics/light_themes/tools/lasso.png.import b/assets/graphics/light_themes/tools/lasso.png.import new file mode 100644 index 000000000..dd9b83966 --- /dev/null +++ b/assets/graphics/light_themes/tools/lasso.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="StreamTexture" +path="res://.import/lasso.png-7f6bc6d622f35bb308d3e3f86a3a2b30.stex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/graphics/light_themes/tools/lasso.png" +dest_files=[ "res://.import/lasso.png-7f6bc6d622f35bb308d3e3f86a3a2b30.stex" ] + +[params] + +compress/mode=0 +compress/lossy_quality=0.7 +compress/hdr_mode=0 +compress/bptc_ldr=0 +compress/normal_map=0 +flags/repeat=0 +flags/filter=false +flags/mipmaps=false +flags/anisotropic=false +flags/srgb=2 +process/fix_alpha_border=true +process/premult_alpha=false +process/HDR_as_SRGB=false +process/invert_color=false +stream=false +size_limit=0 +detect_3d=true +svg/scale=1.0 diff --git a/project.godot b/project.godot index 2d6b06e22..478be171a 100644 --- a/project.godot +++ b/project.godot @@ -576,6 +576,16 @@ right_ellipse_select_tool={ "events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":true,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":89,"unicode":0,"echo":false,"script":null) ] } +left_lasso_tool={ +"deadzone": 0.5, +"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":70,"unicode":0,"echo":false,"script":null) + ] +} +right_lasso_tool={ +"deadzone": 0.5, +"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":true,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":70,"unicode":0,"echo":false,"script":null) + ] +} [locale] diff --git a/src/Autoload/Global.gd b/src/Autoload/Global.gd index 8aa398a7b..a2453ff68 100644 --- a/src/Autoload/Global.gd +++ b/src/Autoload/Global.gd @@ -498,6 +498,13 @@ func update_hint_tooltips() -> void: %s for right mouse button""") % [InputMap.get_action_list("left_magic_wand_tool")[0].as_text(), InputMap.get_action_list("right_magic_wand_tool")[0].as_text()] + var lasso : BaseButton = tool_buttons.find_node("Lasso") + lasso.hint_tooltip = tr("""Lasso / Free Select Tool + +%s for left mouse button +%s for right mouse button""") % [InputMap.get_action_list("left_lasso_tool")[0].as_text(), InputMap.get_action_list("right_lasso_tool")[0].as_text()] + + var move_select : BaseButton = tool_buttons.find_node("Move") move_select.hint_tooltip = tr("""Move diff --git a/src/Autoload/Tools.gd b/src/Autoload/Tools.gd index 936ddb548..f43a151a2 100644 --- a/src/Autoload/Tools.gd +++ b/src/Autoload/Tools.gd @@ -43,6 +43,7 @@ var _tools = { "EllipseSelect" : "res://src/Tools/SelectionTools/EllipseSelect.tscn", "ColorSelect" : "res://src/Tools/SelectionTools/ColorSelect.tscn", "MagicWand" : "res://src/Tools/SelectionTools/MagicWand.tscn", + "Lasso" : "res://src/Tools/SelectionTools/Lasso.tscn", "Move" : "res://src/Tools/Move.tscn", "Zoom" : "res://src/Tools/Zoom.tscn", "Pan" : "res://src/Tools/Pan.tscn", diff --git a/src/Tools/SelectionTools/Lasso.gd b/src/Tools/SelectionTools/Lasso.gd new file mode 100644 index 000000000..4bf5f8bbf --- /dev/null +++ b/src/Tools/SelectionTools/Lasso.gd @@ -0,0 +1,165 @@ +extends SelectionTool + + +var _last_position := Vector2.INF +var _draw_points := [] + + +func draw_start(position : Vector2) -> void: + .draw_start(position) + if !_move: + _draw_points.append(position) + _last_position = position + + +func draw_move(position : Vector2) -> void: + if selection_node.arrow_key_move: + return + .draw_move(position) + if !_move: + append_gap(_last_position, position) + _last_position = position + _draw_points.append(position) + _offset = position + + +func draw_end(position : Vector2) -> void: + if selection_node.arrow_key_move: + return + if !_move: + _draw_points.append(position) + .draw_end(position) + + +func draw_preview() -> void: + if !_move: + var canvas : Node2D = Global.canvas.previews + var _position := canvas.position + var _scale := canvas.scale + if Global.mirror_view: + _position.x = _position.x + Global.current_project.size.x + _scale.x = -1 + canvas.draw_set_transform(_position, canvas.rotation, _scale) + var indicator := _fill_bitmap_with_points(_draw_points, Global.current_project.size) + + for line in _create_polylines(indicator): + canvas.draw_polyline(PoolVector2Array(line), Color.black) + + # Handle mirroring + if tool_slot.horizontal_mirror: + for line in _create_polylines(_fill_bitmap_with_points(mirror_array(_draw_points, true, false), Global.current_project.size)): + canvas.draw_polyline(PoolVector2Array(line), Color.black) + if tool_slot.vertical_mirror: + for line in _create_polylines(_fill_bitmap_with_points(mirror_array(_draw_points, true, true), Global.current_project.size)): + canvas.draw_polyline(PoolVector2Array(line), Color.black) + if tool_slot.vertical_mirror: + for line in _create_polylines(_fill_bitmap_with_points(mirror_array(_draw_points, false, true), Global.current_project.size)): + canvas.draw_polyline(PoolVector2Array(line), Color.black) + + canvas.draw_set_transform(canvas.position, canvas.rotation, canvas.scale) + + +func apply_selection(_position) -> void: + var project : Project = Global.current_project + var cleared := false + if !_add and !_subtract and !_intersect: + cleared = true + Global.canvas.selection.clear_selection() + if _draw_points.size() > 3: + var selection_bitmap_copy : BitMap = project.selection_bitmap.duplicate() + var bitmap_size : Vector2 = selection_bitmap_copy.get_size() + if _intersect: + selection_bitmap_copy.set_bit_rect(Rect2(Vector2.ZERO, bitmap_size), false) + lasso_selection(selection_bitmap_copy, _draw_points) + + # Handle mirroring + if tool_slot.horizontal_mirror: + lasso_selection(selection_bitmap_copy, mirror_array(_draw_points, true, false)) + if tool_slot.vertical_mirror: + lasso_selection(selection_bitmap_copy, mirror_array(_draw_points, true, true)) + if tool_slot.vertical_mirror: + lasso_selection(selection_bitmap_copy, mirror_array(_draw_points, false, true)) + + project.selection_bitmap = selection_bitmap_copy + Global.canvas.selection.big_bounding_rectangle = project.get_selection_rectangle(project.selection_bitmap) + else: + if !cleared: + Global.canvas.selection.clear_selection() + + Global.canvas.selection.commit_undo("Rectangle Select", undo_data) + _draw_points.clear() + + +func lasso_selection(bitmap : BitMap, points : PoolVector2Array) -> void: + var project : Project = Global.current_project + var size := bitmap.get_size() + for point in points: + if point.x < 0 or point.y < 0 or point.x >= size.x or point.y >= size.y: + continue + if _intersect: + if project.selection_bitmap.get_bit(point): + bitmap.set_bit(point, true) + else: + bitmap.set_bit(point, !_subtract) + + var image = _get_draw_image() + var v := Vector2() + var image_size = image.get_size() + for x in image_size.x: + v.x = x + for y in image_size.y: + v.y = y + if Geometry.is_point_in_polygon(v, points): + if _intersect: + if project.selection_bitmap.get_bit(v): + bitmap.set_bit(v, true) + else: + bitmap.set_bit(v, !_subtract) + + +# Bresenham's Algorithm +# Thanks to https://godotengine.org/qa/35276/tile-based-line-drawing-algorithm-efficiency +func append_gap(start : Vector2, end : Vector2) -> void: + var dx := int(abs(end.x - start.x)) + var dy := int(-abs(end.y - start.y)) + var err := dx + dy + var e2 := err << 1 + var sx = 1 if start.x < end.x else -1 + var sy = 1 if start.y < end.y else -1 + var x = start.x + var y = start.y + while !(x == end.x && y == end.y): + e2 = err << 1 + if e2 >= dy: + err += dy + x += sx + if e2 <= dx: + err += dx + y += sy + _draw_points.append(Vector2(x, y)) + + +func _fill_bitmap_with_points(points: Array, size: Vector2) -> BitMap: + var bitmap := BitMap.new() + bitmap.create(size) + + for point in points: + if point.x < 0 or point.y < 0 or point.x >= size.x or point.y >= size.y: + continue + bitmap.set_bit(point, 1) + + return bitmap + + +func mirror_array(array : Array, h : bool, v : bool) -> Array: + var new_array := [] + var project := Global.current_project + for point in array: + if h and v: + new_array.append(Vector2(project.x_symmetry_point - point.x, project.y_symmetry_point - point.y)) + elif h: + new_array.append(Vector2(project.x_symmetry_point - point.x, point.y)) + elif v: + new_array.append(Vector2(point.x, project.y_symmetry_point - point.y)) + + return new_array diff --git a/src/Tools/SelectionTools/Lasso.tscn b/src/Tools/SelectionTools/Lasso.tscn new file mode 100644 index 000000000..ec598ad36 --- /dev/null +++ b/src/Tools/SelectionTools/Lasso.tscn @@ -0,0 +1,7 @@ +[gd_scene load_steps=3 format=2] + +[ext_resource path="res://src/Tools/SelectionTools/SelectionTool.tscn" type="PackedScene" id=1] +[ext_resource path="res://src/Tools/SelectionTools/Lasso.gd" type="Script" id=2] + +[node name="ToolOptions" instance=ExtResource( 1 )] +script = ExtResource( 2 ) diff --git a/src/UI/ToolButtons.gd b/src/UI/ToolButtons.gd index b7af9e999..1ab337807 100644 --- a/src/UI/ToolButtons.gd +++ b/src/UI/ToolButtons.gd @@ -7,6 +7,7 @@ onready var tools := [ [$EllipseSelect, "ellipse_select"], [$ColorSelect, "color_select"], [$MagicWand, "magic_wand"], + [$Lasso, "lasso"], [$Move, "move"], [$Zoom, "zoom"], [$Pan, "pan"], diff --git a/src/UI/UI.tscn b/src/UI/UI.tscn index 31a906bdf..142d280d5 100644 --- a/src/UI/UI.tscn +++ b/src/UI/UI.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=35 format=2] +[gd_scene load_steps=36 format=2] [ext_resource path="res://src/UI/ToolButtons.gd" type="Script" id=1] [ext_resource path="res://src/UI/Canvas/CanvasPreview.tscn" type="PackedScene" id=2] @@ -31,6 +31,7 @@ [ext_resource path="res://assets/graphics/dark_themes/tools/magicwand.png" type="Texture" id=29] [ext_resource path="res://assets/graphics/dark_themes/tools/linetool.png" type="Texture" id=30] [ext_resource path="res://assets/graphics/dark_themes/tools/ellipseselect.png" type="Texture" id=31] +[ext_resource path="res://assets/graphics/dark_themes/tools/lasso.png" type="Texture" id=32] [sub_resource type="ShaderMaterial" id=1] shader = ExtResource( 9 ) @@ -95,7 +96,7 @@ __meta__ = { margin_left = 7.0 margin_top = 7.0 margin_right = 39.0 -margin_bottom = 543.0 +margin_bottom = 579.0 size_flags_horizontal = 4 size_flags_vertical = 0 script = ExtResource( 1 ) @@ -199,7 +200,7 @@ __meta__ = { "_edit_use_anchors_": false } -[node name="Move" type="Button" parent="ToolsAndCanvas/ToolPanel/PanelContainer/ToolButtons" groups=[ +[node name="Lasso" type="Button" parent="ToolsAndCanvas/ToolPanel/PanelContainer/ToolButtons" groups=[ "UIButtons", ]] margin_top = 144.0 @@ -209,6 +210,31 @@ rect_min_size = Vector2( 32, 32 ) mouse_default_cursor_shape = 2 button_mask = 3 +[node name="Background" type="TextureRect" parent="ToolsAndCanvas/ToolPanel/PanelContainer/ToolButtons/Lasso"] +margin_right = 32.0 +margin_bottom = 32.0 +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="ToolIcon" type="TextureRect" parent="ToolsAndCanvas/ToolPanel/PanelContainer/ToolButtons/Lasso"] +margin_right = 32.0 +margin_bottom = 32.0 +texture = ExtResource( 32 ) +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="Move" type="Button" parent="ToolsAndCanvas/ToolPanel/PanelContainer/ToolButtons" groups=[ +"UIButtons", +]] +margin_top = 180.0 +margin_right = 32.0 +margin_bottom = 212.0 +rect_min_size = Vector2( 32, 32 ) +mouse_default_cursor_shape = 2 +button_mask = 3 + [node name="Background" type="TextureRect" parent="ToolsAndCanvas/ToolPanel/PanelContainer/ToolButtons/Move"] margin_right = 32.0 margin_bottom = 32.0 @@ -227,9 +253,9 @@ __meta__ = { [node name="Zoom" type="Button" parent="ToolsAndCanvas/ToolPanel/PanelContainer/ToolButtons" groups=[ "UIButtons", ]] -margin_top = 180.0 +margin_top = 216.0 margin_right = 32.0 -margin_bottom = 212.0 +margin_bottom = 248.0 rect_min_size = Vector2( 32, 32 ) mouse_default_cursor_shape = 2 button_mask = 3 @@ -252,9 +278,9 @@ __meta__ = { [node name="Pan" type="Button" parent="ToolsAndCanvas/ToolPanel/PanelContainer/ToolButtons" groups=[ "UIButtons", ]] -margin_top = 216.0 +margin_top = 252.0 margin_right = 32.0 -margin_bottom = 248.0 +margin_bottom = 284.0 rect_min_size = Vector2( 32, 32 ) mouse_default_cursor_shape = 2 button_mask = 3 @@ -277,9 +303,9 @@ __meta__ = { [node name="ColorPicker" type="Button" parent="ToolsAndCanvas/ToolPanel/PanelContainer/ToolButtons" groups=[ "UIButtons", ]] -margin_top = 252.0 +margin_top = 288.0 margin_right = 32.0 -margin_bottom = 284.0 +margin_bottom = 320.0 rect_min_size = Vector2( 32, 32 ) mouse_default_cursor_shape = 2 button_mask = 3 @@ -302,9 +328,9 @@ __meta__ = { [node name="Pencil" type="Button" parent="ToolsAndCanvas/ToolPanel/PanelContainer/ToolButtons" groups=[ "UIButtons", ]] -margin_top = 288.0 +margin_top = 324.0 margin_right = 32.0 -margin_bottom = 320.0 +margin_bottom = 356.0 rect_min_size = Vector2( 32, 32 ) mouse_default_cursor_shape = 2 button_mask = 3 @@ -328,9 +354,9 @@ __meta__ = { [node name="Eraser" type="Button" parent="ToolsAndCanvas/ToolPanel/PanelContainer/ToolButtons" groups=[ "UIButtons", ]] -margin_top = 324.0 +margin_top = 360.0 margin_right = 32.0 -margin_bottom = 356.0 +margin_bottom = 392.0 rect_min_size = Vector2( 32, 32 ) mouse_default_cursor_shape = 2 button_mask = 3 @@ -354,9 +380,9 @@ __meta__ = { [node name="Bucket" type="Button" parent="ToolsAndCanvas/ToolPanel/PanelContainer/ToolButtons" groups=[ "UIButtons", ]] -margin_top = 360.0 +margin_top = 396.0 margin_right = 32.0 -margin_bottom = 392.0 +margin_bottom = 428.0 rect_min_size = Vector2( 32, 32 ) mouse_default_cursor_shape = 2 button_mask = 3 @@ -379,9 +405,9 @@ __meta__ = { [node name="LightenDarken" type="Button" parent="ToolsAndCanvas/ToolPanel/PanelContainer/ToolButtons" groups=[ "UIButtons", ]] -margin_top = 396.0 +margin_top = 432.0 margin_right = 32.0 -margin_bottom = 428.0 +margin_bottom = 464.0 rect_min_size = Vector2( 32, 32 ) mouse_default_cursor_shape = 2 button_mask = 3 @@ -404,9 +430,9 @@ __meta__ = { [node name="LineTool" type="Button" parent="ToolsAndCanvas/ToolPanel/PanelContainer/ToolButtons" groups=[ "UIButtons", ]] -margin_top = 432.0 +margin_top = 468.0 margin_right = 32.0 -margin_bottom = 464.0 +margin_bottom = 500.0 rect_min_size = Vector2( 32, 32 ) mouse_default_cursor_shape = 2 button_mask = 3 @@ -429,9 +455,9 @@ __meta__ = { [node name="RectangleTool" type="Button" parent="ToolsAndCanvas/ToolPanel/PanelContainer/ToolButtons" groups=[ "UIButtons", ]] -margin_top = 468.0 +margin_top = 504.0 margin_right = 32.0 -margin_bottom = 500.0 +margin_bottom = 536.0 rect_min_size = Vector2( 32, 32 ) mouse_default_cursor_shape = 2 button_mask = 3 @@ -454,9 +480,9 @@ __meta__ = { [node name="EllipseTool" type="Button" parent="ToolsAndCanvas/ToolPanel/PanelContainer/ToolButtons" groups=[ "UIButtons", ]] -margin_top = 504.0 +margin_top = 540.0 margin_right = 32.0 -margin_bottom = 536.0 +margin_bottom = 572.0 rect_min_size = Vector2( 32, 32 ) mouse_default_cursor_shape = 2 button_mask = 3