From 0d7d759095e5cab6cc1c9799574ca712caba8bc0 Mon Sep 17 00:00:00 2001 From: Timothy Slater Date: Sat, 22 Oct 2022 13:35:52 -0500 Subject: [PATCH] PixelPaint: Limit editing tools to selection This effectively creates a double-buffer for tools to use when modifying the layer's bitmap (content or mask). Once the changes have been made the tool reports to the layer that it has made changes along with a Rect of the changed region. The layer will then merge the changes from the scratch image to the real bitmap. This merge is done as follows: If a given pixel is inside the selected region, the pixel from the scratch bitmap is copied to the real bitmap. If the pixel is not inside the selected region, the pixel from the real bitmap is copied to the scratch bitmap. As an optimization, when there is no selection active, the new method for getting the scratch bitmap will return the real bitmap and no merging will need to take place. --- Userland/Applications/PixelPaint/Layer.cpp | 34 ++++++++++++++++++- Userland/Applications/PixelPaint/Layer.h | 3 ++ .../PixelPaint/Tools/BrushTool.cpp | 6 ++-- .../PixelPaint/Tools/BucketTool.cpp | 6 ++-- .../PixelPaint/Tools/EllipseTool.cpp | 4 +-- .../PixelPaint/Tools/LineTool.cpp | 5 +-- .../PixelPaint/Tools/RectangleTool.cpp | 5 +-- .../PixelPaint/Tools/SprayTool.cpp | 2 +- 8 files changed, 51 insertions(+), 14 deletions(-) diff --git a/Userland/Applications/PixelPaint/Layer.cpp b/Userland/Applications/PixelPaint/Layer.cpp index 3c8bab3035..6a8c38cb90 100644 --- a/Userland/Applications/PixelPaint/Layer.cpp +++ b/Userland/Applications/PixelPaint/Layer.cpp @@ -1,6 +1,7 @@ /* * Copyright (c) 2020-2021, Andreas Kling * Copyright (c) 2022, Tobias Christiansen + * Copyright (c) 2022, Timothy Slater * * SPDX-License-Identifier: BSD-2-Clause */ @@ -65,6 +66,21 @@ Layer::Layer(Image& image, NonnullRefPtr bitmap, String name) void Layer::did_modify_bitmap(Gfx::IntRect const& rect) { + if (!m_scratch_edited_bitmap.is_null()) { + for (int y = 0; y < rect.height(); ++y) { + for (int x = 0; x < rect.width(); ++x) { + Gfx::IntPoint next_point = { rect.left() + x, rect.top() + y }; + if (!m_scratch_edited_bitmap->rect().contains(next_point)) + continue; + + if (this->image().selection().is_selected(next_point.translated(this->location()))) + currently_edited_bitmap().set_pixel(next_point, m_scratch_edited_bitmap->get_pixel(next_point)); + else + m_scratch_edited_bitmap->set_pixel(next_point, currently_edited_bitmap().get_pixel(next_point)); + } + } + } + m_image.layer_did_modify_bitmap({}, *this, rect); update_cached_bitmap(); } @@ -93,6 +109,21 @@ void Layer::set_name(String name) m_image.layer_did_modify_properties({}, *this); } +Gfx::Bitmap& Layer::get_scratch_edited_bitmap() +{ + if (this->image().selection().is_empty()) { + m_scratch_edited_bitmap = nullptr; + return currently_edited_bitmap(); + } + + if (!m_scratch_edited_bitmap.is_null()) + return *m_scratch_edited_bitmap; + + m_scratch_edited_bitmap = MUST(currently_edited_bitmap().clone()); + + return *m_scratch_edited_bitmap; +} + RefPtr Layer::try_copy_bitmap(Selection const& selection) const { if (selection.is_empty()) { @@ -158,6 +189,7 @@ ErrorOr Layer::try_set_bitmaps(NonnullRefPtr content, RefPtr< m_content_bitmap = move(content); m_mask_bitmap = move(mask); + m_scratch_edited_bitmap = nullptr; update_cached_bitmap(); return {}; } @@ -274,7 +306,7 @@ void Layer::set_edit_mode(Layer::EditMode mode) { if (m_edit_mode == mode) return; - + m_scratch_edited_bitmap = nullptr; m_edit_mode = mode; } diff --git a/Userland/Applications/PixelPaint/Layer.h b/Userland/Applications/PixelPaint/Layer.h index a2996e24c0..6e5121ecee 100644 --- a/Userland/Applications/PixelPaint/Layer.h +++ b/Userland/Applications/PixelPaint/Layer.h @@ -2,6 +2,7 @@ * Copyright (c) 2020-2021, Andreas Kling * Copyright (c) 2022, the SerenityOS developers. * Copyright (c) 2022, Tobias Christiansen + * Copyright (c) 2022, Timothy Slater * * SPDX-License-Identifier: BSD-2-Clause */ @@ -45,6 +46,7 @@ public: Gfx::Bitmap* mask_bitmap() { return m_mask_bitmap; } void create_mask(); + Gfx::Bitmap& get_scratch_edited_bitmap(); Gfx::IntSize size() const { return content_bitmap().size(); } @@ -102,6 +104,7 @@ private: String m_name; Gfx::IntPoint m_location; NonnullRefPtr m_content_bitmap; + RefPtr m_scratch_edited_bitmap { nullptr }; RefPtr m_mask_bitmap { nullptr }; NonnullRefPtr m_cached_display_bitmap; diff --git a/Userland/Applications/PixelPaint/Tools/BrushTool.cpp b/Userland/Applications/PixelPaint/Tools/BrushTool.cpp index 802ebbc0d2..88568da916 100644 --- a/Userland/Applications/PixelPaint/Tools/BrushTool.cpp +++ b/Userland/Applications/PixelPaint/Tools/BrushTool.cpp @@ -29,7 +29,7 @@ void BrushTool::on_mousedown(Layer* layer, MouseEvent& event) // Shift+Click draws a line from the last position to current one. if (layer_event.shift() && m_has_clicked) { - draw_line(layer->currently_edited_bitmap(), color_for(layer_event), m_last_position, layer_event.position()); + draw_line(layer->get_scratch_edited_bitmap(), color_for(layer_event), m_last_position, layer_event.position()); auto modified_rect = Gfx::IntRect::from_two_points(m_last_position, layer_event.position()).inflated(m_size * 2, m_size * 2); layer->did_modify_bitmap(modified_rect); m_last_position = layer_event.position(); @@ -39,7 +39,7 @@ void BrushTool::on_mousedown(Layer* layer, MouseEvent& event) int const first_draw_opacity = 10; for (int i = 0; i < first_draw_opacity; ++i) - draw_point(layer->currently_edited_bitmap(), color_for(layer_event), layer_event.position()); + draw_point(layer->get_scratch_edited_bitmap(), color_for(layer_event), layer_event.position()); layer->did_modify_bitmap(Gfx::IntRect::centered_on(layer_event.position(), Gfx::IntSize { m_size * 2, m_size * 2 })); m_last_position = layer_event.position(); @@ -55,7 +55,7 @@ void BrushTool::on_mousemove(Layer* layer, MouseEvent& event) if (!(layer_event.buttons() & GUI::MouseButton::Primary || layer_event.buttons() & GUI::MouseButton::Secondary)) return; - draw_line(layer->currently_edited_bitmap(), color_for(layer_event), m_last_position, layer_event.position()); + draw_line(layer->get_scratch_edited_bitmap(), color_for(layer_event), m_last_position, layer_event.position()); auto modified_rect = Gfx::IntRect::from_two_points(m_last_position, layer_event.position()).inflated(m_size * 2, m_size * 2); diff --git a/Userland/Applications/PixelPaint/Tools/BucketTool.cpp b/Userland/Applications/PixelPaint/Tools/BucketTool.cpp index 4cba2b9699..80228d5fc7 100644 --- a/Userland/Applications/PixelPaint/Tools/BucketTool.cpp +++ b/Userland/Applications/PixelPaint/Tools/BucketTool.cpp @@ -50,11 +50,11 @@ void BucketTool::on_mousedown(Layer* layer, MouseEvent& event) if (!layer->rect().contains(layer_event.position())) return; - GUI::Painter painter(layer->currently_edited_bitmap()); + GUI::Painter painter(layer->get_scratch_edited_bitmap()); - flood_fill(layer->currently_edited_bitmap(), layer_event.position(), m_editor->color_for(layer_event), m_threshold); + flood_fill(layer->get_scratch_edited_bitmap(), layer_event.position(), m_editor->color_for(layer_event), m_threshold); - layer->did_modify_bitmap(); + layer->did_modify_bitmap(layer->get_scratch_edited_bitmap().rect()); m_editor->did_complete_action(tool_name()); } diff --git a/Userland/Applications/PixelPaint/Tools/EllipseTool.cpp b/Userland/Applications/PixelPaint/Tools/EllipseTool.cpp index a9256cb100..aa3b676843 100644 --- a/Userland/Applications/PixelPaint/Tools/EllipseTool.cpp +++ b/Userland/Applications/PixelPaint/Tools/EllipseTool.cpp @@ -84,10 +84,10 @@ void EllipseTool::on_mouseup(Layer* layer, MouseEvent& event) return; if (event.layer_event().button() == m_drawing_button) { - GUI::Painter painter(layer->currently_edited_bitmap()); + GUI::Painter painter(layer->get_scratch_edited_bitmap()); draw_using(painter, m_ellipse_start_position, m_ellipse_end_position, m_thickness); m_drawing_button = GUI::MouseButton::None; - layer->did_modify_bitmap(); + layer->did_modify_bitmap(layer->get_scratch_edited_bitmap().rect()); m_editor->update(); m_editor->did_complete_action(tool_name()); } diff --git a/Userland/Applications/PixelPaint/Tools/LineTool.cpp b/Userland/Applications/PixelPaint/Tools/LineTool.cpp index dbb12f3f3b..5aebf8b885 100644 --- a/Userland/Applications/PixelPaint/Tools/LineTool.cpp +++ b/Userland/Applications/PixelPaint/Tools/LineTool.cpp @@ -76,10 +76,11 @@ void LineTool::on_mouseup(Layer* layer, MouseEvent& event) auto& layer_event = event.layer_event(); if (layer_event.button() == m_drawing_button) { - GUI::Painter painter(layer->currently_edited_bitmap()); + GUI::Painter painter(layer->get_scratch_edited_bitmap()); draw_using(painter, m_line_start_position, m_line_end_position, m_editor->color_for(m_drawing_button), m_thickness); m_drawing_button = GUI::MouseButton::None; - layer->did_modify_bitmap(); + auto modified_rect = Gfx::IntRect::from_two_points(m_line_start_position, m_line_end_position).inflated(m_thickness * 2, m_thickness * 2); + layer->did_modify_bitmap(modified_rect); m_editor->update(); m_editor->did_complete_action(tool_name()); } diff --git a/Userland/Applications/PixelPaint/Tools/RectangleTool.cpp b/Userland/Applications/PixelPaint/Tools/RectangleTool.cpp index 44fe26ec5c..fb88922eef 100644 --- a/Userland/Applications/PixelPaint/Tools/RectangleTool.cpp +++ b/Userland/Applications/PixelPaint/Tools/RectangleTool.cpp @@ -87,10 +87,11 @@ void RectangleTool::on_mouseup(Layer* layer, MouseEvent& event) return; if (event.layer_event().button() == m_drawing_button) { - GUI::Painter painter(layer->currently_edited_bitmap()); + GUI::Painter painter(layer->get_scratch_edited_bitmap()); draw_using(painter, m_rectangle_start_position, m_rectangle_end_position, m_thickness, m_corner_radius); m_drawing_button = GUI::MouseButton::None; - layer->did_modify_bitmap(); + auto modified_rect = Gfx::IntRect::from_two_points(m_rectangle_start_position, m_rectangle_end_position).inflated(m_thickness * 2, m_thickness * 2); + layer->did_modify_bitmap(modified_rect); m_editor->update(); m_editor->did_complete_action(tool_name()); } diff --git a/Userland/Applications/PixelPaint/Tools/SprayTool.cpp b/Userland/Applications/PixelPaint/Tools/SprayTool.cpp index 1118d6333f..a28bc03137 100644 --- a/Userland/Applications/PixelPaint/Tools/SprayTool.cpp +++ b/Userland/Applications/PixelPaint/Tools/SprayTool.cpp @@ -40,7 +40,7 @@ void SprayTool::paint_it() if (!layer) return; - auto& bitmap = layer->currently_edited_bitmap(); + auto& bitmap = layer->get_scratch_edited_bitmap(); GUI::Painter painter(bitmap); VERIFY(bitmap.bpp() == 32); double const minimal_radius = 2;