From 660d6f171c1e00bc5ee417ffa5eb23ec6bd5c60f Mon Sep 17 00:00:00 2001 From: Torstennator Date: Tue, 13 Jun 2023 14:59:32 +0200 Subject: [PATCH] PixelPaint: Add function to visualize editing-masks This patch adds a function to make the editing-eask visible while beeing in mask-mode so that the user can see which parts are covered by the masks and can therefore be modified by other tools that support editing masks. --- .../Applications/PixelPaint/ImageEditor.cpp | 14 ++++++++ .../Applications/PixelPaint/ImageEditor.h | 1 + Userland/Applications/PixelPaint/Layer.cpp | 35 +++++++++++++++++++ Userland/Applications/PixelPaint/Layer.h | 7 ++++ .../Applications/PixelPaint/MainWidget.cpp | 18 ++++++++++ Userland/Applications/PixelPaint/MainWidget.h | 1 + 6 files changed, 76 insertions(+) diff --git a/Userland/Applications/PixelPaint/ImageEditor.cpp b/Userland/Applications/PixelPaint/ImageEditor.cpp index 1e4d605038..870204a683 100644 --- a/Userland/Applications/PixelPaint/ImageEditor.cpp +++ b/Userland/Applications/PixelPaint/ImageEditor.cpp @@ -290,6 +290,9 @@ Gfx::IntRect ImageEditor::mouse_indicator_rect_y() const void ImageEditor::second_paint_event(GUI::PaintEvent& event) { + if (m_active_layer && m_active_layer->mask_type() != Layer::MaskType::None) + m_active_layer->on_second_paint(*this); + if (m_active_tool) { if (m_show_rulers) { auto clipped_event = GUI::PaintEvent(subtract_rulers_from_rect(event.rect()), event.window_size()); @@ -945,4 +948,15 @@ DeprecatedString ImageEditor::generate_unique_layer_name(DeprecatedString const& return new_layer_name.to_deprecated_string(); } +Gfx::IntRect ImageEditor::active_layer_visible_rect() +{ + if (!active_layer()) + return {}; + + auto scaled_layer_rect = active_layer()->relative_rect().to_type().scaled(scale(), scale()).to_type().translated(content_rect().location()); + auto visible_editor_rect = ruler_visibility() ? subtract_rulers_from_rect(rect()) : rect(); + scaled_layer_rect.intersect(visible_editor_rect); + return scaled_layer_rect; +} + } diff --git a/Userland/Applications/PixelPaint/ImageEditor.h b/Userland/Applications/PixelPaint/ImageEditor.h index da8b03c0d8..46843f59ba 100644 --- a/Userland/Applications/PixelPaint/ImageEditor.h +++ b/Userland/Applications/PixelPaint/ImageEditor.h @@ -37,6 +37,7 @@ public: Layer* active_layer() { return m_active_layer; } void set_active_layer(Layer*); + Gfx::IntRect active_layer_visible_rect(); ErrorOr add_new_layer_from_selection(); Tool* active_tool() { return m_active_tool; } diff --git a/Userland/Applications/PixelPaint/Layer.cpp b/Userland/Applications/PixelPaint/Layer.cpp index 96e800df02..297499ea52 100644 --- a/Userland/Applications/PixelPaint/Layer.cpp +++ b/Userland/Applications/PixelPaint/Layer.cpp @@ -8,9 +8,11 @@ #include "Layer.h" #include "Image.h" +#include "ImageEditor.h" #include "Selection.h" #include #include +#include #include #include @@ -45,6 +47,7 @@ ErrorOr> Layer::create_snapshot(Image& image, Layer const& snapshot->m_mask_bitmap = TRY(layer.mask_bitmap()->clone()); snapshot->m_edit_mode = layer.m_edit_mode; snapshot->m_mask_type = layer.m_mask_type; + snapshot->m_visible_mask = layer.m_visible_mask; } /* @@ -322,6 +325,7 @@ void Layer::delete_mask() { m_mask_bitmap = nullptr; m_mask_type = MaskType::None; + m_visible_mask = false; set_edit_mode(EditMode::Content); update_cached_bitmap(); } @@ -464,4 +468,35 @@ Layer::MaskType Layer::mask_type() return m_mask_type; } +void Layer::on_second_paint(ImageEditor& editor) +{ + if (!m_visible_mask || edit_mode() != EditMode::Mask) + return; + + auto visible_rect = editor.active_layer_visible_rect(); + if (visible_rect.width() == 0 || visible_rect.height() == 0) + return; + + GUI::Painter painter(editor); + painter.translate(visible_rect.location()); + + auto content_offset = editor.content_to_frame_position(location()); + auto drawing_cursor_offset = visible_rect.location() - content_offset.to_type(); + + Gfx::Color editing_mask_color = editor.primary_color(); + int mask_alpha; + Gfx::IntPoint mask_coordinates; + + for (int y = 0; y < visible_rect.height(); y++) { + for (int x = 0; x < visible_rect.width(); x++) { + mask_coordinates = (Gfx::FloatPoint(drawing_cursor_offset.x() + x, drawing_cursor_offset.y() + y) / editor.scale()).to_type(); + mask_alpha = mask_bitmap()->get_pixel(mask_coordinates).alpha(); + if (!mask_alpha) + continue; + + painter.set_pixel(x, y, editing_mask_color.with_alpha(mask_alpha), true); + } + } +} + } diff --git a/Userland/Applications/PixelPaint/Layer.h b/Userland/Applications/PixelPaint/Layer.h index 0509f6da7a..382a5c565c 100644 --- a/Userland/Applications/PixelPaint/Layer.h +++ b/Userland/Applications/PixelPaint/Layer.h @@ -13,12 +13,14 @@ #include #include #include +#include #include #include namespace PixelPaint { class Image; +class ImageEditor; class Selection; class Layer @@ -56,6 +58,8 @@ public: void apply_mask(); void invert_mask(); void clear_mask(); + void set_mask_visibility(bool visible) { m_visible_mask = visible; } + bool mask_visibility() { return m_visible_mask; } Gfx::Bitmap& get_scratch_edited_bitmap(); @@ -126,6 +130,8 @@ public: return current_color.mixed_with(target_color, mask_intensity); } + void on_second_paint(ImageEditor&); + private: Layer(Image&, NonnullRefPtr, DeprecatedString name); @@ -140,6 +146,7 @@ private: bool m_selected { false }; bool m_visible { true }; + bool m_visible_mask { false }; int m_opacity_percent { 100 }; diff --git a/Userland/Applications/PixelPaint/MainWidget.cpp b/Userland/Applications/PixelPaint/MainWidget.cpp index e72902521c..992b25d955 100644 --- a/Userland/Applications/PixelPaint/MainWidget.cpp +++ b/Userland/Applications/PixelPaint/MainWidget.cpp @@ -853,6 +853,20 @@ ErrorOr MainWidget::initialize_menubar(GUI::Window& window) })); TRY(m_layer_menu->try_add_action(*m_clear_mask_action)); + m_toggle_mask_visibility_action = GUI::Action::create_checkable( + "Show Mask", [&](auto&) { + auto* editor = current_image_editor(); + VERIFY(editor); + if (!editor->active_layer()) + return; + + VERIFY(editor->active_layer()->is_masked()); + editor->active_layer()->set_mask_visibility(m_toggle_mask_visibility_action->is_checked()); + editor->update(); + }); + + TRY(m_layer_menu->try_add_action(*m_toggle_mask_visibility_action)); + TRY(m_layer_menu->try_add_separator()); TRY(m_layer_menu->try_add_action(GUI::Action::create( @@ -1232,6 +1246,8 @@ void MainWidget::set_mask_actions_for_layer(Layer* layer) m_clear_mask_action->set_visible(masked); m_delete_mask_action->set_visible(masked); m_apply_mask_action->set_visible(layer->mask_type() == Layer::MaskType::BasicMask); + m_toggle_mask_visibility_action->set_visible(layer->mask_type() == Layer::MaskType::EditingMask); + m_toggle_mask_visibility_action->set_checked(layer->mask_visibility()); } void MainWidget::open_image(FileSystemAccessClient::File file) @@ -1373,6 +1389,8 @@ ImageEditor& MainWidget::create_new_editor(NonnullRefPtr image) m_palette_widget->set_primary_color(color); if (image_editor.active_tool()) image_editor.active_tool()->on_primary_color_change(color); + if (image_editor.active_layer()->mask_visibility()) + image_editor.update(); }; image_editor.on_secondary_color_change = [&](Color color) { m_palette_widget->set_secondary_color(color); diff --git a/Userland/Applications/PixelPaint/MainWidget.h b/Userland/Applications/PixelPaint/MainWidget.h index ed11d70207..2cf99a870e 100644 --- a/Userland/Applications/PixelPaint/MainWidget.h +++ b/Userland/Applications/PixelPaint/MainWidget.h @@ -117,6 +117,7 @@ private: RefPtr m_add_editing_mask_action; RefPtr m_invert_mask_action; RefPtr m_clear_mask_action; + RefPtr m_toggle_mask_visibility_action; Gfx::IntPoint m_last_image_editor_mouse_position; };