Serenity Operating System
1/*
2 * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
3 * Copyright (c) 2021, Tobias Christiansen <tobyase@serenityos.org>
4 * Copyright (c) 2021-2022, Mustafa Quraish <mustafa@serenityos.org>
5 * Copyright (c) 2021, David Isaksson <davidisaksson93@gmail.com>
6 * Copyright (c) 2022, Timothy Slater <tslater2006@gmail.com>
7 *
8 * SPDX-License-Identifier: BSD-2-Clause
9 */
10
11#include "ImageEditor.h"
12#include "Image.h"
13#include "Layer.h"
14#include "Tools/MoveTool.h"
15#include "Tools/Tool.h"
16#include <AK/IntegralMath.h>
17#include <AK/LexicalPath.h>
18#include <LibConfig/Client.h>
19#include <LibFileSystemAccessClient/Client.h>
20#include <LibGUI/Command.h>
21#include <LibGUI/MessageBox.h>
22#include <LibGUI/Painter.h>
23#include <LibGfx/DisjointRectSet.h>
24#include <LibGfx/Palette.h>
25#include <LibGfx/Rect.h>
26
27namespace PixelPaint {
28
29constexpr int marching_ant_length = 4;
30
31ImageEditor::ImageEditor(NonnullRefPtr<Image> image)
32 : m_image(move(image))
33 , m_title("Untitled")
34 , m_gui_event_loop(Core::EventLoop::current())
35{
36 set_focus_policy(GUI::FocusPolicy::StrongFocus);
37 m_undo_stack.push(make<ImageUndoCommand>(*m_image, DeprecatedString()));
38 m_image->add_client(*this);
39 m_image->selection().add_client(*this);
40 set_original_rect(m_image->rect());
41 set_scale_bounds(0.1f, 100.0f);
42
43 m_pixel_grid_threshold = (float)Config::read_i32("PixelPaint"sv, "PixelGrid"sv, "Threshold"sv, 15);
44 m_show_pixel_grid = Config::read_bool("PixelPaint"sv, "PixelGrid"sv, "Show"sv, true);
45
46 m_show_rulers = Config::read_bool("PixelPaint"sv, "Rulers"sv, "Show"sv, true);
47 m_show_guides = Config::read_bool("PixelPaint"sv, "Guides"sv, "Show"sv, true);
48
49 m_marching_ants_timer = Core::Timer::create_repeating(80, [this] {
50 ++m_marching_ants_offset;
51 m_marching_ants_offset %= (marching_ant_length * 2);
52 if (!m_image->selection().is_empty() || m_image->selection().in_interactive_selection())
53 update();
54 }).release_value_but_fixme_should_propagate_errors();
55 m_marching_ants_timer->start();
56}
57
58ImageEditor::~ImageEditor()
59{
60 m_image->selection().remove_client(*this);
61 m_image->remove_client(*this);
62}
63
64void ImageEditor::did_complete_action(DeprecatedString action_text)
65{
66 set_modified(move(action_text));
67}
68
69bool ImageEditor::is_modified()
70{
71 return undo_stack().is_current_modified();
72}
73
74bool ImageEditor::undo()
75{
76 if (!m_undo_stack.can_undo())
77 return false;
78
79 /* Without this you need to undo twice to actually start undoing stuff.
80 * This is due to the fact that the top of the UndoStack contains the snapshot of the currently
81 * shown image but what we actually want to restore is the snapshot right below it.
82 * Doing "undo->undo->redo" restores the 2nd topmost snapshot on the stack while lowering the
83 * stack pointer only by 1. This is important because we want the UndoStack's pointer to always point
84 * at the currently shown snapshot, otherwise doing 'undo->undo->draw something else' would delete
85 * one of the snapshots.
86 * This works because UndoStack::undo first decrements the stack pointer and then restores the snapshot,
87 * while UndoStack::redo first restores the snapshot and then increments the stack pointer.
88 */
89 m_undo_stack.undo();
90 m_undo_stack.undo();
91 m_undo_stack.redo();
92 layers_did_change();
93 return true;
94}
95
96bool ImageEditor::redo()
97{
98 if (!m_undo_stack.can_redo())
99 return false;
100
101 m_undo_stack.redo();
102 layers_did_change();
103 return true;
104}
105
106void ImageEditor::set_title(DeprecatedString title)
107{
108 m_title = move(title);
109 if (on_title_change)
110 on_title_change(m_title);
111}
112
113void ImageEditor::set_path(DeprecatedString path)
114{
115 m_path = move(path);
116 set_title(LexicalPath::title(m_path));
117}
118
119void ImageEditor::set_modified(DeprecatedString action_text)
120{
121 m_undo_stack.push(make<ImageUndoCommand>(*m_image, move(action_text)));
122 update_modified();
123}
124
125void ImageEditor::set_unmodified()
126{
127 m_undo_stack.set_current_unmodified();
128 update_modified();
129}
130
131void ImageEditor::update_modified()
132{
133 if (on_modified_change)
134 on_modified_change(is_modified());
135}
136
137Gfx::IntRect ImageEditor::subtract_rulers_from_rect(Gfx::IntRect const& rect) const
138{
139 Gfx::IntRect clipped_rect {};
140 clipped_rect.set_top(max(rect.y(), m_ruler_thickness + 1));
141 clipped_rect.set_left(max(rect.x(), m_ruler_thickness + 1));
142 clipped_rect.set_bottom(rect.bottom());
143 clipped_rect.set_right(rect.right());
144 return clipped_rect;
145}
146
147void ImageEditor::paint_event(GUI::PaintEvent& event)
148{
149 GUI::Frame::paint_event(event);
150
151 GUI::Painter painter(*this);
152 painter.add_clip_rect(event.rect());
153 painter.add_clip_rect(frame_inner_rect());
154
155 {
156 Gfx::DisjointIntRectSet background_rects;
157 background_rects.add(frame_inner_rect());
158 background_rects.shatter(content_rect());
159 for (auto& rect : background_rects.rects())
160 painter.fill_rect(rect, palette().color(Gfx::ColorRole::Tray));
161 }
162
163 Gfx::StylePainter::paint_transparency_grid(painter, content_rect(), palette());
164
165 painter.draw_rect(content_rect().inflated(2, 2), Color::Black);
166 m_image->paint_into(painter, content_rect(), scale());
167
168 if (m_active_layer && m_show_active_layer_boundary)
169 painter.draw_rect(content_to_frame_rect(m_active_layer->relative_rect()).to_type<int>().inflated(2, 2), Color::Black);
170
171 if (m_show_pixel_grid && scale() > m_pixel_grid_threshold) {
172 auto event_image_rect = enclosing_int_rect(frame_to_content_rect(event.rect())).inflated(1, 1);
173 auto image_rect = m_image->rect().inflated(1, 1).intersected(event_image_rect);
174
175 for (auto i = image_rect.left(); i < image_rect.right(); i++) {
176 auto start_point = content_to_frame_position({ i, image_rect.top() }).to_type<int>();
177 auto end_point = content_to_frame_position({ i, image_rect.bottom() }).to_type<int>();
178 painter.draw_line(start_point, end_point, Color::LightGray);
179 }
180
181 for (auto i = image_rect.top(); i < image_rect.bottom(); i++) {
182 auto start_point = content_to_frame_position({ image_rect.left(), i }).to_type<int>();
183 auto end_point = content_to_frame_position({ image_rect.right(), i }).to_type<int>();
184 painter.draw_line(start_point, end_point, Color::LightGray);
185 }
186 }
187
188 if (m_show_guides) {
189 for (auto& guide : m_guides) {
190 if (guide->orientation() == Guide::Orientation::Horizontal) {
191 int y_coordinate = (int)content_to_frame_position({ 0.0f, guide->offset() }).y();
192 painter.draw_line({ 0, y_coordinate }, { rect().width(), y_coordinate }, Color::Cyan, 1, Gfx::Painter::LineStyle::Dashed, Color::LightGray);
193 } else if (guide->orientation() == Guide::Orientation::Vertical) {
194 int x_coordinate = (int)content_to_frame_position({ guide->offset(), 0.0f }).x();
195 painter.draw_line({ x_coordinate, 0 }, { x_coordinate, rect().height() }, Color::Cyan, 1, Gfx::Painter::LineStyle::Dashed, Color::LightGray);
196 }
197 }
198 }
199
200 paint_selection(painter);
201
202 if (m_show_rulers) {
203 auto const ruler_bg_color = palette().color(Gfx::ColorRole::InactiveSelection);
204 auto const ruler_fg_color = palette().color(Gfx::ColorRole::Ruler);
205 auto const ruler_text_color = palette().color(Gfx::ColorRole::InactiveSelectionText);
206 auto const mouse_indicator_color = Color::White;
207
208 // Ruler background
209 painter.fill_rect({ { 0, 0 }, { m_ruler_thickness, rect().height() } }, ruler_bg_color);
210 painter.fill_rect({ { 0, 0 }, { rect().width(), m_ruler_thickness } }, ruler_bg_color);
211
212 auto const ruler_step = calculate_ruler_step_size();
213 auto const editor_origin_to_image = frame_to_content_position({ 0, 0 });
214 auto const editor_max_to_image = frame_to_content_position({ width(), height() });
215
216 // Horizontal ruler
217 painter.draw_line({ 0, m_ruler_thickness }, { rect().width(), m_ruler_thickness }, ruler_fg_color);
218 auto const x_start = floor(editor_origin_to_image.x()) - ((int)floor(editor_origin_to_image.x()) % ruler_step) - ruler_step;
219 for (int x = x_start; x < editor_max_to_image.x(); x += ruler_step) {
220 int const num_sub_divisions = min(ruler_step, 10);
221 for (int x_sub = 0; x_sub < num_sub_divisions; ++x_sub) {
222 int const x_pos = x + (int)(ruler_step * x_sub / num_sub_divisions);
223 int const editor_x_sub = content_to_frame_position({ x_pos, 0 }).x();
224 int const line_length = (x_sub % 2 == 0) ? m_ruler_thickness / 3 : m_ruler_thickness / 6;
225 painter.draw_line({ editor_x_sub, m_ruler_thickness - line_length }, { editor_x_sub, m_ruler_thickness }, ruler_fg_color);
226 }
227
228 int const editor_x = content_to_frame_position({ x, 0 }).x();
229 painter.draw_line({ editor_x, 0 }, { editor_x, m_ruler_thickness }, ruler_fg_color);
230 painter.draw_text(Gfx::IntRect { { editor_x + 2, 0 }, { m_ruler_thickness, m_ruler_thickness - 2 } }, DeprecatedString::formatted("{}", x), painter.font(), Gfx::TextAlignment::CenterLeft, ruler_text_color);
231 }
232
233 // Vertical ruler
234 painter.draw_line({ m_ruler_thickness, 0 }, { m_ruler_thickness, rect().height() }, ruler_fg_color);
235 auto const y_start = floor(editor_origin_to_image.y()) - ((int)floor(editor_origin_to_image.y()) % ruler_step) - ruler_step;
236 for (int y = y_start; y < editor_max_to_image.y(); y += ruler_step) {
237 int const num_sub_divisions = min(ruler_step, 10);
238 for (int y_sub = 0; y_sub < num_sub_divisions; ++y_sub) {
239 int const y_pos = y + (int)(ruler_step * y_sub / num_sub_divisions);
240 int const editor_y_sub = content_to_frame_position({ 0, y_pos }).y();
241 int const line_length = (y_sub % 2 == 0) ? m_ruler_thickness / 3 : m_ruler_thickness / 6;
242 painter.draw_line({ m_ruler_thickness - line_length, editor_y_sub }, { m_ruler_thickness, editor_y_sub }, ruler_fg_color);
243 }
244
245 int const editor_y = content_to_frame_position({ 0, y }).y();
246 painter.draw_line({ 0, editor_y }, { m_ruler_thickness, editor_y }, ruler_fg_color);
247 painter.draw_text(Gfx::IntRect { { 0, editor_y - m_ruler_thickness }, { m_ruler_thickness, m_ruler_thickness } }, DeprecatedString::formatted("{}", y), painter.font(), Gfx::TextAlignment::BottomRight, ruler_text_color);
248 }
249
250 // Mouse position indicator
251 const Gfx::IntPoint indicator_x({ m_mouse_position.x(), m_ruler_thickness });
252 const Gfx::IntPoint indicator_y({ m_ruler_thickness, m_mouse_position.y() });
253 painter.draw_triangle(indicator_x, indicator_x + Gfx::IntPoint(-m_mouse_indicator_triangle_size, -m_mouse_indicator_triangle_size), indicator_x + Gfx::IntPoint(m_mouse_indicator_triangle_size, -m_mouse_indicator_triangle_size), mouse_indicator_color);
254 painter.draw_triangle(indicator_y, indicator_y + Gfx::IntPoint(-m_mouse_indicator_triangle_size, -m_mouse_indicator_triangle_size), indicator_y + Gfx::IntPoint(-m_mouse_indicator_triangle_size, m_mouse_indicator_triangle_size), mouse_indicator_color);
255
256 // Top left square
257 painter.fill_rect({ { 0, 0 }, { m_ruler_thickness, m_ruler_thickness } }, ruler_bg_color);
258 }
259}
260
261int ImageEditor::calculate_ruler_step_size() const
262{
263 auto const step_target = 80 / scale();
264 auto const max_factor = 5;
265 for (int factor = 0; factor < max_factor; ++factor) {
266 int ten_to_factor = AK::pow<int>(10, factor);
267 if (step_target <= 1 * ten_to_factor)
268 return 1 * ten_to_factor;
269 if (step_target <= 2 * ten_to_factor)
270 return 2 * ten_to_factor;
271 if (step_target <= 5 * ten_to_factor)
272 return 5 * ten_to_factor;
273 }
274 return AK::pow<int>(10, max_factor);
275}
276
277Gfx::IntRect ImageEditor::mouse_indicator_rect_x() const
278{
279 const Gfx::IntPoint top_left({ m_ruler_thickness, m_ruler_thickness - m_mouse_indicator_triangle_size });
280 const Gfx::IntSize size({ width() + 1, m_mouse_indicator_triangle_size + 1 });
281 return Gfx::IntRect(top_left, size);
282}
283
284Gfx::IntRect ImageEditor::mouse_indicator_rect_y() const
285{
286 const Gfx::IntPoint top_left({ m_ruler_thickness - m_mouse_indicator_triangle_size, m_ruler_thickness });
287 const Gfx::IntSize size({ m_mouse_indicator_triangle_size + 1, height() + 1 });
288 return Gfx::IntRect(top_left, size);
289}
290
291void ImageEditor::second_paint_event(GUI::PaintEvent& event)
292{
293 if (m_active_tool) {
294 if (m_show_rulers) {
295 auto clipped_event = GUI::PaintEvent(subtract_rulers_from_rect(event.rect()), event.window_size());
296 m_active_tool->on_second_paint(m_active_layer, clipped_event);
297 } else {
298 m_active_tool->on_second_paint(m_active_layer, event);
299 }
300 }
301}
302
303GUI::MouseEvent ImageEditor::event_with_pan_and_scale_applied(GUI::MouseEvent const& event) const
304{
305 auto image_position = frame_to_content_position(event.position());
306 auto tool_adjusted_image_position = m_active_tool->point_position_to_preferred_cell(image_position);
307
308 return {
309 static_cast<GUI::Event::Type>(event.type()),
310 tool_adjusted_image_position,
311 event.buttons(),
312 event.button(),
313 event.modifiers(),
314 event.wheel_delta_x(),
315 event.wheel_delta_y(),
316 event.wheel_raw_delta_x(),
317 event.wheel_raw_delta_y(),
318 };
319}
320
321GUI::MouseEvent ImageEditor::event_adjusted_for_layer(GUI::MouseEvent const& event, Layer const& layer) const
322{
323 auto image_position = frame_to_content_position(event.position());
324 image_position.translate_by(-layer.location().x(), -layer.location().y());
325 auto tool_adjusted_image_position = m_active_tool->point_position_to_preferred_cell(image_position);
326
327 return {
328 static_cast<GUI::Event::Type>(event.type()),
329 tool_adjusted_image_position,
330 event.buttons(),
331 event.button(),
332 event.modifiers(),
333 event.wheel_delta_x(),
334 event.wheel_delta_y(),
335 event.wheel_raw_delta_x(),
336 event.wheel_raw_delta_y(),
337 };
338}
339
340Optional<Color> ImageEditor::color_from_position(Gfx::IntPoint position, bool sample_all_layers)
341{
342 Color color;
343 auto* layer = active_layer();
344 if (sample_all_layers) {
345 color = image().color_at(position);
346 } else {
347 if (!layer || !layer->rect().contains(position))
348 return {};
349 color = layer->currently_edited_bitmap().get_pixel(position);
350 }
351 return color;
352}
353
354void ImageEditor::set_status_info_to_color_at_mouse_position(Gfx::IntPoint position, bool sample_all_layers)
355{
356 auto const color = color_from_position(position, sample_all_layers);
357 if (!color.has_value())
358 return;
359
360 set_appended_status_info(DeprecatedString::formatted("R:{}, G:{}, B:{}, A:{} [{}]", color->red(), color->green(), color->blue(), color->alpha(), color->to_deprecated_string()));
361}
362
363void ImageEditor::set_editor_color_to_color_at_mouse_position(GUI::MouseEvent const& event, bool sample_all_layers = false)
364{
365 auto const color = color_from_position(event.position(), sample_all_layers);
366
367 if (!color.has_value())
368 return;
369
370 // We picked a transparent pixel, do nothing.
371 if (!color->alpha())
372 return;
373
374 if (event.buttons() & GUI::MouseButton::Primary)
375 set_primary_color(*color);
376 if (event.buttons() & GUI::MouseButton::Secondary)
377 set_secondary_color(*color);
378}
379
380void ImageEditor::mousedown_event(GUI::MouseEvent& event)
381{
382 if (event.button() == GUI::MouseButton::Middle) {
383 start_panning(event.position());
384 set_override_cursor(Gfx::StandardCursor::Drag);
385 return;
386 }
387
388 if (!m_active_tool)
389 return;
390
391 if (auto* tool = dynamic_cast<MoveTool*>(m_active_tool); tool && tool->layer_selection_mode() == MoveTool::LayerSelectionMode::ForegroundLayer) {
392 if (auto* foreground_layer = layer_at_editor_position(event.position()); foreground_layer && !tool->cursor_is_within_resize_anchor())
393 set_active_layer(foreground_layer);
394 }
395
396 auto layer_event = m_active_layer ? event_adjusted_for_layer(event, *m_active_layer) : event;
397 if (event.alt() && !m_active_tool->is_overriding_alt()) {
398 set_editor_color_to_color_at_mouse_position(layer_event);
399 return; // Pick Color instead of acivating active tool when holding alt.
400 }
401
402 auto image_event = event_with_pan_and_scale_applied(event);
403 Tool::MouseEvent tool_event(Tool::MouseEvent::Action::MouseDown, layer_event, image_event, event);
404 m_active_tool->on_mousedown(m_active_layer.ptr(), tool_event);
405}
406
407void ImageEditor::doubleclick_event(GUI::MouseEvent& event)
408{
409 if (!m_active_tool || (event.alt() && !m_active_tool->is_overriding_alt()))
410 return;
411 auto layer_event = m_active_layer ? event_adjusted_for_layer(event, *m_active_layer) : event;
412 auto image_event = event_with_pan_and_scale_applied(event);
413 Tool::MouseEvent tool_event(Tool::MouseEvent::Action::DoubleClick, layer_event, image_event, event);
414 m_active_tool->on_doubleclick(m_active_layer.ptr(), tool_event);
415}
416
417void ImageEditor::mousemove_event(GUI::MouseEvent& event)
418{
419 m_mouse_position = event.position();
420 if (m_show_rulers) {
421 update(mouse_indicator_rect_x());
422 update(mouse_indicator_rect_y());
423 }
424
425 if (is_panning()) {
426 GUI::AbstractZoomPanWidget::mousemove_event(event);
427 return;
428 }
429
430 auto image_event = event_with_pan_and_scale_applied(event);
431 if (on_image_mouse_position_change) {
432 on_image_mouse_position_change(image_event.position());
433 }
434
435 auto layer_event = m_active_layer ? event_adjusted_for_layer(event, *m_active_layer) : event;
436 if (m_active_tool && event.alt() && !m_active_tool->is_overriding_alt()) {
437 set_override_cursor(Gfx::StandardCursor::Eyedropper);
438 set_editor_color_to_color_at_mouse_position(layer_event);
439 return;
440 }
441
442 Tool::MouseEvent tool_event(Tool::MouseEvent::Action::MouseDown, layer_event, image_event, event);
443 m_active_tool->on_mousemove(m_active_layer.ptr(), tool_event);
444}
445
446void ImageEditor::mouseup_event(GUI::MouseEvent& event)
447{
448 if (!(m_active_tool && event.alt() && !m_active_tool->is_overriding_alt()))
449 set_override_cursor(m_active_cursor);
450
451 if (event.button() == GUI::MouseButton::Middle) {
452 stop_panning();
453 return;
454 }
455
456 if (!m_active_tool || (event.alt() && !m_active_tool->is_overriding_alt()))
457 return;
458 auto layer_event = m_active_layer ? event_adjusted_for_layer(event, *m_active_layer) : event;
459 auto image_event = event_with_pan_and_scale_applied(event);
460 Tool::MouseEvent tool_event(Tool::MouseEvent::Action::MouseDown, layer_event, image_event, event);
461 m_active_tool->on_mouseup(m_active_layer.ptr(), tool_event);
462}
463
464void ImageEditor::context_menu_event(GUI::ContextMenuEvent& event)
465{
466 if (!m_active_tool)
467 return;
468 m_active_tool->on_context_menu(m_active_layer, event);
469}
470
471void ImageEditor::keydown_event(GUI::KeyEvent& event)
472{
473 if (event.key() == Key_Delete && !m_image->selection().is_empty() && active_layer()) {
474 active_layer()->erase_selection(m_image->selection());
475 did_complete_action("Erase Selection"sv);
476 return;
477 }
478
479 if (!m_active_tool)
480 return;
481
482 if (!m_active_tool->is_overriding_alt() && event.key() == Key_Alt)
483 set_override_cursor(Gfx::StandardCursor::Eyedropper);
484
485 if (m_active_tool->on_keydown(event))
486 return;
487
488 if (event.key() == Key_Escape && !m_image->selection().is_empty()) {
489 m_image->selection().clear();
490 did_complete_action("Clear Selection"sv);
491 return;
492 }
493
494 event.ignore();
495}
496
497void ImageEditor::keyup_event(GUI::KeyEvent& event)
498{
499 if (!m_active_tool)
500 return;
501
502 if (!m_active_tool->is_overriding_alt() && event.key() == Key_Alt)
503 update_tool_cursor();
504
505 m_active_tool->on_keyup(event);
506}
507
508void ImageEditor::enter_event(Core::Event&)
509{
510 set_override_cursor(m_active_cursor);
511}
512
513void ImageEditor::leave_event(Core::Event&)
514{
515 set_override_cursor(Gfx::StandardCursor::None);
516
517 if (on_leave)
518 on_leave();
519}
520
521void ImageEditor::set_active_layer(Layer* layer)
522{
523 if (m_active_layer == layer)
524 return;
525 m_active_layer = layer;
526
527 if (m_active_layer) {
528 VERIFY(&m_active_layer->image() == m_image.ptr());
529 size_t index = 0;
530 for (; index < m_image->layer_count(); ++index) {
531 if (&m_image->layer(index) == layer)
532 break;
533 }
534 if (on_active_layer_change)
535 on_active_layer_change(layer);
536 } else {
537 if (on_active_layer_change)
538 on_active_layer_change({});
539 }
540 if (m_show_active_layer_boundary)
541 update();
542}
543
544ErrorOr<void> ImageEditor::add_new_layer_from_selection()
545{
546 auto current_layer_selection = image().selection();
547 if (current_layer_selection.is_empty())
548 return Error::from_string_literal("There is no active selection to create layer from.");
549
550 // save offsets of selection so we know where to place the new layer
551 auto selection_offset = current_layer_selection.bounding_rect().location();
552
553 auto selection_bitmap = active_layer()->copy_bitmap(current_layer_selection);
554 if (selection_bitmap.is_null())
555 return Error::from_string_literal("Unable to create bitmap from selection.");
556
557 auto layer_or_error = PixelPaint::Layer::create_with_bitmap(image(), selection_bitmap.release_nonnull(), "New Layer"sv);
558 if (layer_or_error.is_error())
559 return Error::from_string_literal("Unable to create layer from selection.");
560
561 auto new_layer = layer_or_error.release_value();
562 new_layer->set_location(selection_offset);
563 image().add_layer(new_layer);
564 layers_did_change();
565 return {};
566}
567
568void ImageEditor::set_active_tool(Tool* tool)
569{
570 if (m_active_tool == tool) {
571 if (m_active_tool)
572 m_active_tool->setup(*this);
573 return;
574 }
575
576 if (m_active_tool) {
577 m_active_tool->on_tool_deactivation();
578 m_active_tool->clear();
579 }
580
581 m_active_tool = tool;
582
583 if (m_active_tool) {
584 m_active_tool->setup(*this);
585 m_active_tool->on_tool_activation();
586 m_active_cursor = m_active_tool->cursor();
587 set_override_cursor(m_active_cursor);
588 }
589}
590
591void ImageEditor::update_tool_cursor()
592{
593 if (m_active_tool) {
594 m_active_cursor = m_active_tool->cursor();
595 set_override_cursor(m_active_cursor);
596 }
597}
598
599void ImageEditor::set_guide_visibility(bool show_guides)
600{
601 if (m_show_guides == show_guides)
602 return;
603
604 m_show_guides = show_guides;
605
606 if (on_set_guide_visibility)
607 on_set_guide_visibility(m_show_guides);
608
609 update();
610}
611
612void ImageEditor::set_ruler_visibility(bool show_rulers)
613{
614 if (m_show_rulers == show_rulers)
615 return;
616
617 m_show_rulers = show_rulers;
618
619 if (on_set_ruler_visibility)
620 on_set_ruler_visibility(m_show_rulers);
621
622 update();
623}
624
625void ImageEditor::set_pixel_grid_visibility(bool show_pixel_grid)
626{
627 if (m_show_pixel_grid == show_pixel_grid)
628 return;
629 m_show_pixel_grid = show_pixel_grid;
630 update();
631}
632
633void ImageEditor::clear_guides()
634{
635 m_guides.clear();
636 update();
637}
638
639void ImageEditor::layers_did_change()
640{
641 update_modified();
642 update();
643}
644
645Color ImageEditor::color_for(GUI::MouseButton button) const
646{
647 if (button == GUI::MouseButton::Primary)
648 return m_primary_color;
649 if (button == GUI::MouseButton::Secondary)
650 return m_secondary_color;
651 VERIFY_NOT_REACHED();
652}
653
654Color ImageEditor::color_for(GUI::MouseEvent const& event) const
655{
656 if (event.buttons() & GUI::MouseButton::Primary)
657 return m_primary_color;
658 if (event.buttons() & GUI::MouseButton::Secondary)
659 return m_secondary_color;
660 VERIFY_NOT_REACHED();
661}
662
663void ImageEditor::set_primary_color(Color color)
664{
665 if (m_primary_color == color)
666 return;
667 m_primary_color = color;
668 if (on_primary_color_change)
669 on_primary_color_change(color);
670}
671
672void ImageEditor::set_secondary_color(Color color)
673{
674 if (m_secondary_color == color)
675 return;
676 m_secondary_color = color;
677 if (on_secondary_color_change)
678 on_secondary_color_change(color);
679}
680
681Layer* ImageEditor::layer_at_editor_position(Gfx::IntPoint editor_position)
682{
683 auto image_position = frame_to_content_position(editor_position);
684 for (ssize_t i = m_image->layer_count() - 1; i >= 0; --i) {
685 auto& layer = m_image->layer(i);
686 if (!layer.is_visible())
687 continue;
688 if (layer.relative_rect().contains(Gfx::IntPoint(image_position.x(), image_position.y())))
689 return const_cast<Layer*>(&layer);
690 }
691 return nullptr;
692}
693
694void ImageEditor::fit_image_to_view(FitType type)
695{
696 auto viewport_rect = rect();
697
698 if (m_show_rulers) {
699 viewport_rect = {
700 viewport_rect.x() + m_ruler_thickness,
701 viewport_rect.y() + m_ruler_thickness,
702 viewport_rect.width() - m_ruler_thickness,
703 viewport_rect.height() - m_ruler_thickness
704 };
705 }
706
707 fit_content_to_rect(viewport_rect, type);
708}
709
710void ImageEditor::image_did_change(Gfx::IntRect const& modified_image_rect)
711{
712 update(content_rect().intersected(enclosing_int_rect(content_to_frame_rect(modified_image_rect))));
713}
714
715void ImageEditor::image_did_change_rect(Gfx::IntRect const& new_image_rect)
716{
717 set_original_rect(new_image_rect);
718 set_content_rect(new_image_rect);
719 relayout();
720}
721
722void ImageEditor::image_select_layer(Layer* layer)
723{
724 set_active_layer(layer);
725}
726
727bool ImageEditor::request_close()
728{
729 if (!undo_stack().is_current_modified())
730 return true;
731
732 auto result = GUI::MessageBox::ask_about_unsaved_changes(window(), path(), undo_stack().last_unmodified_timestamp());
733
734 if (result == GUI::MessageBox::ExecResult::Yes) {
735 save_project();
736 return true;
737 }
738
739 if (result == GUI::MessageBox::ExecResult::No)
740 return true;
741
742 return false;
743}
744
745void ImageEditor::save_project()
746{
747 if (path().is_empty() || m_loaded_from_image) {
748 save_project_as();
749 return;
750 }
751 auto response = FileSystemAccessClient::Client::the().request_file(window(), path(), Core::File::OpenMode::Truncate | Core::File::OpenMode::Write);
752 if (response.is_error())
753 return;
754 auto result = save_project_to_file(response.value().release_stream());
755 if (result.is_error()) {
756 GUI::MessageBox::show_error(window(), DeprecatedString::formatted("Could not save {}: {}", path(), result.error()));
757 return;
758 }
759 set_unmodified();
760}
761
762void ImageEditor::save_project_as()
763{
764 auto response = FileSystemAccessClient::Client::the().save_file(window(), m_title, "pp");
765 if (response.is_error())
766 return;
767 auto file = response.release_value();
768 auto result = save_project_to_file(file.release_stream());
769 if (result.is_error()) {
770 GUI::MessageBox::show_error(window(), DeprecatedString::formatted("Could not save {}: {}", file.filename(), result.error()));
771 return;
772 }
773 set_path(file.filename().to_deprecated_string());
774 set_loaded_from_image(false);
775 set_unmodified();
776}
777
778ErrorOr<void> ImageEditor::save_project_to_file(NonnullOwnPtr<Core::File> file) const
779{
780 StringBuilder builder;
781 auto json = TRY(JsonObjectSerializer<>::try_create(builder));
782 TRY(m_image->serialize_as_json(json));
783 auto json_guides = TRY(json.add_array("guides"sv));
784 for (auto const& guide : m_guides) {
785 auto json_guide = TRY(json_guides.add_object());
786 TRY(json_guide.add("offset"sv, (double)guide->offset()));
787 if (guide->orientation() == Guide::Orientation::Vertical)
788 TRY(json_guide.add("orientation"sv, "vertical"));
789 else if (guide->orientation() == Guide::Orientation::Horizontal)
790 TRY(json_guide.add("orientation"sv, "horizontal"));
791 TRY(json_guide.finish());
792 }
793 TRY(json_guides.finish());
794 TRY(json.finish());
795
796 TRY(file->write_until_depleted(builder.string_view().bytes()));
797 return {};
798}
799
800void ImageEditor::set_show_active_layer_boundary(bool show)
801{
802 if (m_show_active_layer_boundary == show)
803 return;
804
805 m_show_active_layer_boundary = show;
806 update();
807}
808
809void ImageEditor::set_loaded_from_image(bool loaded_from_image)
810{
811 m_loaded_from_image = loaded_from_image;
812}
813
814void ImageEditor::paint_selection(Gfx::Painter& painter)
815{
816 if (m_image->selection().is_empty())
817 return;
818
819 draw_marching_ants(painter, m_image->selection().mask());
820}
821
822void ImageEditor::draw_marching_ants(Gfx::Painter& painter, Gfx::IntRect const& rect) const
823{
824 // Top line
825 for (int x = rect.left(); x <= rect.right(); ++x)
826 draw_marching_ants_pixel(painter, x, rect.top());
827
828 // Right line
829 for (int y = rect.top() + 1; y <= rect.bottom(); ++y)
830 draw_marching_ants_pixel(painter, rect.right(), y);
831
832 // Bottom line
833 for (int x = rect.right() - 1; x >= rect.left(); --x)
834 draw_marching_ants_pixel(painter, x, rect.bottom());
835
836 // Left line
837 for (int y = rect.bottom() - 1; y > rect.top(); --y)
838 draw_marching_ants_pixel(painter, rect.left(), y);
839}
840
841void ImageEditor::draw_marching_ants(Gfx::Painter& painter, Mask const& mask) const
842{
843 // If the zoom is < 100%, we can skip pixels to save a lot of time drawing the ants
844 int step = max(1, (int)floorf(1.0f / scale()));
845
846 // Only check the visible selection area when drawing for performance
847 auto rect = this->rect();
848 rect = Gfx::enclosing_int_rect(frame_to_content_rect(rect));
849 rect.inflate(step * 2, step * 2); // prevent borders from having visible ants if the selection extends beyond it
850
851 // Scan the image horizontally to find vertical borders
852 for (int y = rect.top(); y <= rect.bottom(); y += step) {
853
854 bool previous_selected = false;
855 for (int x = rect.left(); x <= rect.right(); x += step) {
856 bool this_selected = mask.get(x, y) > 0;
857
858 if (this_selected != previous_selected) {
859 Gfx::IntRect image_pixel { x, y, 1, 1 };
860 auto pixel = content_to_frame_rect(image_pixel).to_type<int>();
861 auto end = max(pixel.top(), pixel.bottom()); // for when the zoom is < 100%
862
863 for (int pixel_y = pixel.top(); pixel_y <= end; pixel_y++) {
864 draw_marching_ants_pixel(painter, pixel.left(), pixel_y);
865 }
866 }
867
868 previous_selected = this_selected;
869 }
870 }
871
872 // Scan the image vertically to find horizontal borders
873 for (int x = rect.left(); x <= rect.right(); x += step) {
874
875 bool previous_selected = false;
876 for (int y = rect.top(); y <= rect.bottom(); y += step) {
877 bool this_selected = mask.get(x, y) > 0;
878
879 if (this_selected != previous_selected) {
880 Gfx::IntRect image_pixel { x, y, 1, 1 };
881 auto pixel = content_to_frame_rect(image_pixel).to_type<int>();
882 auto end = max(pixel.left(), pixel.right()); // for when the zoom is < 100%
883
884 for (int pixel_x = pixel.left(); pixel_x <= end; pixel_x++) {
885 draw_marching_ants_pixel(painter, pixel_x, pixel.top());
886 }
887 }
888
889 previous_selected = this_selected;
890 }
891 }
892}
893
894void ImageEditor::draw_marching_ants_pixel(Gfx::Painter& painter, int x, int y) const
895{
896 int pattern_index = x + y + m_marching_ants_offset;
897
898 if (pattern_index % (marching_ant_length * 2) < marching_ant_length) {
899 painter.set_pixel(x, y, Color::Black);
900 } else {
901 painter.set_pixel(x, y, Color::White);
902 }
903}
904
905void ImageEditor::selection_did_change()
906{
907 update();
908}
909
910void ImageEditor::set_appended_status_info(DeprecatedString new_status_info)
911{
912 m_appended_status_info = new_status_info;
913 if (on_appended_status_info_change)
914 on_appended_status_info_change(m_appended_status_info);
915}
916
917}