Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Further speed up of bucket fill #672

Merged
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 54 additions & 36 deletions src/Tools/Bucket.gd
Original file line number Diff line number Diff line change
Expand Up @@ -256,10 +256,22 @@ func _flood_line_around_point(position: Vector2, project: Project, image: Image,
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
if project.has_selection:
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
else:
while west.x >= 0 && image.get_pixelv(west).is_equal_approx(src_color):
west += Vector2.LEFT
while east.x < project.size.x && 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]
Expand All @@ -280,16 +292,14 @@ func _flood_line_around_point(position: Vector2, project: Project, image: Image,
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)
# 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 = position.y > 0
segment.todo_below = position.y < project.size.y - 1
# 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)
Expand Down Expand Up @@ -326,8 +336,14 @@ func _flood_fill(position: Vector2) -> void:
for image in images:
var color: Color = image.get_pixelv(position)
if _fill_with == 0 or _pattern == null:
# end early if we are filling with the same color
if tool_slot.color.is_equal_approx(color):
return
else:
# end early if we are filling with an empty pattern
var pattern_size = _pattern.image.get_size()
if pattern_size.x == 0 or pattern_size.y == 0:
return
# init flood data structures
_allegro_flood_segments = []
_allegro_image_segments = []
Expand All @@ -354,31 +370,33 @@ func _flood_fill(position: Vector2) -> void:
p.y - 1, p.left_position, p.right_position, project, image, color
):
done = false
# now actually color the image
for c in _allegro_image_segments.size():
var p = _allegro_image_segments[c]
if p.flooding: # sanity check: should always be true
# 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.
if _fill_with == 0 or _pattern == null:
# 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):
_set_pixel(image, px, p.y, tool_slot.color)
# We don't have to check again whether the point being processed is within the bounds
image.set_pixel(px, p.y, tool_slot.color)
else:
# shortcircuit tests for patternfills
var pattern_size = _pattern.image.get_size()
# we know the pattern had a valid size when we began flooding, so we can skip testing that
# again for every point in the pattern.
for c in _allegro_image_segments.size():
var p = _allegro_image_segments[c]
for px in range(p.left_position, p.right_position + 1):
_set_pixel_pattern(image, px, p.y, pattern_size)


func _set_pixel(image: Image, x: int, y: int, color: Color) -> void:
var project: Project = Global.current_project
if !project.can_pixel_get_drawn(Vector2(x, y)):
return

if _fill_with == 0 or _pattern == null:
image.set_pixel(x, y, color)
else:
var size := _pattern.image.get_size()
if size.x == 0 or size.y == 0:
return
_pattern.image.lock()
var px := int(x + _offset_x) % int(size.x)
var py := int(y + _offset_y) % int(size.y)
var pc := _pattern.image.get_pixel(px, py)
_pattern.image.unlock()
image.set_pixel(x, y, pc)
func _set_pixel_pattern(image: Image, x: int, y: int, pattern_size: Vector2) -> void:
_pattern.image.lock()
var px := int(x + _offset_x) % int(pattern_size.x)
var py := int(y + _offset_y) % int(pattern_size.y)
var pc := _pattern.image.get_pixel(px, py)
_pattern.image.unlock()
image.set_pixel(x, y, pc)


func commit_undo(action: String, undo_data: Dictionary) -> void:
Expand Down