LibGfx: Implement flood fill algorithm in Bitmap class

This change implements a flood fill algorithm for the Bitmap class. This
will be leveraged by various Tools in PixelPaint. Moving the code into
Bitmap reduces the duplication of the algorithm throughout the
PixelPaint Tools (currently Bucket Tool and Wand Select).

The flood fill function requires you to pass in a threshold value (0 -
100) as well as a lambda for what to do when a pixel gets reached. The
lambda is provided an IntPoint representing the coordinates of the pixel
that was just reached.

The genericized lambda approach allows for a variety of things to be
done as the flood algorithm progresses. For example, the Bucket Tool
will paint each pixel that gets reached with the fill_color. The Wand
Select tool wont actually alter the bitmap itself, instead it uses the
reached pixels to alter a selection mask.
This commit is contained in:
Timothy Slater 2022-09-30 12:26:13 -05:00 committed by Linus Groh
parent 6933644b2e
commit eec881ea34
Notes: sideshowbarker 2024-07-17 16:42:19 +09:00
3 changed files with 62 additions and 0 deletions

View file

@ -1,14 +1,17 @@
/*
* Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org>
* Copyright (c) 2022, Timothy Slater <tslater2006@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Bitmap.h>
#include <AK/Checked.h>
#include <AK/LexicalPath.h>
#include <AK/Memory.h>
#include <AK/MemoryStream.h>
#include <AK/Optional.h>
#include <AK/Queue.h>
#include <AK/ScopeGuard.h>
#include <AK/String.h>
#include <AK/Try.h>
@ -633,4 +636,50 @@ Optional<Color> Bitmap::solid_color(u8 alpha_threshold) const
return color;
}
void Bitmap::flood_visit_from_point(Gfx::IntPoint const& start_point, int threshold,
Function<void(Gfx::IntPoint location)> pixel_reached)
{
VERIFY(rect().contains(start_point));
auto target_color = get_pixel(start_point.x(), start_point.y());
float threshold_normalized_squared = (threshold / 100.0f) * (threshold / 100.0f);
Queue<Gfx::IntPoint> points_to_visit = Queue<Gfx::IntPoint>();
points_to_visit.enqueue(start_point);
pixel_reached(start_point);
auto flood_mask = AK::Bitmap::must_create(width() * height(), false);
flood_mask.set(width() * start_point.y() + start_point.x(), true);
// This implements a non-recursive flood fill. This is a breadth-first search of paintable neighbors
// As we find neighbors that are reachable we call the location_reached callback, add them to the queue, and mark them in the mask
while (!points_to_visit.is_empty()) {
auto current_point = points_to_visit.dequeue();
auto candidate_points = Array {
current_point.moved_left(1),
current_point.moved_right(1),
current_point.moved_up(1),
current_point.moved_down(1)
};
for (auto candidate_point : candidate_points) {
auto flood_mask_index = width() * candidate_point.y() + candidate_point.x();
if (!rect().contains(candidate_point))
continue;
auto pixel_color = get_pixel<Gfx::StorageFormat::BGRA8888>(candidate_point.x(), candidate_point.y());
auto can_paint = pixel_color.distance_squared_to(target_color) <= threshold_normalized_squared;
if (flood_mask.get(flood_mask_index) == false && can_paint) {
points_to_visit.enqueue(candidate_point);
pixel_reached(candidate_point);
}
flood_mask.set(flood_mask_index, true);
}
}
}
}

View file

@ -1,5 +1,6 @@
/*
* Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org>
* Copyright (c) 2022, Timothy Slater <tslater2006@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -7,6 +8,7 @@
#pragma once
#include <AK/Forward.h>
#include <AK/Function.h>
#include <AK/RefCounted.h>
#include <LibCore/AnonymousBuffer.h>
#include <LibGfx/Color.h>
@ -242,6 +244,8 @@ public:
[[nodiscard]] Optional<Color> solid_color(u8 alpha_threshold = 0) const;
void flood_visit_from_point(Gfx::IntPoint const& start_point, int threshold, Function<void(Gfx::IntPoint location)> pixel_reached);
private:
Bitmap(BitmapFormat, IntSize const&, int, BackingStore const&);
Bitmap(BitmapFormat, IntSize const&, int, size_t pitch, void*);

View file

@ -255,6 +255,15 @@ public:
alpha() * other.alpha() / 255);
}
constexpr float distance_squared_to(Color const& other) const
{
int a = other.red() - red();
int b = other.green() - green();
int c = other.blue() - blue();
int d = other.alpha() - alpha();
return (a * a + b * b + c * c + d * d) / (4.0f * 255.0f * 255.0f);
}
constexpr u8 luminosity() const
{
return (red() * 0.2126f + green() * 0.7152f + blue() * 0.0722f);