ImageMasking.cpp 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. /*
  2. * Copyright (c) 2023, Torsten Engelmann <engelTorsten@gmx.de>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include "ImageMasking.h"
  7. #include <Applications/PixelPaint/LuminosityMaskingGML.h>
  8. #include <LibGUI/Button.h>
  9. #include <LibGUI/CheckBox.h>
  10. #include <LibGUI/Label.h>
  11. #include <LibGUI/Painter.h>
  12. #include <LibGUI/RangeSlider.h>
  13. #include <LibGfx/Palette.h>
  14. #include <LibGfx/Path.h>
  15. namespace PixelPaint {
  16. ImageMasking::ImageMasking(GUI::Window* parent_window, ImageEditor* editor)
  17. : GUI::Dialog(parent_window)
  18. {
  19. set_title("Luminosity Mask");
  20. set_icon(parent_window->icon());
  21. auto main_widget = set_main_widget<GUI::Widget>().release_value_but_fixme_should_propagate_errors();
  22. main_widget->load_from_gml(luminosity_masking_gml).release_value_but_fixme_should_propagate_errors();
  23. resize(300, 170);
  24. set_resizable(false);
  25. m_editor = editor;
  26. m_full_masking_slider = main_widget->find_descendant_of_type_named<GUI::RangeSlider>("full_masking");
  27. m_edge_masking_slider = main_widget->find_descendant_of_type_named<GUI::RangeSlider>("edge_masking");
  28. auto range_illustration_container = main_widget->find_descendant_of_type_named<GUI::Widget>("range_illustration");
  29. auto mask_visibility = main_widget->find_descendant_of_type_named<GUI::CheckBox>("mask_visibility");
  30. auto apply_button = main_widget->find_descendant_of_type_named<GUI::Button>("apply_button");
  31. auto cancel_button = main_widget->find_descendant_of_type_named<GUI::Button>("cancel_button");
  32. VERIFY(m_full_masking_slider);
  33. VERIFY(m_edge_masking_slider);
  34. VERIFY(range_illustration_container);
  35. VERIFY(mask_visibility);
  36. VERIFY(apply_button);
  37. VERIFY(cancel_button);
  38. VERIFY(m_editor->active_layer());
  39. m_full_masking_slider->set_gradient_color(Color(0, 0, 0, 255), Color(255, 255, 255, 255));
  40. m_edge_masking_slider->set_gradient_color(Color(0, 0, 0, 255), Color(255, 255, 255, 255));
  41. auto illustration_widget = range_illustration_container->try_add<RangeIllustrationWidget>(m_edge_masking_slider, m_full_masking_slider).release_value();
  42. illustration_widget->set_width(range_illustration_container->width());
  43. illustration_widget->set_height(range_illustration_container->height());
  44. // check that edges of full and edge masking are not intersecting, and refine the mask with the updated values
  45. m_full_masking_slider->on_range_change = [this, illustration_widget](int lower, int upper) {
  46. if (lower < m_edge_masking_slider->lower_range())
  47. m_full_masking_slider->set_lower_range(AK::max(lower, m_edge_masking_slider->lower_range()));
  48. if (upper > m_edge_masking_slider->upper_range())
  49. m_full_masking_slider->set_upper_range(AK::min(upper, m_edge_masking_slider->upper_range()));
  50. illustration_widget->update();
  51. generate_new_mask();
  52. };
  53. m_edge_masking_slider->on_range_change = [this, illustration_widget](int lower, int upper) {
  54. if (lower > m_full_masking_slider->lower_range())
  55. m_edge_masking_slider->set_lower_range(AK::min(lower, m_full_masking_slider->lower_range()));
  56. if (upper < m_full_masking_slider->upper_range())
  57. m_edge_masking_slider->set_upper_range(AK::max(upper, m_full_masking_slider->upper_range()));
  58. illustration_widget->update();
  59. generate_new_mask();
  60. };
  61. mask_visibility->set_checked(m_editor->active_layer()->mask_visibility());
  62. mask_visibility->on_checked = [this](auto checked) {
  63. m_editor->active_layer()->set_mask_visibility(checked);
  64. m_editor->update();
  65. };
  66. apply_button->on_click = [this](auto) {
  67. if (m_did_change)
  68. m_editor->did_complete_action("Luminosity Masking"sv);
  69. cleanup_resources();
  70. done(ExecResult::OK);
  71. };
  72. cancel_button->on_click = [this](auto) {
  73. done(ExecResult::Cancel);
  74. };
  75. generate_new_mask();
  76. }
  77. void ImageMasking::revert_possible_changes()
  78. {
  79. if (m_did_change && m_reference_mask) {
  80. MUST(m_editor->active_layer()->set_bitmaps(m_editor->active_layer()->content_bitmap(), m_reference_mask.release_nonnull()));
  81. m_editor->layers_did_change();
  82. }
  83. cleanup_resources();
  84. }
  85. void ImageMasking::generate_new_mask()
  86. {
  87. ensure_reference_mask().release_value_but_fixme_should_propagate_errors();
  88. if (m_reference_mask.is_null())
  89. return;
  90. int min_luminosity_start = m_edge_masking_slider->lower_range();
  91. int min_luminosity_full = m_full_masking_slider->lower_range();
  92. int max_luminosity_full = m_full_masking_slider->upper_range();
  93. int max_luminosity_end = m_edge_masking_slider->upper_range();
  94. int current_content_luminosity, approximation_alpha;
  95. bool has_start_range = min_luminosity_start != min_luminosity_full;
  96. bool has_end_range = max_luminosity_end != max_luminosity_full;
  97. Gfx::Color reference_mask_pixel, content_pixel;
  98. for (int y = 0; y < m_reference_mask->height(); y++) {
  99. for (int x = 0; x < m_reference_mask->width(); x++) {
  100. reference_mask_pixel = m_reference_mask->get_pixel(x, y);
  101. if (!reference_mask_pixel.alpha())
  102. continue;
  103. content_pixel = m_editor->active_layer()->content_bitmap().get_pixel(x, y);
  104. current_content_luminosity = content_pixel.luminosity();
  105. if (!content_pixel.alpha() || current_content_luminosity < min_luminosity_start || current_content_luminosity > max_luminosity_end) {
  106. reference_mask_pixel.set_alpha(0);
  107. } else if (current_content_luminosity >= min_luminosity_start && current_content_luminosity < min_luminosity_full && has_start_range) {
  108. approximation_alpha = reference_mask_pixel.alpha() * static_cast<float>((current_content_luminosity - min_luminosity_start)) / (min_luminosity_full - min_luminosity_start);
  109. reference_mask_pixel.set_alpha(approximation_alpha);
  110. } else if (current_content_luminosity > max_luminosity_full && current_content_luminosity <= max_luminosity_end && has_end_range) {
  111. approximation_alpha = reference_mask_pixel.alpha() * (1 - static_cast<float>((current_content_luminosity - max_luminosity_full)) / (max_luminosity_end - max_luminosity_full));
  112. reference_mask_pixel.set_alpha(approximation_alpha);
  113. }
  114. m_editor->active_layer()->mask_bitmap()->set_pixel(x, y, reference_mask_pixel);
  115. }
  116. }
  117. m_editor->active_layer()->did_modify_bitmap();
  118. m_did_change = true;
  119. }
  120. ErrorOr<void> ImageMasking::ensure_reference_mask()
  121. {
  122. if (m_reference_mask.is_null())
  123. m_reference_mask = TRY(m_editor->active_layer()->mask_bitmap()->clone());
  124. return {};
  125. }
  126. void ImageMasking::cleanup_resources()
  127. {
  128. if (m_reference_mask)
  129. m_reference_mask = nullptr;
  130. }
  131. void RangeIllustrationWidget::paint_event(GUI::PaintEvent&)
  132. {
  133. GUI::Painter painter(*this);
  134. painter.fill_rect(Gfx::IntRect(0, 0, width(), height()), palette().color(background_role()));
  135. float fraction = width() / 255.0f;
  136. Gfx::Path illustration;
  137. illustration.move_to({ fraction * m_edge_mask_values->lower_range(), static_cast<float>(height()) });
  138. illustration.line_to({ fraction * m_full_mask_values->lower_range(), 0 });
  139. illustration.line_to({ fraction * m_full_mask_values->upper_range(), 0 });
  140. illustration.line_to({ fraction * m_edge_mask_values->upper_range(), static_cast<float>(height()) });
  141. illustration.close();
  142. painter.fill_path(illustration, Color::MidGray);
  143. }
  144. }