From eec881ea34f197cf3d890b7e16d35eea39af51c1 Mon Sep 17 00:00:00 2001 From: Timothy Slater Date: Fri, 30 Sep 2022 12:26:13 -0500 Subject: [PATCH] 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. --- Userland/Libraries/LibGfx/Bitmap.cpp | 49 ++++++++++++++++++++++++++++ Userland/Libraries/LibGfx/Bitmap.h | 4 +++ Userland/Libraries/LibGfx/Color.h | 9 +++++ 3 files changed, 62 insertions(+) diff --git a/Userland/Libraries/LibGfx/Bitmap.cpp b/Userland/Libraries/LibGfx/Bitmap.cpp index bb6a0e828e..7303280554 100644 --- a/Userland/Libraries/LibGfx/Bitmap.cpp +++ b/Userland/Libraries/LibGfx/Bitmap.cpp @@ -1,14 +1,17 @@ /* * Copyright (c) 2018-2021, Andreas Kling + * Copyright (c) 2022, Timothy Slater * * SPDX-License-Identifier: BSD-2-Clause */ +#include #include #include #include #include #include +#include #include #include #include @@ -633,4 +636,50 @@ Optional Bitmap::solid_color(u8 alpha_threshold) const return color; } +void Bitmap::flood_visit_from_point(Gfx::IntPoint const& start_point, int threshold, + Function 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 points_to_visit = Queue(); + + 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(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); + } + } +} + } diff --git a/Userland/Libraries/LibGfx/Bitmap.h b/Userland/Libraries/LibGfx/Bitmap.h index c34f2b620f..9f627a21a1 100644 --- a/Userland/Libraries/LibGfx/Bitmap.h +++ b/Userland/Libraries/LibGfx/Bitmap.h @@ -1,5 +1,6 @@ /* * Copyright (c) 2018-2021, Andreas Kling + * Copyright (c) 2022, Timothy Slater * * SPDX-License-Identifier: BSD-2-Clause */ @@ -7,6 +8,7 @@ #pragma once #include +#include #include #include #include @@ -242,6 +244,8 @@ public: [[nodiscard]] Optional solid_color(u8 alpha_threshold = 0) const; + void flood_visit_from_point(Gfx::IntPoint const& start_point, int threshold, Function pixel_reached); + private: Bitmap(BitmapFormat, IntSize const&, int, BackingStore const&); Bitmap(BitmapFormat, IntSize const&, int, size_t pitch, void*); diff --git a/Userland/Libraries/LibGfx/Color.h b/Userland/Libraries/LibGfx/Color.h index f0540a18c6..ac1810640a 100644 --- a/Userland/Libraries/LibGfx/Color.h +++ b/Userland/Libraries/LibGfx/Color.h @@ -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);