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);