mirror of
https://github.com/Orama-Interactive/Pixelorama.git
synced 2025-02-22 05:23:14 +00:00
Upgrade/floodfill (#667)
* Implemented floodfill routine by Shawn Hargreaves * Removed performance timers used for testing * Reformatting file after setting "Trim Trailing Whitespace On Save". * Refactoring to respect code guidelines. * More reformatting * More refactoring * Refactor: added # gdlint: ignore=max-line-length to line the parser has me set to longer than 100 chars * Manage selected areas
This commit is contained in:
parent
2152412432
commit
6a7ee3407c
1 changed files with 124 additions and 35 deletions
|
@ -9,6 +9,10 @@ var _fill_area := 0
|
||||||
var _fill_with := 0
|
var _fill_with := 0
|
||||||
var _offset_x := 0
|
var _offset_x := 0
|
||||||
var _offset_y := 0
|
var _offset_y := 0
|
||||||
|
# working array used as buffer for segments while flooding
|
||||||
|
var _allegro_flood_segments: Array
|
||||||
|
# results array per image while flooding
|
||||||
|
var _allegro_image_segments: Array
|
||||||
|
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
|
@ -228,7 +232,95 @@ func fill_in_area(position: Vector2) -> void:
|
||||||
_flood_fill(Vector2(position.x, mirror_y))
|
_flood_fill(Vector2(position.x, mirror_y))
|
||||||
|
|
||||||
|
|
||||||
|
# Add a new segment to the array
|
||||||
|
func _add_new_segment(y: int = 0) -> void:
|
||||||
|
var segment = {}
|
||||||
|
segment.flooding = false
|
||||||
|
segment.todo_above = false
|
||||||
|
segment.todo_below = false
|
||||||
|
segment.left_position = -5 # anything less than -1 is ok
|
||||||
|
segment.right_position = -5
|
||||||
|
segment.y = y
|
||||||
|
segment.next = 0
|
||||||
|
_allegro_flood_segments.append(segment)
|
||||||
|
|
||||||
|
|
||||||
|
# fill an horizontal segment around the specifid 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.
|
||||||
|
# gdlint: ignore=max-line-length
|
||||||
|
func _flood_line_around_point(position: Vector2, project: Project, image: Image, src_color: Color) -> int:
|
||||||
|
# this method is called by `_my_flood_fill` after the required data structures
|
||||||
|
# have been initialized
|
||||||
|
if not image.get_pixelv(position).is_equal_approx(src_color):
|
||||||
|
return int(position.x) + 1
|
||||||
|
var west: Vector2 = position
|
||||||
|
var east: Vector2 = position
|
||||||
|
while project.can_pixel_get_drawn(west) && image.get_pixelv(west).is_equal_approx(src_color):
|
||||||
|
west += Vector2.LEFT
|
||||||
|
while project.can_pixel_get_drawn(east) && image.get_pixelv(east).is_equal_approx(src_color):
|
||||||
|
east += Vector2.RIGHT
|
||||||
|
# Make a note of the stuff we processed
|
||||||
|
var c = int(position.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(position.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 = position.y
|
||||||
|
segment.next = 0
|
||||||
|
# Should we process segments above or below this one?
|
||||||
|
if project.has_selection:
|
||||||
|
# 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.
|
||||||
|
segment.todo_above = position.y > 0
|
||||||
|
segment.todo_below = position.y < project.size.y - 1
|
||||||
|
else:
|
||||||
|
segment.todo_above = project.can_pixel_get_drawn(position + Vector2.UP)
|
||||||
|
segment.todo_below = project.can_pixel_get_drawn(position + Vector2.DOWN)
|
||||||
|
# this is an actual segment we should be coloring, so we add it to the results for the
|
||||||
|
# current image
|
||||||
|
_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 int(east.x) + 1
|
||||||
|
|
||||||
|
|
||||||
|
func _check_flooded_segment(
|
||||||
|
y: int, left: int, right: int, project: Project, image: Image, src_color: Color
|
||||||
|
) -> bool:
|
||||||
|
var ret = false
|
||||||
|
var c: int = 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(Vector2(left, y), project, image, src_color)
|
||||||
|
ret = true
|
||||||
|
break
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
func _flood_fill(position: Vector2) -> void:
|
func _flood_fill(position: Vector2) -> 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: Project = Global.current_project
|
var project: Project = Global.current_project
|
||||||
var images := _get_selected_draw_images()
|
var images := _get_selected_draw_images()
|
||||||
for image in images:
|
for image in images:
|
||||||
|
@ -236,41 +328,38 @@ func _flood_fill(position: Vector2) -> void:
|
||||||
if _fill_with == 0 or _pattern == null:
|
if _fill_with == 0 or _pattern == null:
|
||||||
if tool_slot.color.is_equal_approx(color):
|
if tool_slot.color.is_equal_approx(color):
|
||||||
return
|
return
|
||||||
|
# init flood data structures
|
||||||
var processed := BitMap.new()
|
_allegro_flood_segments = []
|
||||||
processed.create(image.get_size())
|
_allegro_image_segments = []
|
||||||
var q = [position]
|
# initially allocate at least 1 segment per line of image
|
||||||
for n in q:
|
for j in image.get_height():
|
||||||
if processed.get_bit(n):
|
_add_new_segment(j)
|
||||||
continue
|
# start flood algorithm
|
||||||
var west: Vector2 = n
|
_flood_line_around_point(position, project, image, color)
|
||||||
var east: Vector2 = n
|
# test all segments while also discovering more
|
||||||
while (
|
var done = false
|
||||||
project.can_pixel_get_drawn(west)
|
while not done:
|
||||||
&& image.get_pixelv(west).is_equal_approx(color)
|
done = true
|
||||||
|
for c in _allegro_flood_segments.size():
|
||||||
|
var p = _allegro_flood_segments[c]
|
||||||
|
if p.todo_below: # check below the segment?
|
||||||
|
p.todo_below = false
|
||||||
|
if _check_flooded_segment(
|
||||||
|
p.y + 1, p.left_position, p.right_position, project, image, color
|
||||||
):
|
):
|
||||||
west += Vector2.LEFT
|
done = false
|
||||||
while (
|
if p.todo_above: # check above the segment?
|
||||||
project.can_pixel_get_drawn(east)
|
p.todo_above = false
|
||||||
&& image.get_pixelv(east).is_equal_approx(color)
|
if _check_flooded_segment(
|
||||||
|
p.y - 1, p.left_position, p.right_position, project, image, color
|
||||||
):
|
):
|
||||||
east += Vector2.RIGHT
|
done = false
|
||||||
for px in range(west.x + 1, east.x):
|
# now actually color the image
|
||||||
var p := Vector2(px, n.y)
|
for c in _allegro_image_segments.size():
|
||||||
_set_pixel(image, p.x, p.y, tool_slot.color)
|
var p = _allegro_image_segments[c]
|
||||||
processed.set_bit(p, true)
|
if p.flooding: # sanity check: should always be true
|
||||||
var north := p + Vector2.UP
|
for px in range(p.left_position, p.right_position + 1):
|
||||||
var south := p + Vector2.DOWN
|
_set_pixel(image, px, p.y, tool_slot.color)
|
||||||
if (
|
|
||||||
project.can_pixel_get_drawn(north)
|
|
||||||
&& image.get_pixelv(north).is_equal_approx(color)
|
|
||||||
):
|
|
||||||
q.append(north)
|
|
||||||
if (
|
|
||||||
project.can_pixel_get_drawn(south)
|
|
||||||
&& image.get_pixelv(south).is_equal_approx(color)
|
|
||||||
):
|
|
||||||
q.append(south)
|
|
||||||
|
|
||||||
|
|
||||||
func _set_pixel(image: Image, x: int, y: int, color: Color) -> void:
|
func _set_pixel(image: Image, x: int, y: int, color: Color) -> void:
|
||||||
|
|
Loading…
Add table
Reference in a new issue