1
0
Fork 0
mirror of https://github.com/Orama-Interactive/Pixelorama.git synced 2025-01-31 15:39:49 +00:00

Support draw tiles mode in the magic wand tool

Now all selection tools are supported. They can all move, but not yet resize.
This commit is contained in:
Emmanouil Papadeas 2024-12-04 04:40:54 +02:00
parent 0c3facf376
commit e0e328f967

View file

@ -34,10 +34,10 @@ func apply_selection(pos: Vector2i) -> void:
var cel_image := Image.new() var cel_image := Image.new()
cel_image.copy_from(_get_draw_image()) cel_image.copy_from(_get_draw_image())
_flood_fill(pos, cel_image, project.selection_map, previous_selection_map) _flood_fill(pos, cel_image, project, previous_selection_map)
# Handle mirroring # Handle mirroring
for mirror_pos in Tools.get_mirrored_positions(pos): for mirror_pos in Tools.get_mirrored_positions(pos):
_flood_fill(mirror_pos, cel_image, project.selection_map, previous_selection_map) _flood_fill(mirror_pos, cel_image, project, previous_selection_map)
Global.canvas.selection.big_bounding_rectangle = project.selection_map.get_used_rect() Global.canvas.selection.big_bounding_rectangle = project.selection_map.get_used_rect()
Global.canvas.selection.commit_undo("Select", undo_data) Global.canvas.selection.commit_undo("Select", undo_data)
@ -59,6 +59,39 @@ func update_config() -> void:
$ToleranceSlider.value = _tolerance * 255.0 $ToleranceSlider.value = _tolerance * 255.0
func _on_tolerance_slider_value_changed(value: float) -> void:
_tolerance = value / 255.0
update_config()
save_config()
func _flood_fill(
pos: Vector2i, image: Image, project: Project, previous_selection_map: SelectionMap
) -> void:
# implements the floodfill routine by Shawn Hargreaves
# from https://www1.udel.edu/CIS/software/dist/allegro-4.2.1/src/flood.c
var selection_map := project.selection_map
if Tools.is_placing_tiles():
for cel in _get_selected_draw_cels():
if cel is not CelTileMap:
continue
var tile_index := (cel as CelTileMap).get_cell_index_at_coords(pos)
# init flood data structures
_allegro_flood_segments = []
_allegro_image_segments = []
_compute_segments_for_tilemap(pos, cel, tile_index)
_select_segments_tilemap(project, previous_selection_map)
return
var color := image.get_pixelv(pos)
# init flood data structures
_allegro_flood_segments = []
_allegro_image_segments = []
_compute_segments_for_image(pos, project, image, color)
# now actually color the image: since we have already checked a few things for the points
# we'll process here, we're going to skip a bunch of safety checks to speed things up.
_select_segments(selection_map, previous_selection_map)
# Add a new segment to the array # Add a new segment to the array
func _add_new_segment(y := 0) -> void: func _add_new_segment(y := 0) -> void:
_allegro_flood_segments.append(Segment.new(y)) _allegro_flood_segments.append(Segment.new(y))
@ -140,22 +173,6 @@ func _check_flooded_segment(
return ret return ret
func _flood_fill(
pos: Vector2i, image: Image, selection_map: SelectionMap, previous_selection_map: SelectionMap
) -> void:
# implements the floodfill routine by Shawn Hargreaves
# from https://www1.udel.edu/CIS/software/dist/allegro-4.2.1/src/flood.c
var project := Global.current_project
var color := image.get_pixelv(pos)
# init flood data structures
_allegro_flood_segments = []
_allegro_image_segments = []
_compute_segments_for_image(pos, project, image, color)
# now actually color the image: since we have already checked a few things for the points
# we'll process here, we're going to skip a bunch of safety checks to speed things up.
_select_segments(selection_map, previous_selection_map)
func _compute_segments_for_image( func _compute_segments_for_image(
pos: Vector2i, project: Project, image: Image, src_color: Color pos: Vector2i, project: Project, image: Image, src_color: Color
) -> void: ) -> void:
@ -201,7 +218,128 @@ func _set_bit(p: Vector2i, selection_map: SelectionMap, prev_selection_map: Sele
selection_map.select_pixel(p, !_subtract) selection_map.select_pixel(p, !_subtract)
func _on_tolerance_slider_value_changed(value: float) -> void: func _compute_segments_for_tilemap(pos: Vector2i, cel: CelTileMap, src_index: int) -> void:
_tolerance = value / 255.0 # initially allocate at least 1 segment per line of the tilemap
update_config() for j in cel.vertical_cells:
save_config() _add_new_segment(j)
pos /= cel.tileset.tile_size
# start flood algorithm
_flood_line_around_point_tilemap(pos, cel, src_index)
# test all segments while also discovering more
var done := false
while not done:
done = true
var max_index := _allegro_flood_segments.size()
for c in max_index:
var p := _allegro_flood_segments[c]
if p.todo_below: # check below the segment?
p.todo_below = false
if _check_flooded_segment_tilemap(
p.y + 1, p.left_position, p.right_position, cel, src_index
):
done = false
if p.todo_above: # check above the segment?
p.todo_above = false
if _check_flooded_segment_tilemap(
p.y - 1, p.left_position, p.right_position, cel, src_index
):
done = false
## Fill an horizontal segment around the specified position, and adds it to the
## list of segments filled. Returns the first x coordinate after the part of the
## line that has been filled.
## Τhis method is called by [method _flood_fill] after the required data structures
## have been initialized.
func _flood_line_around_point_tilemap(pos: Vector2i, cel: CelTileMap, src_index: int) -> int:
if cel.get_cell_index_at_coords_in_tilemap_space(pos) != src_index:
return pos.x + 1
var west := pos
var east := pos
while west.x >= 0 && cel.get_cell_index_at_coords_in_tilemap_space(west) == src_index:
west += Vector2i.LEFT
while (
east.x < cel.horizontal_cells
&& cel.get_cell_index_at_coords_in_tilemap_space(east) == src_index
):
east += Vector2i.RIGHT
# Make a note of the stuff we processed
var c := pos.y
var segment := _allegro_flood_segments[c]
# we may have already processed some segments on this y coordinate
if segment.flooding:
while segment.next > 0:
c = segment.next # index of next segment in this line of image
segment = _allegro_flood_segments[c]
# found last current segment on this line
c = _allegro_flood_segments.size()
segment.next = c
_add_new_segment(pos.y)
segment = _allegro_flood_segments[c]
# set the values for the current segment
segment.flooding = true
segment.left_position = west.x + 1
segment.right_position = east.x - 1
segment.y = pos.y
segment.next = 0
# Should we process segments above or below this one?
# when there is a selected area, the pixels above and below the one we started creating this
# segment from may be outside it. It's easier to assume we should be checking for segments
# above and below this one than to specifically check every single pixel in it, because that
# test will be performed later anyway.
# On the other hand, this test we described is the same `project.can_pixel_get_drawn` does if
# there is no selection, so we don't need branching here.
segment.todo_above = pos.y > 0
segment.todo_below = pos.y < cel.vertical_cells - 1
# this is an actual segment we should be coloring, so we add it to the results for the
# current image
if segment.right_position >= segment.left_position:
_allegro_image_segments.append(segment)
# we know the point just east of the segment is not part of a segment that should be
# processed, else it would be part of this segment
return east.x + 1
func _check_flooded_segment_tilemap(
y: int, left: int, right: int, cel: CelTileMap, src_index: int
) -> bool:
var ret := false
var c := 0
while left <= right:
c = y
while true:
var segment := _allegro_flood_segments[c]
if left >= segment.left_position and left <= segment.right_position:
left = segment.right_position + 2
break
c = segment.next
if c == 0: # couldn't find a valid segment, so we draw a new one
left = _flood_line_around_point_tilemap(Vector2i(left, y), cel, src_index)
ret = true
break
return ret
func _select_segments_tilemap(project: Project, previous_selection_map: SelectionMap) -> void:
# short circuit for flat colors
for c in _allegro_image_segments.size():
var p := _allegro_image_segments[c]
for px in range(p.left_position, p.right_position + 1):
# We don't have to check again whether the point being processed is within the bounds
_set_bit_rect(Vector2i(px, p.y), project, previous_selection_map)
func _set_bit_rect(p: Vector2i, project: Project, prev_selection_map: SelectionMap) -> void:
var selection_map := project.selection_map
var tilemap := project.get_current_cel() as CelTileMap
var cell_position := tilemap.get_cell_position_in_tilemap_space(p)
if _intersect:
var image_coords := tilemap.get_cell_coords_in_image(cell_position)
select_tilemap_cell(
tilemap,
cell_position,
project.selection_map,
prev_selection_map.is_pixel_selected(image_coords)
)
else:
select_tilemap_cell(tilemap, cell_position, project.selection_map, !_subtract)