From 67596d9546fc2a08b5022dc60e5a30e3ea2c2f18 Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Thu, 25 Aug 2022 20:42:56 +0200 Subject: PixelPaint: Move "marching ants" painting logic to ImageEditor Since this code needs to look at a bunch of ImageEditor state anyway, it makes more sense for it to live in ImageEditor. --- Userland/Applications/PixelPaint/ImageEditor.cpp | 104 ++++++++++++++++++++- Userland/Applications/PixelPaint/ImageEditor.h | 10 ++ Userland/Applications/PixelPaint/Selection.cpp | 97 ------------------- Userland/Applications/PixelPaint/Selection.h | 11 +-- .../PixelPaint/Tools/RectangleSelectTool.cpp | 2 +- 5 files changed, 116 insertions(+), 108 deletions(-) (limited to 'Userland/Applications') diff --git a/Userland/Applications/PixelPaint/ImageEditor.cpp b/Userland/Applications/PixelPaint/ImageEditor.cpp index 3723034673..637d11a9c8 100644 --- a/Userland/Applications/PixelPaint/ImageEditor.cpp +++ b/Userland/Applications/PixelPaint/ImageEditor.cpp @@ -25,6 +25,8 @@ namespace PixelPaint { +constexpr int marching_ant_length = 4; + ImageEditor::ImageEditor(NonnullRefPtr image) : m_image(move(image)) , m_title("Untitled") @@ -41,6 +43,14 @@ ImageEditor::ImageEditor(NonnullRefPtr image) m_show_rulers = Config::read_bool("PixelPaint"sv, "Rulers"sv, "Show"sv, true); m_show_guides = Config::read_bool("PixelPaint"sv, "Guides"sv, "Show"sv, true); + + m_marching_ants_timer = Core::Timer::create_repeating(80, [this] { + ++m_marching_ants_offset; + m_marching_ants_offset %= (marching_ant_length * 2); + if (!m_selection.is_empty() || m_selection.in_interactive_selection()) + update(); + }); + m_marching_ants_timer->start(); } ImageEditor::~ImageEditor() @@ -158,8 +168,7 @@ void ImageEditor::paint_event(GUI::PaintEvent& event) } } - if (!m_selection.is_empty()) - m_selection.paint(painter); + paint_selection(painter); if (m_show_rulers) { auto const ruler_bg_color = palette().color(Gfx::ColorRole::InactiveSelection); @@ -651,4 +660,95 @@ void ImageEditor::set_loaded_from_image(bool loaded_from_image) m_loaded_from_image = loaded_from_image; } +void ImageEditor::paint_selection(Gfx::Painter& painter) +{ + if (m_selection.is_empty()) + return; + + draw_marching_ants(painter, m_selection.mask()); +} + +void ImageEditor::draw_marching_ants(Gfx::Painter& painter, Gfx::IntRect const& rect) const +{ + // Top line + for (int x = rect.left(); x <= rect.right(); ++x) + draw_marching_ants_pixel(painter, x, rect.top()); + + // Right line + for (int y = rect.top() + 1; y <= rect.bottom(); ++y) + draw_marching_ants_pixel(painter, rect.right(), y); + + // Bottom line + for (int x = rect.right() - 1; x >= rect.left(); --x) + draw_marching_ants_pixel(painter, x, rect.bottom()); + + // Left line + for (int y = rect.bottom() - 1; y > rect.top(); --y) + draw_marching_ants_pixel(painter, rect.left(), y); +} + +void ImageEditor::draw_marching_ants(Gfx::Painter& painter, Mask const& mask) const +{ + // If the zoom is < 100%, we can skip pixels to save a lot of time drawing the ants + int step = max(1, (int)floorf(1.0f / scale())); + + // Only check the visible selection area when drawing for performance + auto rect = this->rect(); + rect = Gfx::enclosing_int_rect(frame_to_content_rect(rect)); + rect.inflate(step * 2, step * 2); // prevent borders from having visible ants if the selection extends beyond it + + // Scan the image horizontally to find vertical borders + for (int y = rect.top(); y <= rect.bottom(); y += step) { + + bool previous_selected = false; + for (int x = rect.left(); x <= rect.right(); x += step) { + bool this_selected = mask.get(x, y) > 0; + + if (this_selected != previous_selected) { + Gfx::IntRect image_pixel { x, y, 1, 1 }; + auto pixel = content_to_frame_rect(image_pixel).to_type(); + auto end = max(pixel.top(), pixel.bottom()); // for when the zoom is < 100% + + for (int pixel_y = pixel.top(); pixel_y <= end; pixel_y++) { + draw_marching_ants_pixel(painter, pixel.left(), pixel_y); + } + } + + previous_selected = this_selected; + } + } + + // Scan the image vertically to find horizontal borders + for (int x = rect.left(); x <= rect.right(); x += step) { + + bool previous_selected = false; + for (int y = rect.top(); y <= rect.bottom(); y += step) { + bool this_selected = mask.get(x, y) > 0; + + if (this_selected != previous_selected) { + Gfx::IntRect image_pixel { x, y, 1, 1 }; + auto pixel = content_to_frame_rect(image_pixel).to_type(); + auto end = max(pixel.left(), pixel.right()); // for when the zoom is < 100% + + for (int pixel_x = pixel.left(); pixel_x <= end; pixel_x++) { + draw_marching_ants_pixel(painter, pixel_x, pixel.top()); + } + } + + previous_selected = this_selected; + } + } +} + +void ImageEditor::draw_marching_ants_pixel(Gfx::Painter& painter, int x, int y) const +{ + int pattern_index = x + y + m_marching_ants_offset; + + if (pattern_index % (marching_ant_length * 2) < marching_ant_length) { + painter.set_pixel(x, y, Color::Black); + } else { + painter.set_pixel(x, y, Color::White); + } +} + } diff --git a/Userland/Applications/PixelPaint/ImageEditor.h b/Userland/Applications/PixelPaint/ImageEditor.h index 04f83c9b1e..5e9fb8e616 100644 --- a/Userland/Applications/PixelPaint/ImageEditor.h +++ b/Userland/Applications/PixelPaint/ImageEditor.h @@ -113,6 +113,9 @@ public: bool is_modified(); + void draw_marching_ants(Gfx::Painter&, Gfx::IntRect const&) const; + void draw_marching_ants(Gfx::Painter&, Mask const&) const; + private: explicit ImageEditor(NonnullRefPtr); @@ -140,6 +143,8 @@ private: Gfx::IntRect mouse_indicator_rect_x() const; Gfx::IntRect mouse_indicator_rect_y() const; + void paint_selection(Gfx::Painter&); + NonnullRefPtr m_image; RefPtr m_active_layer; GUI::UndoStack m_undo_stack; @@ -171,6 +176,11 @@ private: Selection m_selection; bool m_loaded_from_image { true }; + + RefPtr m_marching_ants_timer; + int m_marching_ants_offset { 0 }; + + void draw_marching_ants_pixel(Gfx::Painter&, int x, int y) const; }; } diff --git a/Userland/Applications/PixelPaint/Selection.cpp b/Userland/Applications/PixelPaint/Selection.cpp index a55975fe7f..57d81ea5fc 100644 --- a/Userland/Applications/PixelPaint/Selection.cpp +++ b/Userland/Applications/PixelPaint/Selection.cpp @@ -10,95 +10,9 @@ namespace PixelPaint { -constexpr int marching_ant_length = 4; - -void Selection::paint(Gfx::Painter& painter) -{ - draw_marching_ants(painter, m_mask); -} - Selection::Selection(ImageEditor& editor) : m_editor(editor) { - m_marching_ants_timer = Core::Timer::create_repeating(80, [this] { - ++m_marching_ants_offset; - m_marching_ants_offset %= (marching_ant_length * 2); - if (!is_empty() || m_in_interactive_selection) - m_editor.update(); - }); - m_marching_ants_timer->start(); -} - -void Selection::draw_marching_ants(Gfx::Painter& painter, Gfx::IntRect const& rect) const -{ - // Top line - for (int x = rect.left(); x <= rect.right(); ++x) - draw_marching_ants_pixel(painter, x, rect.top()); - - // Right line - for (int y = rect.top() + 1; y <= rect.bottom(); ++y) - draw_marching_ants_pixel(painter, rect.right(), y); - - // Bottom line - for (int x = rect.right() - 1; x >= rect.left(); --x) - draw_marching_ants_pixel(painter, x, rect.bottom()); - - // Left line - for (int y = rect.bottom() - 1; y > rect.top(); --y) - draw_marching_ants_pixel(painter, rect.left(), y); -} - -void Selection::draw_marching_ants(Gfx::Painter& painter, Mask const& mask) const -{ - // If the zoom is < 100%, we can skip pixels to save a lot of time drawing the ants - int step = max(1, (int)floorf(1.0f / m_editor.scale())); - - // Only check the visible selection area when drawing for performance - auto rect = m_editor.rect(); - rect = Gfx::enclosing_int_rect(m_editor.frame_to_content_rect(rect)); - rect.inflate(step * 2, step * 2); // prevent borders from having visible ants if the selection extends beyond it - - // Scan the image horizontally to find vertical borders - for (int y = rect.top(); y <= rect.bottom(); y += step) { - - bool previous_selected = false; - for (int x = rect.left(); x <= rect.right(); x += step) { - bool this_selected = mask.get(x, y) > 0; - - if (this_selected != previous_selected) { - Gfx::IntRect image_pixel { x, y, 1, 1 }; - auto pixel = m_editor.content_to_frame_rect(image_pixel).to_type(); - auto end = max(pixel.top(), pixel.bottom()); // for when the zoom is < 100% - - for (int pixel_y = pixel.top(); pixel_y <= end; pixel_y++) { - draw_marching_ants_pixel(painter, pixel.left(), pixel_y); - } - } - - previous_selected = this_selected; - } - } - - // Scan the image vertically to find horizontal borders - for (int x = rect.left(); x <= rect.right(); x += step) { - - bool previous_selected = false; - for (int y = rect.top(); y <= rect.bottom(); y += step) { - bool this_selected = mask.get(x, y) > 0; - - if (this_selected != previous_selected) { - Gfx::IntRect image_pixel { x, y, 1, 1 }; - auto pixel = m_editor.content_to_frame_rect(image_pixel).to_type(); - auto end = max(pixel.left(), pixel.right()); // for when the zoom is < 100% - - for (int pixel_x = pixel.left(); pixel_x <= end; pixel_x++) { - draw_marching_ants_pixel(painter, pixel_x, pixel.top()); - } - } - - previous_selected = this_selected; - } - } } void Selection::clear() @@ -127,15 +41,4 @@ void Selection::merge(Mask const& mask, MergeMode mode) } } -void Selection::draw_marching_ants_pixel(Gfx::Painter& painter, int x, int y) const -{ - int pattern_index = x + y + m_marching_ants_offset; - - if (pattern_index % (marching_ant_length * 2) < marching_ant_length) { - painter.set_pixel(x, y, Color::Black); - } else { - painter.set_pixel(x, y, Color::White); - } -} - } diff --git a/Userland/Applications/PixelPaint/Selection.h b/Userland/Applications/PixelPaint/Selection.h index 4080b9c09e..7f269cc1e0 100644 --- a/Userland/Applications/PixelPaint/Selection.h +++ b/Userland/Applications/PixelPaint/Selection.h @@ -40,22 +40,17 @@ public: [[nodiscard]] u8 get_selection_alpha(int x, int y) const { return m_mask.get(x, y); } [[nodiscard]] u8 get_selection_alpha(Gfx::IntPoint const& point) const { return get_selection_alpha(point.x(), point.y()); } - void paint(Gfx::Painter&); - - void draw_marching_ants(Gfx::Painter&, Gfx::IntRect const&) const; - void draw_marching_ants(Gfx::Painter&, Mask const&) const; + Mask const& mask() const { return m_mask; } void begin_interactive_selection() { m_in_interactive_selection = true; } void end_interactive_selection() { m_in_interactive_selection = false; } + bool in_interactive_selection() { return m_in_interactive_selection; } + private: ImageEditor& m_editor; Mask m_mask; - RefPtr m_marching_ants_timer; - int m_marching_ants_offset { 0 }; bool m_in_interactive_selection { false }; - - void draw_marching_ants_pixel(Gfx::Painter&, int x, int y) const; }; } diff --git a/Userland/Applications/PixelPaint/Tools/RectangleSelectTool.cpp b/Userland/Applications/PixelPaint/Tools/RectangleSelectTool.cpp index 0d2d7daea1..890aa9bc4f 100644 --- a/Userland/Applications/PixelPaint/Tools/RectangleSelectTool.cpp +++ b/Userland/Applications/PixelPaint/Tools/RectangleSelectTool.cpp @@ -136,7 +136,7 @@ void RectangleSelectTool::on_second_paint(Layer const*, GUI::PaintEvent& event) auto rect_in_image = Gfx::IntRect::from_two_points(m_selection_start, m_selection_end); auto rect_in_editor = m_editor->content_to_frame_rect(rect_in_image); - m_editor->selection().draw_marching_ants(painter, rect_in_editor.to_type()); + m_editor->draw_marching_ants(painter, rect_in_editor.to_rounded()); } GUI::Widget* RectangleSelectTool::get_properties_widget() -- cgit v1.2.3