1
0
Fork 0
mirror of https://github.com/Orama-Interactive/Pixelorama.git synced 2025-02-21 13:03:13 +00:00
Pixelorama/src/Tools/SelectionTools/Lasso.gd
Emmanouil Papadeas 1e9c8487ba Optimize the lasso & polygon select tools by making them check fewer pixels
The time they take to complete now depends on the size of the selection, rather than checking all of the pixels of the entire canvas.
2024-09-05 03:34:30 +03:00

158 lines
5.1 KiB
GDScript

extends BaseSelectionTool
var _last_position := Vector2.INF
var _draw_points: Array[Vector2i] = []
func draw_start(pos: Vector2i) -> void:
pos = snap_position(pos)
super.draw_start(pos)
if !_move:
_draw_points.append(pos)
_last_position = pos
func draw_move(pos_i: Vector2i) -> void:
if selection_node.arrow_key_move:
return
var pos := _get_stabilized_position(pos_i)
pos = snap_position(pos)
super.draw_move(pos)
if !_move:
append_gap(_last_position, pos)
_last_position = pos
_draw_points.append(Vector2i(pos))
_offset = pos
func draw_end(pos: Vector2i) -> void:
if selection_node.arrow_key_move:
return
pos = snap_position(pos)
super.draw_end(pos)
func draw_preview() -> void:
var canvas := Global.canvas.previews_sprite
if _last_position != Vector2.INF and !_move:
var image := Image.create(
Global.current_project.size.x, Global.current_project.size.y, false, Image.FORMAT_LA8
)
for point in _draw_points:
var draw_point := point
if Global.mirror_view: # This fixes previewing in mirror mode
draw_point.x = image.get_width() - draw_point.x - 1
if Rect2i(Vector2i.ZERO, image.get_size()).has_point(draw_point):
image.set_pixelv(draw_point, Color.WHITE)
# Handle mirroring
if Tools.horizontal_mirror:
for point in mirror_array(_draw_points, true, false):
var draw_point := point
if Global.mirror_view: # This fixes previewing in mirror mode
draw_point.x = image.get_width() - draw_point.x - 1
if Rect2i(Vector2i.ZERO, image.get_size()).has_point(draw_point):
image.set_pixelv(draw_point, Color.WHITE)
if Tools.vertical_mirror:
for point in mirror_array(_draw_points, true, true):
var draw_point := point
if Global.mirror_view: # This fixes previewing in mirror mode
draw_point.x = image.get_width() - draw_point.x - 1
if Rect2i(Vector2i.ZERO, image.get_size()).has_point(draw_point):
image.set_pixelv(draw_point, Color.WHITE)
if Tools.vertical_mirror:
for point in mirror_array(_draw_points, false, true):
var draw_point := point
if Global.mirror_view: # This fixes previewing in mirror mode
draw_point.x = image.get_width() - draw_point.x - 1
if Rect2i(Vector2i.ZERO, image.get_size()).has_point(draw_point):
image.set_pixelv(draw_point, Color.WHITE)
var texture := ImageTexture.create_from_image(image)
canvas.texture = texture
else:
canvas.texture = null
func apply_selection(_position) -> void:
super.apply_selection(_position)
var project := Global.current_project
var cleared := false
var previous_selection_map := SelectionMap.new() # Used for intersect
previous_selection_map.copy_from(project.selection_map)
if !_add and !_subtract and !_intersect:
cleared = true
Global.canvas.selection.clear_selection()
if _draw_points.size() > 3:
if _intersect:
project.selection_map.clear()
lasso_selection(project.selection_map, previous_selection_map, _draw_points)
# Handle mirroring
if Tools.horizontal_mirror:
var mirror_x := mirror_array(_draw_points, true, false)
lasso_selection(project.selection_map, previous_selection_map, mirror_x)
if Tools.vertical_mirror:
var mirror_xy := mirror_array(_draw_points, true, true)
lasso_selection(project.selection_map, previous_selection_map, mirror_xy)
if Tools.vertical_mirror:
var mirror_y := mirror_array(_draw_points, false, true)
lasso_selection(project.selection_map, previous_selection_map, mirror_y)
Global.canvas.selection.big_bounding_rectangle = project.selection_map.get_used_rect()
else:
if !cleared:
Global.canvas.selection.clear_selection()
Global.canvas.selection.commit_undo("Select", undo_data)
_draw_points.clear()
_last_position = Vector2.INF
func lasso_selection(
selection_map: SelectionMap, previous_selection_map: SelectionMap, points: Array[Vector2i]
) -> void:
var selection_size := selection_map.get_size()
var bounding_rect := Rect2i(points[0], Vector2i.ZERO)
for point in points:
if point.x < 0 or point.y < 0 or point.x >= selection_size.x or point.y >= selection_size.y:
continue
bounding_rect = bounding_rect.expand(point)
if _intersect:
if previous_selection_map.is_pixel_selected(point):
selection_map.select_pixel(point, true)
else:
selection_map.select_pixel(point, !_subtract)
var v := Vector2i()
for x in bounding_rect.size.x:
v.x = x + bounding_rect.position.x
for y in bounding_rect.size.y:
v.y = y + bounding_rect.position.y
if Geometry2D.is_point_in_polygon(v, points):
if _intersect:
if previous_selection_map.is_pixel_selected(v):
selection_map.select_pixel(v, true)
else:
selection_map.select_pixel(v, !_subtract)
# Bresenham's Algorithm
# Thanks to https://godotengine.org/qa/35276/tile-based-line-drawing-algorithm-efficiency
func append_gap(start: Vector2i, end: Vector2i) -> void:
var dx := absi(end.x - start.x)
var dy := -absi(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(Vector2i(x, y))