diff --git a/Userland/Applications/PixelPaint/Layer.cpp b/Userland/Applications/PixelPaint/Layer.cpp index de1b6a0b24..96e800df02 100644 --- a/Userland/Applications/PixelPaint/Layer.cpp +++ b/Userland/Applications/PixelPaint/Layer.cpp @@ -44,6 +44,7 @@ ErrorOr> Layer::create_snapshot(Image& image, Layer const& if (layer.is_masked()) { 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; } /* @@ -273,7 +274,7 @@ ErrorOr Layer::scale(Gfx::IntRect const& new_rect, Gfx::Painter::ScalingMo void Layer::update_cached_bitmap() { - if (!is_masked()) { + if (mask_type() == MaskType::None || mask_type() == MaskType::EditingMask) { if (m_content_bitmap.ptr() == m_cached_display_bitmap.ptr()) return; m_cached_display_bitmap = m_content_bitmap; @@ -296,10 +297,23 @@ void Layer::update_cached_bitmap() } } -ErrorOr Layer::create_mask() +ErrorOr Layer::create_mask(MaskType type) { - m_mask_bitmap = TRY(Gfx::Bitmap::create(Gfx::BitmapFormat::BGRx8888, size())); - m_mask_bitmap->fill(Gfx::Color::White); + m_mask_type = type; + + switch (type) { + case MaskType::BasicMask: + m_mask_bitmap = TRY(Gfx::Bitmap::create(Gfx::BitmapFormat::BGRx8888, size())); + m_mask_bitmap->fill(Gfx::Color::White); + break; + case MaskType::EditingMask: + m_mask_bitmap = TRY(Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, size())); + break; + case MaskType::None: + VERIFY_NOT_REACHED(); + } + + set_edit_mode(EditMode::Mask); update_cached_bitmap(); return {}; } @@ -307,6 +321,7 @@ ErrorOr Layer::create_mask() void Layer::delete_mask() { m_mask_bitmap = nullptr; + m_mask_type = MaskType::None; set_edit_mode(EditMode::Content); update_cached_bitmap(); } @@ -319,6 +334,38 @@ void Layer::apply_mask() delete_mask(); } +void Layer::invert_mask() +{ + VERIFY(mask_type() != MaskType::None); + + for (int y = 0; y < size().height(); ++y) { + for (int x = 0; x < size().width(); ++x) { + auto inverted_mask_color = m_mask_bitmap->get_pixel(x, y).inverted(); + if (mask_type() == MaskType::EditingMask) + inverted_mask_color.set_alpha(255 - inverted_mask_color.alpha()); + m_mask_bitmap->set_pixel(x, y, inverted_mask_color); + } + } + + update_cached_bitmap(); +} + +void Layer::clear_mask() +{ + switch (mask_type()) { + case MaskType::None: + VERIFY_NOT_REACHED(); + case MaskType::BasicMask: + m_mask_bitmap->fill(Gfx::Color::White); + break; + case MaskType::EditingMask: + m_mask_bitmap->fill(Gfx::Color::Transparent); + break; + } + + update_cached_bitmap(); +} + Gfx::Bitmap& Layer::currently_edited_bitmap() { switch (edit_mode()) { @@ -410,4 +457,11 @@ ErrorOr> Layer::duplicate(DeprecatedString name) return duplicated_layer; } +Layer::MaskType Layer::mask_type() +{ + if (m_mask_bitmap.is_null()) + return MaskType::None; + return m_mask_type; +} + } diff --git a/Userland/Applications/PixelPaint/Layer.h b/Userland/Applications/PixelPaint/Layer.h index 37ffe37364..0509f6da7a 100644 --- a/Userland/Applications/PixelPaint/Layer.h +++ b/Userland/Applications/PixelPaint/Layer.h @@ -45,9 +45,17 @@ public: Gfx::Bitmap const* mask_bitmap() const { return m_mask_bitmap; } Gfx::Bitmap* mask_bitmap() { return m_mask_bitmap; } - ErrorOr create_mask(); + enum class MaskType { + None, + BasicMask, + EditingMask, + }; + + ErrorOr create_mask(MaskType); void delete_mask(); void apply_mask(); + void invert_mask(); + void clear_mask(); Gfx::Bitmap& get_scratch_edited_bitmap(); @@ -91,6 +99,7 @@ public: void erase_selection(Selection const&); bool is_masked() const { return !m_mask_bitmap.is_null(); } + MaskType mask_type(); enum class EditMode { Content, @@ -104,6 +113,19 @@ public: ErrorOr> duplicate(DeprecatedString name); + ALWAYS_INLINE Color modify_pixel_with_editing_mask(int x, int y, Color const& target_color, Color const& current_color) + { + if (mask_type() != MaskType::EditingMask) + return target_color; + + auto mask = mask_bitmap()->get_pixel(x, y).alpha(); + if (!mask) + return current_color; + + float mask_intensity = mask / 255.0f; + return current_color.mixed_with(target_color, mask_intensity); + } + private: Layer(Image&, NonnullRefPtr, DeprecatedString name); @@ -122,6 +144,7 @@ private: int m_opacity_percent { 100 }; EditMode m_edit_mode { EditMode::Content }; + MaskType m_mask_type { MaskType::None }; void update_cached_bitmap(); }; diff --git a/Userland/Applications/PixelPaint/MainWidget.cpp b/Userland/Applications/PixelPaint/MainWidget.cpp index cc17da94eb..e72902521c 100644 --- a/Userland/Applications/PixelPaint/MainWidget.cpp +++ b/Userland/Applications/PixelPaint/MainWidget.cpp @@ -812,11 +812,19 @@ ErrorOr MainWidget::initialize_menubar(GUI::Window& window) m_add_mask_action = GUI::Action::create( "Add M&ask", { Mod_Ctrl | Mod_Shift, Key_M }, g_icon_bag.add_mask, create_layer_mask_callback("Add Mask", [&](Layer* active_layer) { VERIFY(!active_layer->is_masked()); - if (auto maybe_error = active_layer->create_mask(); maybe_error.is_error()) + if (auto maybe_error = active_layer->create_mask(Layer::MaskType::BasicMask); maybe_error.is_error()) GUI::MessageBox::show_error(&window, MUST(String::formatted("Failed to create layer mask: {}", maybe_error.release_error()))); })); TRY(m_layer_menu->try_add_action(*m_add_mask_action)); + m_add_editing_mask_action = GUI::Action::create( + "Add E&diting Mask", { Mod_Ctrl | Mod_Alt, Key_E }, g_icon_bag.add_mask, create_layer_mask_callback("Add Editing Mask", [&](Layer* active_layer) { + VERIFY(!active_layer->is_masked()); + if (auto maybe_error = active_layer->create_mask(Layer::MaskType::EditingMask); maybe_error.is_error()) + GUI::MessageBox::show_error(&window, MUST(String::formatted("Failed to create layer mask: {}", maybe_error.release_error()))); + })); + TRY(m_layer_menu->try_add_action(*m_add_editing_mask_action)); + m_delete_mask_action = GUI::Action::create( "Delete Mask", create_layer_mask_callback("Delete Mask", [&](Layer* active_layer) { VERIFY(active_layer->is_masked()); @@ -831,6 +839,20 @@ ErrorOr MainWidget::initialize_menubar(GUI::Window& window) })); TRY(m_layer_menu->try_add_action(*m_apply_mask_action)); + m_invert_mask_action = GUI::Action::create( + "Invert Mask", create_layer_mask_callback("Invert Mask", [&](Layer* active_layer) { + VERIFY(active_layer->is_masked()); + active_layer->invert_mask(); + })); + TRY(m_layer_menu->try_add_action(*m_invert_mask_action)); + + m_clear_mask_action = GUI::Action::create( + "Clear Mask", create_layer_mask_callback("Clear Mask", [&](Layer* active_layer) { + VERIFY(active_layer->is_masked()); + active_layer->clear_mask(); + })); + TRY(m_layer_menu->try_add_action(*m_clear_mask_action)); + TRY(m_layer_menu->try_add_separator()); TRY(m_layer_menu->try_add_action(GUI::Action::create( @@ -1205,8 +1227,11 @@ void MainWidget::set_mask_actions_for_layer(Layer* layer) auto masked = layer->is_masked(); m_add_mask_action->set_visible(!masked); + m_add_editing_mask_action->set_visible(!masked); + m_invert_mask_action->set_visible(masked); + m_clear_mask_action->set_visible(masked); m_delete_mask_action->set_visible(masked); - m_apply_mask_action->set_visible(masked); + m_apply_mask_action->set_visible(layer->mask_type() == Layer::MaskType::BasicMask); } void MainWidget::open_image(FileSystemAccessClient::File file) diff --git a/Userland/Applications/PixelPaint/MainWidget.h b/Userland/Applications/PixelPaint/MainWidget.h index a72593b4f7..ed11d70207 100644 --- a/Userland/Applications/PixelPaint/MainWidget.h +++ b/Userland/Applications/PixelPaint/MainWidget.h @@ -114,6 +114,9 @@ private: RefPtr m_add_mask_action; RefPtr m_delete_mask_action; RefPtr m_apply_mask_action; + RefPtr m_add_editing_mask_action; + RefPtr m_invert_mask_action; + RefPtr m_clear_mask_action; Gfx::IntPoint m_last_image_editor_mouse_position; }; diff --git a/Userland/Applications/PixelPaint/Tools/Tool.cpp b/Userland/Applications/PixelPaint/Tools/Tool.cpp index 9ac6fb6169..a7699ff1b9 100644 --- a/Userland/Applications/PixelPaint/Tools/Tool.cpp +++ b/Userland/Applications/PixelPaint/Tools/Tool.cpp @@ -83,4 +83,35 @@ Gfx::IntPoint Tool::constrain_line_angle(Gfx::IntPoint start_pos, Gfx::IntPoint start_pos.y() + (int)(AK::sin(constrained_angle) * line_length) }; } +template<> +void Tool::set_pixel_with_possible_mask(int x, int y, Gfx::Color color, Gfx::Bitmap& bitmap) +{ + if (!m_editor || !m_editor->active_layer()) + return; + + switch (m_editor->active_layer()->edit_mode()) { + case Layer::EditMode::Content: + bitmap.set_pixel(x, y, m_editor->active_layer()->modify_pixel_with_editing_mask(x, y, color, bitmap.get_pixel(x, y))); + break; + case Layer::EditMode::Mask: + bitmap.set_pixel(x, y, color); + break; + } +} + +void Tool::set_pixel_with_possible_mask(int x, int y, Gfx::Color color, Gfx::Bitmap& bitmap) +{ + if (!m_editor || !m_editor->active_layer()) + return; + + switch (m_editor->active_layer()->edit_mode()) { + case Layer::EditMode::Content: + bitmap.set_pixel(x, y, m_editor->active_layer()->modify_pixel_with_editing_mask(x, y, color, bitmap.get_pixel(x, y))); + break; + case Layer::EditMode::Mask: + bitmap.set_pixel(x, y, color); + break; + } +} + } diff --git a/Userland/Applications/PixelPaint/Tools/Tool.h b/Userland/Applications/PixelPaint/Tools/Tool.h index 745a7d6a22..579f0fdb70 100644 --- a/Userland/Applications/PixelPaint/Tools/Tool.h +++ b/Userland/Applications/PixelPaint/Tools/Tool.h @@ -101,6 +101,10 @@ protected: GUI::AbstractSlider* m_primary_slider { nullptr }; GUI::AbstractSlider* m_secondary_slider { nullptr }; + + template + void set_pixel_with_possible_mask(int x, int y, Gfx::Color color, Gfx::Bitmap& bitmap); + void set_pixel_with_possible_mask(int x, int y, Gfx::Color color, Gfx::Bitmap& bitmap); }; }