PolygonalSelectTool.cpp 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  1. /*
  2. * Copyright (c) 2022-2023, the SerenityOS developers.
  3. * Copyright (c) 2023, Tim Ledbetter <timledbetter@gmail.com>
  4. *
  5. * SPDX-License-Identifier: BSD-2-Clause
  6. */
  7. #include "PolygonalSelectTool.h"
  8. #include "../ImageEditor.h"
  9. #include "../Layer.h"
  10. #include <AK/Queue.h>
  11. #include <LibGUI/BoxLayout.h>
  12. #include <LibGUI/Button.h>
  13. #include <LibGUI/ComboBox.h>
  14. #include <LibGUI/ItemListModel.h>
  15. #include <LibGUI/Label.h>
  16. #include <LibGUI/Model.h>
  17. #include <LibGUI/Painter.h>
  18. #include <LibGUI/ValueSlider.h>
  19. namespace PixelPaint {
  20. void PolygonalSelectTool::flood_polygon_selection(Gfx::Bitmap& polygon_bitmap, Gfx::IntPoint polygon_delta)
  21. {
  22. VERIFY(polygon_bitmap.bpp() == 32);
  23. // Create Mask which will track already-processed pixels.
  24. auto mask_rect = Gfx::IntRect(polygon_delta, polygon_bitmap.size()).intersected(m_editor->image().rect());
  25. auto selection_mask = Mask::full(mask_rect);
  26. auto pixel_reached = [&](Gfx::IntPoint location) {
  27. auto point_to_set = location.translated(polygon_delta);
  28. if (mask_rect.contains(point_to_set))
  29. selection_mask.set(point_to_set, 0);
  30. };
  31. polygon_bitmap.flood_visit_from_point({ 0, 0 }, 0, move(pixel_reached));
  32. selection_mask.shrink_to_fit();
  33. m_editor->image().selection().merge(selection_mask, m_merge_mode);
  34. }
  35. void PolygonalSelectTool::process_polygon()
  36. {
  37. // Determine minimum bounding box that can hold the polygon.
  38. auto top_left = m_polygon_points.at(0);
  39. auto bottom_right = m_polygon_points.at(0);
  40. for (auto point : m_polygon_points) {
  41. if (point.x() < top_left.x())
  42. top_left.set_x(point.x());
  43. if (point.x() > bottom_right.x())
  44. bottom_right.set_x(point.x());
  45. if (point.y() < top_left.y())
  46. top_left.set_y(point.y());
  47. if (point.y() > bottom_right.y())
  48. bottom_right.set_y(point.y());
  49. }
  50. top_left.translate_by(-1);
  51. auto polygon_rect = Gfx::IntRect::from_two_points(top_left, bottom_right);
  52. auto image_rect = m_editor->image().rect();
  53. if (!polygon_rect.intersects(image_rect)) {
  54. m_editor->image().selection().merge(Gfx::IntRect {}, m_merge_mode);
  55. return;
  56. }
  57. if (m_polygon_points.last() != m_polygon_points.first())
  58. m_polygon_points.append(m_polygon_points.first());
  59. // We want to paint the polygon into the bitmap such that there is an empty 1px border all the way around it
  60. // this ensures that we have a known pixel (0,0) that is outside the polygon.
  61. auto bitmap_rect = polygon_rect.inflated(2, 2);
  62. // FIXME: It should be possible to limit the size of the polygon bitmap to the size of the canvas, as that is
  63. // the maximum possible size of the selection.
  64. auto polygon_bitmap_or_error = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, bitmap_rect.size());
  65. if (polygon_bitmap_or_error.is_error())
  66. return;
  67. auto polygon_bitmap = polygon_bitmap_or_error.release_value();
  68. Gfx::Painter polygon_painter(polygon_bitmap);
  69. for (size_t i = 0; i < m_polygon_points.size() - 1; i++) {
  70. auto line_start = m_polygon_points.at(i) - top_left;
  71. auto line_end = m_polygon_points.at(i + 1) - top_left;
  72. polygon_painter.draw_line(line_start, line_end, Color::Black);
  73. }
  74. flood_polygon_selection(polygon_bitmap, top_left);
  75. }
  76. void PolygonalSelectTool::on_mousedown(Layer*, MouseEvent& event)
  77. {
  78. auto const& image_event = event.image_event();
  79. if (image_event.button() != GUI::MouseButton::Primary)
  80. return;
  81. if (!m_selecting) {
  82. m_polygon_points.clear();
  83. m_last_selecting_cursor_position = image_event.position();
  84. }
  85. m_selecting = true;
  86. auto new_point = image_event.position();
  87. if (!m_polygon_points.is_empty() && image_event.shift())
  88. new_point = Tool::constrain_line_angle(m_polygon_points.last(), new_point);
  89. // This point matches the first point exactly. Consider this polygon finished.
  90. if (m_polygon_points.size() > 0 && new_point == m_polygon_points.at(0)) {
  91. m_selecting = false;
  92. m_editor->image().selection().end_interactive_selection();
  93. process_polygon();
  94. m_editor->did_complete_action(tool_name());
  95. m_editor->update();
  96. return;
  97. }
  98. // Avoid adding the same point multiple times if the user clicks again without moving the mouse.
  99. if (m_polygon_points.size() > 0 && m_polygon_points.at(m_polygon_points.size() - 1) == new_point)
  100. return;
  101. m_polygon_points.append(new_point);
  102. m_editor->image().selection().begin_interactive_selection();
  103. m_editor->update();
  104. }
  105. void PolygonalSelectTool::on_mousemove(Layer*, MouseEvent& event)
  106. {
  107. if (!m_selecting)
  108. return;
  109. auto const& image_event = event.image_event();
  110. if (image_event.shift())
  111. m_last_selecting_cursor_position = Tool::constrain_line_angle(m_polygon_points.last(), image_event.position());
  112. else
  113. m_last_selecting_cursor_position = image_event.position();
  114. m_editor->update();
  115. }
  116. void PolygonalSelectTool::on_doubleclick(Layer*, MouseEvent&)
  117. {
  118. m_selecting = false;
  119. m_editor->image().selection().end_interactive_selection();
  120. process_polygon();
  121. m_editor->did_complete_action(tool_name());
  122. m_editor->update();
  123. }
  124. void PolygonalSelectTool::on_second_paint(Layer const*, GUI::PaintEvent& event)
  125. {
  126. if (!m_selecting)
  127. return;
  128. GUI::Painter painter(*m_editor);
  129. painter.add_clip_rect(event.rect());
  130. auto draw_preview_lines = [&](auto color, auto thickness) {
  131. for (size_t i = 0; i < m_polygon_points.size() - 1; i++) {
  132. auto preview_start = editor_stroke_position(m_polygon_points.at(i), 1);
  133. auto preview_end = editor_stroke_position(m_polygon_points.at(i + 1), 1);
  134. painter.draw_line(preview_start, preview_end, color, thickness);
  135. }
  136. auto last_line_start = editor_stroke_position(m_polygon_points.at(m_polygon_points.size() - 1), 1);
  137. auto last_line_stop = editor_stroke_position(m_last_selecting_cursor_position, 1);
  138. painter.draw_line(last_line_start, last_line_stop, color, thickness);
  139. };
  140. draw_preview_lines(Gfx::Color::Black, 3);
  141. draw_preview_lines(Gfx::Color::White, 1);
  142. }
  143. bool PolygonalSelectTool::on_keydown(GUI::KeyEvent& key_event)
  144. {
  145. if (key_event.key() == KeyCode::Key_Escape) {
  146. if (m_selecting) {
  147. m_selecting = false;
  148. m_polygon_points.clear();
  149. } else {
  150. m_editor->image().selection().clear();
  151. }
  152. return true;
  153. }
  154. return Tool::on_keydown(key_event);
  155. }
  156. ErrorOr<GUI::Widget*> PolygonalSelectTool::get_properties_widget()
  157. {
  158. if (m_properties_widget)
  159. return m_properties_widget.ptr();
  160. auto properties_widget = GUI::Widget::construct();
  161. properties_widget->set_layout<GUI::VerticalBoxLayout>();
  162. auto mode_container = TRY(properties_widget->try_add<GUI::Widget>());
  163. mode_container->set_fixed_height(20);
  164. mode_container->set_layout<GUI::HorizontalBoxLayout>();
  165. auto mode_label = TRY(mode_container->try_add<GUI::Label>());
  166. mode_label->set_text("Mode:"_string);
  167. mode_label->set_text_alignment(Gfx::TextAlignment::CenterLeft);
  168. mode_label->set_fixed_size(80, 20);
  169. static constexpr auto s_merge_mode_names = [] {
  170. Array<StringView, (int)Selection::MergeMode::__Count> names;
  171. for (size_t i = 0; i < names.size(); i++) {
  172. switch ((Selection::MergeMode)i) {
  173. case Selection::MergeMode::Set:
  174. names[i] = "Set"sv;
  175. break;
  176. case Selection::MergeMode::Add:
  177. names[i] = "Add"sv;
  178. break;
  179. case Selection::MergeMode::Subtract:
  180. names[i] = "Subtract"sv;
  181. break;
  182. case Selection::MergeMode::Intersect:
  183. names[i] = "Intersect"sv;
  184. break;
  185. default:
  186. break;
  187. }
  188. }
  189. return names;
  190. }();
  191. auto mode_combo = TRY(mode_container->try_add<GUI::ComboBox>());
  192. mode_combo->set_only_allow_values_from_model(true);
  193. mode_combo->set_model(*GUI::ItemListModel<StringView, decltype(s_merge_mode_names)>::create(s_merge_mode_names));
  194. mode_combo->set_selected_index((int)m_merge_mode);
  195. mode_combo->on_change = [this](auto&&, GUI::ModelIndex const& index) {
  196. VERIFY(index.row() >= 0);
  197. VERIFY(index.row() < (int)Selection::MergeMode::__Count);
  198. m_merge_mode = (Selection::MergeMode)index.row();
  199. };
  200. m_properties_widget = properties_widget;
  201. return m_properties_widget.ptr();
  202. }
  203. Gfx::IntPoint PolygonalSelectTool::point_position_to_preferred_cell(Gfx::FloatPoint position) const
  204. {
  205. return position.to_type<int>();
  206. }
  207. }