From f9e0815c3b2588297ebdf08938859988bbe8a5b8 Mon Sep 17 00:00:00 2001 From: Marcus Nilsson Date: Fri, 17 Sep 2021 09:54:55 +0200 Subject: PixelPaint: Move Tools to it's own subdirectory The PixelPaint source directory was getting a bit large, let's move all the Tools to it's own subdirectory. Also remove some unused includes. --- Userland/Applications/PixelPaint/BrushTool.cpp | 183 ------------------ Userland/Applications/PixelPaint/BrushTool.h | 45 ----- Userland/Applications/PixelPaint/BucketTool.cpp | 123 ------------ Userland/Applications/PixelPaint/BucketTool.h | 26 --- Userland/Applications/PixelPaint/CMakeLists.txt | 38 ++-- Userland/Applications/PixelPaint/CloneTool.cpp | 179 ----------------- Userland/Applications/PixelPaint/CloneTool.h | 41 ---- Userland/Applications/PixelPaint/EllipseTool.cpp | 203 ------------------- Userland/Applications/PixelPaint/EllipseTool.h | 55 ------ Userland/Applications/PixelPaint/EraseTool.cpp | 140 -------------- Userland/Applications/PixelPaint/EraseTool.h | 39 ---- Userland/Applications/PixelPaint/GuideTool.cpp | 215 --------------------- Userland/Applications/PixelPaint/GuideTool.h | 43 ----- Userland/Applications/PixelPaint/ImageEditor.cpp | 4 +- Userland/Applications/PixelPaint/LineTool.cpp | 153 --------------- Userland/Applications/PixelPaint/LineTool.h | 38 ---- Userland/Applications/PixelPaint/MainWidget.h | 2 +- Userland/Applications/PixelPaint/MoveTool.cpp | 122 ------------ Userland/Applications/PixelPaint/MoveTool.h | 33 ---- Userland/Applications/PixelPaint/PenTool.cpp | 68 ------- Userland/Applications/PixelPaint/PenTool.h | 31 --- Userland/Applications/PixelPaint/PickerTool.cpp | 63 ------ Userland/Applications/PixelPaint/PickerTool.h | 29 --- .../PixelPaint/RectangleSelectTool.cpp | 212 -------------------- .../Applications/PixelPaint/RectangleSelectTool.h | 48 ----- Userland/Applications/PixelPaint/RectangleTool.cpp | 213 -------------------- Userland/Applications/PixelPaint/RectangleTool.h | 56 ------ Userland/Applications/PixelPaint/SprayTool.cpp | 142 -------------- Userland/Applications/PixelPaint/SprayTool.h | 38 ---- Userland/Applications/PixelPaint/Tool.cpp | 64 ------ Userland/Applications/PixelPaint/Tool.h | 88 --------- .../PixelPaint/ToolPropertiesWidget.cpp | 2 +- Userland/Applications/PixelPaint/ToolboxWidget.cpp | 28 +-- .../Applications/PixelPaint/Tools/BrushTool.cpp | 183 ++++++++++++++++++ Userland/Applications/PixelPaint/Tools/BrushTool.h | 45 +++++ .../Applications/PixelPaint/Tools/BucketTool.cpp | 123 ++++++++++++ .../Applications/PixelPaint/Tools/BucketTool.h | 26 +++ .../Applications/PixelPaint/Tools/CloneTool.cpp | 179 +++++++++++++++++ Userland/Applications/PixelPaint/Tools/CloneTool.h | 41 ++++ .../Applications/PixelPaint/Tools/EllipseTool.cpp | 203 +++++++++++++++++++ .../Applications/PixelPaint/Tools/EllipseTool.h | 55 ++++++ .../Applications/PixelPaint/Tools/EraseTool.cpp | 140 ++++++++++++++ Userland/Applications/PixelPaint/Tools/EraseTool.h | 39 ++++ .../Applications/PixelPaint/Tools/GuideTool.cpp | 214 ++++++++++++++++++++ Userland/Applications/PixelPaint/Tools/GuideTool.h | 43 +++++ .../Applications/PixelPaint/Tools/LineTool.cpp | 153 +++++++++++++++ Userland/Applications/PixelPaint/Tools/LineTool.h | 38 ++++ .../Applications/PixelPaint/Tools/MoveTool.cpp | 121 ++++++++++++ Userland/Applications/PixelPaint/Tools/MoveTool.h | 33 ++++ Userland/Applications/PixelPaint/Tools/PenTool.cpp | 68 +++++++ Userland/Applications/PixelPaint/Tools/PenTool.h | 31 +++ .../Applications/PixelPaint/Tools/PickerTool.cpp | 63 ++++++ .../Applications/PixelPaint/Tools/PickerTool.h | 29 +++ .../PixelPaint/Tools/RectangleSelectTool.cpp | 212 ++++++++++++++++++++ .../PixelPaint/Tools/RectangleSelectTool.h | 48 +++++ .../PixelPaint/Tools/RectangleTool.cpp | 213 ++++++++++++++++++++ .../Applications/PixelPaint/Tools/RectangleTool.h | 56 ++++++ .../Applications/PixelPaint/Tools/SprayTool.cpp | 142 ++++++++++++++ Userland/Applications/PixelPaint/Tools/SprayTool.h | 38 ++++ Userland/Applications/PixelPaint/Tools/Tool.cpp | 64 ++++++ Userland/Applications/PixelPaint/Tools/Tool.h | 88 +++++++++ .../Applications/PixelPaint/Tools/ZoomTool.cpp | 60 ++++++ Userland/Applications/PixelPaint/Tools/ZoomTool.h | 29 +++ Userland/Applications/PixelPaint/ZoomTool.cpp | 60 ------ Userland/Applications/PixelPaint/ZoomTool.h | 29 --- 65 files changed, 2814 insertions(+), 2816 deletions(-) delete mode 100644 Userland/Applications/PixelPaint/BrushTool.cpp delete mode 100644 Userland/Applications/PixelPaint/BrushTool.h delete mode 100644 Userland/Applications/PixelPaint/BucketTool.cpp delete mode 100644 Userland/Applications/PixelPaint/BucketTool.h delete mode 100644 Userland/Applications/PixelPaint/CloneTool.cpp delete mode 100644 Userland/Applications/PixelPaint/CloneTool.h delete mode 100644 Userland/Applications/PixelPaint/EllipseTool.cpp delete mode 100644 Userland/Applications/PixelPaint/EllipseTool.h delete mode 100644 Userland/Applications/PixelPaint/EraseTool.cpp delete mode 100644 Userland/Applications/PixelPaint/EraseTool.h delete mode 100644 Userland/Applications/PixelPaint/GuideTool.cpp delete mode 100644 Userland/Applications/PixelPaint/GuideTool.h delete mode 100644 Userland/Applications/PixelPaint/LineTool.cpp delete mode 100644 Userland/Applications/PixelPaint/LineTool.h delete mode 100644 Userland/Applications/PixelPaint/MoveTool.cpp delete mode 100644 Userland/Applications/PixelPaint/MoveTool.h delete mode 100644 Userland/Applications/PixelPaint/PenTool.cpp delete mode 100644 Userland/Applications/PixelPaint/PenTool.h delete mode 100644 Userland/Applications/PixelPaint/PickerTool.cpp delete mode 100644 Userland/Applications/PixelPaint/PickerTool.h delete mode 100644 Userland/Applications/PixelPaint/RectangleSelectTool.cpp delete mode 100644 Userland/Applications/PixelPaint/RectangleSelectTool.h delete mode 100644 Userland/Applications/PixelPaint/RectangleTool.cpp delete mode 100644 Userland/Applications/PixelPaint/RectangleTool.h delete mode 100644 Userland/Applications/PixelPaint/SprayTool.cpp delete mode 100644 Userland/Applications/PixelPaint/SprayTool.h delete mode 100644 Userland/Applications/PixelPaint/Tool.cpp delete mode 100644 Userland/Applications/PixelPaint/Tool.h create mode 100644 Userland/Applications/PixelPaint/Tools/BrushTool.cpp create mode 100644 Userland/Applications/PixelPaint/Tools/BrushTool.h create mode 100644 Userland/Applications/PixelPaint/Tools/BucketTool.cpp create mode 100644 Userland/Applications/PixelPaint/Tools/BucketTool.h create mode 100644 Userland/Applications/PixelPaint/Tools/CloneTool.cpp create mode 100644 Userland/Applications/PixelPaint/Tools/CloneTool.h create mode 100644 Userland/Applications/PixelPaint/Tools/EllipseTool.cpp create mode 100644 Userland/Applications/PixelPaint/Tools/EllipseTool.h create mode 100644 Userland/Applications/PixelPaint/Tools/EraseTool.cpp create mode 100644 Userland/Applications/PixelPaint/Tools/EraseTool.h create mode 100644 Userland/Applications/PixelPaint/Tools/GuideTool.cpp create mode 100644 Userland/Applications/PixelPaint/Tools/GuideTool.h create mode 100644 Userland/Applications/PixelPaint/Tools/LineTool.cpp create mode 100644 Userland/Applications/PixelPaint/Tools/LineTool.h create mode 100644 Userland/Applications/PixelPaint/Tools/MoveTool.cpp create mode 100644 Userland/Applications/PixelPaint/Tools/MoveTool.h create mode 100644 Userland/Applications/PixelPaint/Tools/PenTool.cpp create mode 100644 Userland/Applications/PixelPaint/Tools/PenTool.h create mode 100644 Userland/Applications/PixelPaint/Tools/PickerTool.cpp create mode 100644 Userland/Applications/PixelPaint/Tools/PickerTool.h create mode 100644 Userland/Applications/PixelPaint/Tools/RectangleSelectTool.cpp create mode 100644 Userland/Applications/PixelPaint/Tools/RectangleSelectTool.h create mode 100644 Userland/Applications/PixelPaint/Tools/RectangleTool.cpp create mode 100644 Userland/Applications/PixelPaint/Tools/RectangleTool.h create mode 100644 Userland/Applications/PixelPaint/Tools/SprayTool.cpp create mode 100644 Userland/Applications/PixelPaint/Tools/SprayTool.h create mode 100644 Userland/Applications/PixelPaint/Tools/Tool.cpp create mode 100644 Userland/Applications/PixelPaint/Tools/Tool.h create mode 100644 Userland/Applications/PixelPaint/Tools/ZoomTool.cpp create mode 100644 Userland/Applications/PixelPaint/Tools/ZoomTool.h delete mode 100644 Userland/Applications/PixelPaint/ZoomTool.cpp delete mode 100644 Userland/Applications/PixelPaint/ZoomTool.h diff --git a/Userland/Applications/PixelPaint/BrushTool.cpp b/Userland/Applications/PixelPaint/BrushTool.cpp deleted file mode 100644 index fa6cea94b3..0000000000 --- a/Userland/Applications/PixelPaint/BrushTool.cpp +++ /dev/null @@ -1,183 +0,0 @@ -/* - * Copyright (c) 2020, Ben Jilks - * Copyright (c) 2021, Mustafa Quraish - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include "BrushTool.h" -#include "ImageEditor.h" -#include "Layer.h" -#include -#include -#include -#include -#include -#include -#include - -namespace PixelPaint { - -BrushTool::BrushTool() -{ -} - -BrushTool::~BrushTool() -{ -} - -void BrushTool::on_mousedown(Layer* layer, MouseEvent& event) -{ - if (!layer) - return; - - auto& layer_event = event.layer_event(); - if (layer_event.button() != GUI::MouseButton::Left && layer_event.button() != GUI::MouseButton::Right) - return; - - // Shift+Click draws a line from the last position to current one. - if (layer_event.shift() && m_has_clicked) { - draw_line(layer->bitmap(), color_for(layer_event), m_last_position, layer_event.position()); - auto modified_rect = Gfx::IntRect::from_two_points(m_last_position, layer_event.position()).inflated(m_size * 2, m_size * 2); - layer->did_modify_bitmap(modified_rect); - m_last_position = layer_event.position(); - return; - } - - const int first_draw_opacity = 10; - - for (int i = 0; i < first_draw_opacity; ++i) - draw_point(layer->bitmap(), color_for(layer_event), layer_event.position()); - - layer->did_modify_bitmap(Gfx::IntRect::centered_on(layer_event.position(), Gfx::IntSize { m_size * 2, m_size * 2 })); - m_last_position = layer_event.position(); - m_has_clicked = true; -} - -void BrushTool::on_mousemove(Layer* layer, MouseEvent& event) -{ - if (!layer) - return; - - auto& layer_event = event.layer_event(); - if (!(layer_event.buttons() & GUI::MouseButton::Left || layer_event.buttons() & GUI::MouseButton::Right)) - return; - - draw_line(layer->bitmap(), color_for(layer_event), m_last_position, layer_event.position()); - - auto modified_rect = Gfx::IntRect::from_two_points(m_last_position, layer_event.position()).inflated(m_size * 2, m_size * 2); - - layer->did_modify_bitmap(modified_rect); - m_last_position = layer_event.position(); - m_was_drawing = true; -} - -void BrushTool::on_mouseup(Layer*, MouseEvent&) -{ - if (m_was_drawing) { - m_editor->did_complete_action(); - m_was_drawing = false; - } -} - -Color BrushTool::color_for(GUI::MouseEvent const& event) -{ - return m_editor->color_for(event); -} - -void BrushTool::draw_point(Gfx::Bitmap& bitmap, Gfx::Color const& color, Gfx::IntPoint const& point) -{ - for (int y = point.y() - size(); y < point.y() + size(); y++) { - for (int x = point.x() - size(); x < point.x() + size(); x++) { - auto distance = point.distance_from({ x, y }); - if (x < 0 || x >= bitmap.width() || y < 0 || y >= bitmap.height()) - continue; - if (distance >= size()) - continue; - - auto falloff = (1.0 - double { distance / size() }) * (1.0 / (100 - hardness())); - auto pixel_color = color; - pixel_color.set_alpha(falloff * 255); - bitmap.set_pixel(x, y, bitmap.get_pixel(x, y).blend(pixel_color)); - } - } -} - -void BrushTool::draw_line(Gfx::Bitmap& bitmap, Gfx::Color const& color, Gfx::IntPoint const& start, Gfx::IntPoint const& end) -{ - int length_x = end.x() - start.x(); - int length_y = end.y() - start.y(); - float y_step = length_y == 0 ? 0 : (float)(length_y) / (float)(length_x); - if (y_step > abs(length_y)) - y_step = abs(length_y); - if (y_step < -abs(length_y)) - y_step = -abs(length_y); - if (y_step == 0 && start.x() == end.x()) - return; - - int start_x = start.x(); - int end_x = end.x(); - int start_y = start.y(); - int end_y = end.y(); - if (start_x > end_x) { - swap(start_x, end_x); - swap(start_y, end_y); - } - - float y = start_y; - for (int x = start_x; x <= end_x; x++) { - int start_step_y = y; - int end_step_y = y + y_step; - if (start_step_y > end_step_y) - swap(start_step_y, end_step_y); - for (int i = start_step_y; i <= end_step_y; i++) - draw_point(bitmap, color, { x, i }); - y += y_step; - } -} - -GUI::Widget* BrushTool::get_properties_widget() -{ - if (!m_properties_widget) { - m_properties_widget = GUI::Widget::construct(); - m_properties_widget->set_layout(); - - auto& size_container = m_properties_widget->add(); - size_container.set_fixed_height(20); - size_container.set_layout(); - - auto& size_label = size_container.add("Size:"); - size_label.set_text_alignment(Gfx::TextAlignment::CenterLeft); - size_label.set_fixed_size(80, 20); - - auto& size_slider = size_container.add(Orientation::Horizontal, "px"); - size_slider.set_range(1, 100); - size_slider.set_value(m_size); - - size_slider.on_change = [&](int value) { - set_size(value); - }; - set_primary_slider(&size_slider); - - auto& hardness_container = m_properties_widget->add(); - hardness_container.set_fixed_height(20); - hardness_container.set_layout(); - - auto& hardness_label = hardness_container.add("Hardness:"); - hardness_label.set_text_alignment(Gfx::TextAlignment::CenterLeft); - hardness_label.set_fixed_size(80, 20); - - auto& hardness_slider = hardness_container.add(Orientation::Horizontal, "%"); - hardness_slider.set_range(1, 99); - hardness_slider.set_value(m_hardness); - - hardness_slider.on_change = [&](int value) { - set_hardness(value); - }; - set_secondary_slider(&hardness_slider); - } - - return m_properties_widget.ptr(); -} - -} diff --git a/Userland/Applications/PixelPaint/BrushTool.h b/Userland/Applications/PixelPaint/BrushTool.h deleted file mode 100644 index c4cca0a04c..0000000000 --- a/Userland/Applications/PixelPaint/BrushTool.h +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (c) 2020, Ben Jilks - * Copyright (c) 2021, Mustafa Quraish - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - -#include "Tool.h" - -namespace PixelPaint { - -class BrushTool : public Tool { -public: - BrushTool(); - virtual ~BrushTool() override; - - virtual void on_mousedown(Layer*, MouseEvent&) override; - virtual void on_mousemove(Layer*, MouseEvent&) override; - virtual void on_mouseup(Layer*, MouseEvent&) override; - virtual GUI::Widget* get_properties_widget() override; - virtual Gfx::StandardCursor cursor() override { return Gfx::StandardCursor::Crosshair; } - - void set_size(int size) { m_size = size; } - int size() const { return m_size; } - - void set_hardness(int hardness) { m_hardness = hardness; } - int hardness() const { return m_hardness; } - -protected: - virtual Color color_for(GUI::MouseEvent const& event); - virtual void draw_point(Gfx::Bitmap& bitmap, Gfx::Color const& color, Gfx::IntPoint const& point); - virtual void draw_line(Gfx::Bitmap& bitmap, Gfx::Color const& color, Gfx::IntPoint const& start, Gfx::IntPoint const& end); - -private: - RefPtr m_properties_widget; - int m_size { 20 }; - int m_hardness { 80 }; - bool m_was_drawing { false }; - bool m_has_clicked { false }; - Gfx::IntPoint m_last_position; -}; - -} diff --git a/Userland/Applications/PixelPaint/BucketTool.cpp b/Userland/Applications/PixelPaint/BucketTool.cpp deleted file mode 100644 index cf255603fb..0000000000 --- a/Userland/Applications/PixelPaint/BucketTool.cpp +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include "BucketTool.h" -#include "ImageEditor.h" -#include "Layer.h" -#include -#include -#include -#include -#include -#include -#include -#include - -namespace PixelPaint { - -BucketTool::BucketTool() -{ -} - -BucketTool::~BucketTool() -{ -} - -static float color_distance_squared(Gfx::Color const& lhs, Gfx::Color const& rhs) -{ - int a = rhs.red() - lhs.red(); - int b = rhs.green() - lhs.green(); - int c = rhs.blue() - lhs.blue(); - return (a * a + b * b + c * c) / (255.0f * 255.0f); -} - -static void flood_fill(Gfx::Bitmap& bitmap, Gfx::IntPoint const& start_position, Color target_color, Color fill_color, int threshold) -{ - VERIFY(bitmap.bpp() == 32); - - if (target_color == fill_color) - return; - - if (!bitmap.rect().contains(start_position)) - return; - - float threshold_normalized_squared = (threshold / 100.0f) * (threshold / 100.0f); - - Queue queue; - queue.enqueue(start_position); - HashTable visited; - while (!queue.is_empty()) { - auto position = queue.dequeue(); - if (visited.contains(position)) - continue; - visited.set(position); - - auto pixel_color = bitmap.get_pixel(position.x(), position.y()); - if (color_distance_squared(pixel_color, target_color) > threshold_normalized_squared) - continue; - - bitmap.set_pixel(position.x(), position.y(), fill_color); - - if (position.x() != 0) - queue.enqueue(position.translated(-1, 0)); - - if (position.x() != bitmap.width() - 1) - queue.enqueue(position.translated(1, 0)); - - if (position.y() != 0) - queue.enqueue(position.translated(0, -1)); - - if (position.y() != bitmap.height() - 1) - queue.enqueue(position.translated(0, 1)); - } -} - -void BucketTool::on_mousedown(Layer* layer, MouseEvent& event) -{ - if (!layer) - return; - - auto& layer_event = event.layer_event(); - if (!layer->rect().contains(layer_event.position())) - return; - - GUI::Painter painter(layer->bitmap()); - auto target_color = layer->bitmap().get_pixel(layer_event.x(), layer_event.y()); - - flood_fill(layer->bitmap(), layer_event.position(), target_color, m_editor->color_for(layer_event), m_threshold); - - layer->did_modify_bitmap(); - m_editor->did_complete_action(); -} - -GUI::Widget* BucketTool::get_properties_widget() -{ - if (!m_properties_widget) { - m_properties_widget = GUI::Widget::construct(); - m_properties_widget->set_layout(); - - auto& threshold_container = m_properties_widget->add(); - threshold_container.set_fixed_height(20); - threshold_container.set_layout(); - - auto& threshold_label = threshold_container.add("Threshold:"); - threshold_label.set_text_alignment(Gfx::TextAlignment::CenterLeft); - threshold_label.set_fixed_size(80, 20); - - auto& threshold_slider = threshold_container.add(Orientation::Horizontal, "%"); - threshold_slider.set_range(0, 100); - threshold_slider.set_value(m_threshold); - - threshold_slider.on_change = [&](int value) { - m_threshold = value; - }; - set_primary_slider(&threshold_slider); - } - - return m_properties_widget.ptr(); -} - -} diff --git a/Userland/Applications/PixelPaint/BucketTool.h b/Userland/Applications/PixelPaint/BucketTool.h deleted file mode 100644 index e9844fc146..0000000000 --- a/Userland/Applications/PixelPaint/BucketTool.h +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - -#include "Tool.h" - -namespace PixelPaint { - -class BucketTool final : public Tool { -public: - BucketTool(); - virtual ~BucketTool() override; - - virtual void on_mousedown(Layer*, MouseEvent&) override; - virtual GUI::Widget* get_properties_widget() override; - -private: - RefPtr m_properties_widget; - int m_threshold { 0 }; -}; - -} diff --git a/Userland/Applications/PixelPaint/CMakeLists.txt b/Userland/Applications/PixelPaint/CMakeLists.txt index 2e77fbc740..84252f41ab 100644 --- a/Userland/Applications/PixelPaint/CMakeLists.txt +++ b/Userland/Applications/PixelPaint/CMakeLists.txt @@ -9,40 +9,40 @@ compile_gml(PixelPaintWindow.gml PixelPaintWindowGML.h pixel_paint_window_gml) compile_gml(EditGuideDialog.gml EditGuideDialogGML.h edit_guide_dialog_gml) set(SOURCES - BrushTool.cpp - BucketTool.cpp - CloneTool.cpp CreateNewImageDialog.cpp CreateNewLayerDialog.cpp EditGuideDialog.cpp EditGuideDialogGML.h - EllipseTool.cpp - EraseTool.cpp - GuideTool.cpp Image.cpp ImageEditor.cpp Layer.cpp LayerListWidget.cpp LayerPropertiesWidget.cpp - LineTool.cpp MainWidget.cpp - main.cpp - MoveTool.cpp + Mask.cpp PaletteWidget.cpp - PenTool.cpp - PickerTool.cpp PixelPaintWindowGML.h ProjectLoader.cpp - RectangleTool.cpp - RectangleSelectTool.cpp - Mask.cpp Selection.cpp - SprayTool.cpp - ToolboxWidget.cpp ToolPropertiesWidget.cpp - Tool.cpp - ZoomTool.cpp -) + ToolboxWidget.cpp + Tools/BrushTool.cpp + Tools/BucketTool.cpp + Tools/CloneTool.cpp + Tools/EllipseTool.cpp + Tools/EraseTool.cpp + Tools/GuideTool.cpp + Tools/LineTool.cpp + Tools/MoveTool.cpp + Tools/PenTool.cpp + Tools/PickerTool.cpp + Tools/RectangleSelectTool.cpp + Tools/RectangleTool.cpp + Tools/SprayTool.cpp + Tools/Tool.cpp + Tools/ZoomTool.cpp + main.cpp + ) serenity_app(PixelPaint ICON app-pixel-paint) target_link_libraries(PixelPaint LibImageDecoderClient LibGUI LibGfx LibFileSystemAccessClient LibConfig) diff --git a/Userland/Applications/PixelPaint/CloneTool.cpp b/Userland/Applications/PixelPaint/CloneTool.cpp deleted file mode 100644 index 831cc23ac5..0000000000 --- a/Userland/Applications/PixelPaint/CloneTool.cpp +++ /dev/null @@ -1,179 +0,0 @@ -/* - * Copyright (c) 2021, Mustafa Quraish - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include "CloneTool.h" -#include "ImageEditor.h" -#include "Layer.h" -#include -#include -#include -#include -#include -#include - -namespace PixelPaint { - -void CloneTool::draw_point(Gfx::Bitmap& bitmap, Gfx::Color const&, Gfx::IntPoint const& point) -{ - if (!m_sample_location.has_value()) - return; - - auto source_point = point - m_cursor_offset.value(); - for (int y = -size(); y < size(); y++) { - for (int x = -size(); x < size(); x++) { - auto target_x = point.x() + x; - auto target_y = point.y() + y; - auto distance = point.distance_from({ target_x, target_y }); - if (target_x < 0 || target_x >= bitmap.width() || target_y < 0 || target_y >= bitmap.height()) - continue; - if (distance >= size()) - continue; - - auto source_x = source_point.x() + x; - auto source_y = source_point.y() + y; - if (source_x < 0 || source_x >= bitmap.width() || source_y < 0 || source_y >= bitmap.height()) - continue; - - auto falloff = (1.0 - double { distance / size() }) * (1.0 / (100 - hardness())); - auto pixel_color = bitmap.get_pixel(source_x, source_y); - pixel_color.set_alpha(falloff * 255); - bitmap.set_pixel(target_x, target_y, bitmap.get_pixel(target_x, target_y).blend(pixel_color)); - } - } -} - -void CloneTool::draw_line(Gfx::Bitmap& bitmap, Gfx::Color const& color, Gfx::IntPoint const& start, Gfx::IntPoint const& end) -{ - if (!m_sample_location.has_value()) - return; - BrushTool::draw_line(bitmap, color, start, end); -} - -Gfx::StandardCursor CloneTool::cursor() -{ - if (m_is_selecting_location) - return Gfx::StandardCursor::Eyedropper; - return Gfx::StandardCursor::Crosshair; -} - -void CloneTool::on_mousemove(Layer* layer, MouseEvent& event) -{ - auto& image_event = event.image_event(); - if (image_event.alt()) - return; - - if (m_cursor_offset.has_value()) { - m_sample_location = image_event.position() - m_cursor_offset.value(); - // FIXME: This is a really inefficient way to update the marker's location - m_editor->update(); - } - - BrushTool::on_mousemove(layer, event); -} - -void CloneTool::on_mousedown(Layer* layer, MouseEvent& event) -{ - auto& image_event = event.image_event(); - if (image_event.alt()) { - m_sample_location = image_event.position(); - m_cursor_offset = {}; - // FIXME: This is a really dumb way to get the marker to show up - m_editor->update(); - return; - } - - if (!m_sample_location.has_value()) - return; - - if (!m_cursor_offset.has_value()) - m_cursor_offset = event.image_event().position() - m_sample_location.value(); - - BrushTool::on_mousedown(layer, event); -} - -void CloneTool::on_second_paint(Layer const*, GUI::PaintEvent& event) -{ - if (!m_sample_location.has_value()) - return; - - GUI::Painter painter(*m_editor); - painter.add_clip_rect(event.rect()); - - auto sample_pos = m_editor->image_position_to_editor_position(m_sample_location.value()); - // We don't want the marker to be a single pixel and hide the color. - auto offset = AK::max(2, size() / 2); - Gfx::IntRect rect = { - (int)sample_pos.x() - offset, - (int)sample_pos.y() - offset, - offset * 2, - offset * 2 - }; - painter.draw_ellipse_intersecting(rect, m_marker_color, 1); -} - -void CloneTool::on_keydown(GUI::KeyEvent& event) -{ - Tool::on_keydown(event); - if (event.key() == KeyCode::Key_Alt && !m_is_selecting_location) { - m_is_selecting_location = true; - m_editor->update_tool_cursor(); - return; - } -} - -void CloneTool::on_keyup(GUI::KeyEvent& event) -{ - if (m_is_selecting_location && event.key() == KeyCode::Key_Alt) { - m_is_selecting_location = false; - m_editor->update_tool_cursor(); - return; - } -} - -GUI::Widget* CloneTool::get_properties_widget() -{ - if (!m_properties_widget) { - m_properties_widget = GUI::Widget::construct(); - m_properties_widget->set_layout(); - - auto& size_container = m_properties_widget->add(); - size_container.set_fixed_height(20); - size_container.set_layout(); - - auto& size_label = size_container.add("Size:"); - size_label.set_text_alignment(Gfx::TextAlignment::CenterLeft); - size_label.set_fixed_size(80, 20); - - auto& size_slider = size_container.add(Orientation::Horizontal, "px"); - size_slider.set_range(1, 100); - size_slider.set_value(size()); - - size_slider.on_change = [&](int value) { - set_size(value); - }; - set_primary_slider(&size_slider); - - auto& hardness_container = m_properties_widget->add(); - hardness_container.set_fixed_height(20); - hardness_container.set_layout(); - - auto& hardness_label = hardness_container.add("Hardness:"); - hardness_label.set_text_alignment(Gfx::TextAlignment::CenterLeft); - hardness_label.set_fixed_size(80, 20); - - auto& hardness_slider = hardness_container.add(Orientation::Horizontal, "%"); - hardness_slider.set_range(1, 99); - hardness_slider.on_change = [&](int value) { - set_hardness(value); - }; - hardness_slider.set_value(99); - set_secondary_slider(&hardness_slider); - } - - return m_properties_widget.ptr(); -} - -} diff --git a/Userland/Applications/PixelPaint/CloneTool.h b/Userland/Applications/PixelPaint/CloneTool.h deleted file mode 100644 index 100ac1aec9..0000000000 --- a/Userland/Applications/PixelPaint/CloneTool.h +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (c) 2021, Mustafa Quraish - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - -#include "BrushTool.h" - -namespace PixelPaint { - -class CloneTool : public BrushTool { -public: - CloneTool() = default; - virtual ~CloneTool() override = default; - - virtual GUI::Widget* get_properties_widget() override; - virtual Gfx::StandardCursor cursor() override; - -protected: - virtual void draw_point(Gfx::Bitmap& bitmap, Gfx::Color const& color, Gfx::IntPoint const& point) override; - virtual void draw_line(Gfx::Bitmap& bitmap, Gfx::Color const& color, Gfx::IntPoint const& start, Gfx::IntPoint const& end) override; - - virtual void on_mousedown(Layer*, MouseEvent&) override; - virtual void on_mousemove(Layer*, MouseEvent&) override; - virtual void on_second_paint(Layer const*, GUI::PaintEvent&) override; - virtual void on_keydown(GUI::KeyEvent&) override; - virtual void on_keyup(GUI::KeyEvent&) override; - -private: - RefPtr m_properties_widget; - - Optional m_sample_location; - Optional m_cursor_offset; - bool m_is_selecting_location { false }; - - Gfx::Color m_marker_color { Gfx::Color::Green }; -}; - -} diff --git a/Userland/Applications/PixelPaint/EllipseTool.cpp b/Userland/Applications/PixelPaint/EllipseTool.cpp deleted file mode 100644 index 03fdea3a17..0000000000 --- a/Userland/Applications/PixelPaint/EllipseTool.cpp +++ /dev/null @@ -1,203 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling - * Copyright (c) 2021, Mustafa Quraish - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include "EllipseTool.h" -#include "ImageEditor.h" -#include "Layer.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace PixelPaint { - -EllipseTool::EllipseTool() -{ -} - -EllipseTool::~EllipseTool() -{ -} - -void EllipseTool::draw_using(GUI::Painter& painter, Gfx::IntPoint const& start_position, Gfx::IntPoint const& end_position, int thickness) -{ - Gfx::IntRect ellipse_intersecting_rect; - if (m_draw_mode == DrawMode::FromCenter) { - auto delta = end_position - start_position; - ellipse_intersecting_rect = Gfx::IntRect::from_two_points(start_position - delta, end_position); - } else { - ellipse_intersecting_rect = Gfx::IntRect::from_two_points(start_position, end_position); - } - - switch (m_fill_mode) { - case FillMode::Outline: - painter.draw_ellipse_intersecting(ellipse_intersecting_rect, m_editor->color_for(m_drawing_button), thickness); - break; - case FillMode::Fill: - painter.fill_ellipse(ellipse_intersecting_rect, m_editor->color_for(m_drawing_button)); - break; - default: - VERIFY_NOT_REACHED(); - } -} - -void EllipseTool::on_mousedown(Layer* layer, MouseEvent& event) -{ - if (!layer) - return; - - auto& layer_event = event.layer_event(); - if (layer_event.button() != GUI::MouseButton::Left && layer_event.button() != GUI::MouseButton::Right) - return; - - if (m_drawing_button != GUI::MouseButton::None) - return; - - m_drawing_button = layer_event.button(); - m_ellipse_start_position = layer_event.position(); - m_ellipse_end_position = layer_event.position(); - m_editor->update(); -} - -void EllipseTool::on_mouseup(Layer* layer, MouseEvent& event) -{ - if (!layer) - return; - - if (event.layer_event().button() == m_drawing_button) { - GUI::Painter painter(layer->bitmap()); - draw_using(painter, m_ellipse_start_position, m_ellipse_end_position, m_thickness); - m_drawing_button = GUI::MouseButton::None; - layer->did_modify_bitmap(); - m_editor->update(); - m_editor->did_complete_action(); - } -} - -void EllipseTool::on_mousemove(Layer*, MouseEvent& event) -{ - if (m_drawing_button == GUI::MouseButton::None) - return; - - m_draw_mode = event.layer_event().alt() ? DrawMode::FromCenter : DrawMode::FromCorner; - - if (event.layer_event().shift()) - m_ellipse_end_position = m_ellipse_start_position.end_point_for_aspect_ratio(event.layer_event().position(), 1.0); - else if (m_aspect_ratio.has_value()) - m_ellipse_end_position = m_ellipse_start_position.end_point_for_aspect_ratio(event.layer_event().position(), m_aspect_ratio.value()); - else - m_ellipse_end_position = event.layer_event().position(); - - m_editor->update(); -} - -void EllipseTool::on_second_paint(Layer const* layer, GUI::PaintEvent& event) -{ - if (!layer || m_drawing_button == GUI::MouseButton::None) - return; - - GUI::Painter painter(*m_editor); - painter.add_clip_rect(event.rect()); - auto preview_start = m_editor->layer_position_to_editor_position(*layer, m_ellipse_start_position).to_type(); - auto preview_end = m_editor->layer_position_to_editor_position(*layer, m_ellipse_end_position).to_type(); - draw_using(painter, preview_start, preview_end, AK::max(m_thickness * m_editor->scale(), 1)); -} - -void EllipseTool::on_keydown(GUI::KeyEvent& event) -{ - Tool::on_keydown(event); - if (event.key() == Key_Escape && m_drawing_button != GUI::MouseButton::None) { - m_drawing_button = GUI::MouseButton::None; - m_editor->update(); - event.accept(); - } -} - -GUI::Widget* EllipseTool::get_properties_widget() -{ - if (!m_properties_widget) { - m_properties_widget = GUI::Widget::construct(); - m_properties_widget->set_layout(); - - auto& thickness_container = m_properties_widget->add(); - thickness_container.set_fixed_height(20); - thickness_container.set_layout(); - - auto& thickness_label = thickness_container.add("Thickness:"); - thickness_label.set_text_alignment(Gfx::TextAlignment::CenterLeft); - thickness_label.set_fixed_size(80, 20); - - auto& thickness_slider = thickness_container.add(Orientation::Horizontal, "px"); - thickness_slider.set_range(1, 10); - thickness_slider.set_value(m_thickness); - - thickness_slider.on_change = [&](int value) { - m_thickness = value; - }; - set_primary_slider(&thickness_slider); - - auto& mode_container = m_properties_widget->add(); - mode_container.set_fixed_height(46); - mode_container.set_layout(); - auto& mode_label = mode_container.add("Mode:"); - mode_label.set_text_alignment(Gfx::TextAlignment::CenterLeft); - mode_label.set_fixed_size(80, 20); - - auto& mode_radio_container = mode_container.add(); - mode_radio_container.set_layout(); - auto& outline_mode_radio = mode_radio_container.add("Outline"); - auto& fill_mode_radio = mode_radio_container.add("Fill"); - - outline_mode_radio.on_checked = [&](bool) { - m_fill_mode = FillMode::Outline; - }; - fill_mode_radio.on_checked = [&](bool) { - m_fill_mode = FillMode::Fill; - }; - - outline_mode_radio.set_checked(true); - - auto& aspect_container = m_properties_widget->add(); - aspect_container.set_fixed_height(20); - aspect_container.set_layout(); - - auto& aspect_label = aspect_container.add("Aspect Ratio:"); - aspect_label.set_text_alignment(Gfx::TextAlignment::CenterLeft); - aspect_label.set_fixed_size(80, 20); - - m_aspect_w_textbox = aspect_container.add(); - m_aspect_w_textbox->set_fixed_height(20); - m_aspect_w_textbox->set_fixed_width(25); - m_aspect_w_textbox->on_change = [&] { - auto x = m_aspect_w_textbox->text().to_int().value_or(0); - auto y = m_aspect_h_textbox->text().to_int().value_or(0); - if (x > 0 && y > 0) { - m_aspect_ratio = (float)x / (float)y; - } else { - m_aspect_ratio = {}; - } - }; - - auto& multiply_label = aspect_container.add("x"); - multiply_label.set_text_alignment(Gfx::TextAlignment::Center); - multiply_label.set_fixed_size(10, 20); - - m_aspect_h_textbox = aspect_container.add(); - m_aspect_h_textbox->set_fixed_height(20); - m_aspect_h_textbox->set_fixed_width(25); - m_aspect_h_textbox->on_change = [&] { m_aspect_w_textbox->on_change(); }; - } - - return m_properties_widget.ptr(); -} - -} diff --git a/Userland/Applications/PixelPaint/EllipseTool.h b/Userland/Applications/PixelPaint/EllipseTool.h deleted file mode 100644 index 885ca2e781..0000000000 --- a/Userland/Applications/PixelPaint/EllipseTool.h +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling - * Copyright (c) 2021, Mustafa Quraish - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - -#include "Tool.h" -#include -#include - -namespace PixelPaint { - -class EllipseTool final : public Tool { -public: - EllipseTool(); - virtual ~EllipseTool() override; - - virtual void on_mousedown(Layer*, MouseEvent&) override; - virtual void on_mousemove(Layer*, MouseEvent&) override; - virtual void on_mouseup(Layer*, MouseEvent&) override; - virtual void on_second_paint(Layer const*, GUI::PaintEvent&) override; - virtual void on_keydown(GUI::KeyEvent&) override; - virtual GUI::Widget* get_properties_widget() override; - virtual Gfx::StandardCursor cursor() override { return Gfx::StandardCursor::Crosshair; } - -private: - enum class FillMode { - Outline, - Fill, - }; - - enum class DrawMode { - FromCenter, - FromCorner, - }; - - void draw_using(GUI::Painter&, Gfx::IntPoint const& start_position, Gfx::IntPoint const& end_position, int thickness); - - RefPtr m_properties_widget; - RefPtr m_aspect_w_textbox; - RefPtr m_aspect_h_textbox; - - GUI::MouseButton m_drawing_button { GUI::MouseButton::None }; - Gfx::IntPoint m_ellipse_start_position; - Gfx::IntPoint m_ellipse_end_position; - int m_thickness { 1 }; - FillMode m_fill_mode { FillMode::Outline }; - DrawMode m_draw_mode { DrawMode::FromCorner }; - Optional m_aspect_ratio; -}; - -} diff --git a/Userland/Applications/PixelPaint/EraseTool.cpp b/Userland/Applications/PixelPaint/EraseTool.cpp deleted file mode 100644 index 8bfd5f365f..0000000000 --- a/Userland/Applications/PixelPaint/EraseTool.cpp +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling - * Copyright (c) 2021, Mustafa Quraish - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include "EraseTool.h" -#include "ImageEditor.h" -#include "Layer.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace PixelPaint { - -EraseTool::EraseTool() -{ -} - -EraseTool::~EraseTool() -{ -} - -Color EraseTool::color_for(GUI::MouseEvent const&) -{ - if (m_use_secondary_color) - return m_editor->secondary_color(); - return Color(255, 255, 255, 0); -} - -void EraseTool::draw_point(Gfx::Bitmap& bitmap, Gfx::Color const& color, Gfx::IntPoint const& point) -{ - if (m_draw_mode == DrawMode::Pencil) { - int radius = size() / 2; - Gfx::IntRect rect { point.x() - radius, point.y() - radius, size(), size() }; - GUI::Painter painter(bitmap); - painter.clear_rect(rect, color); - } else { - for (int y = point.y() - size(); y < point.y() + size(); y++) { - for (int x = point.x() - size(); x < point.x() + size(); x++) { - auto distance = point.distance_from({ x, y }); - if (x < 0 || x >= bitmap.width() || y < 0 || y >= bitmap.height()) - continue; - if (distance >= size()) - continue; - auto old_color = bitmap.get_pixel(x, y); - auto falloff = (1.0 - double { distance / size() }) * (1.0 / (100 - hardness())); - auto new_color = old_color.interpolate(color, falloff); - bitmap.set_pixel(x, y, new_color); - } - } - } -} - -GUI::Widget* EraseTool::get_properties_widget() -{ - if (!m_properties_widget) { - m_properties_widget = GUI::Widget::construct(); - m_properties_widget->set_layout(); - - auto& size_container = m_properties_widget->add(); - size_container.set_fixed_height(20); - size_container.set_layout(); - - auto& size_label = size_container.add("Size:"); - size_label.set_text_alignment(Gfx::TextAlignment::CenterLeft); - size_label.set_fixed_size(80, 20); - - auto& size_slider = size_container.add(Orientation::Horizontal, "px"); - size_slider.set_range(1, 100); - size_slider.set_value(size()); - - size_slider.on_change = [&](int value) { - set_size(value); - }; - set_primary_slider(&size_slider); - - auto& hardness_container = m_properties_widget->add(); - hardness_container.set_fixed_height(20); - hardness_container.set_layout(); - - auto& hardness_label = hardness_container.add("Hardness:"); - hardness_label.set_text_alignment(Gfx::TextAlignment::CenterLeft); - hardness_label.set_fixed_size(80, 20); - - auto& hardness_slider = hardness_container.add(Orientation::Horizontal, "%"); - hardness_slider.set_range(1, 99); - hardness_slider.set_value(hardness()); - - hardness_slider.on_change = [&](int value) { - set_hardness(value); - }; - set_secondary_slider(&hardness_slider); - - auto& secondary_color_container = m_properties_widget->add(); - secondary_color_container.set_fixed_height(20); - secondary_color_container.set_layout(); - - auto& use_secondary_color_checkbox = secondary_color_container.add(); - use_secondary_color_checkbox.set_checked(m_use_secondary_color); - use_secondary_color_checkbox.set_text("Use secondary color"); - use_secondary_color_checkbox.on_checked = [&](bool checked) { - m_use_secondary_color = checked; - }; - - auto& mode_container = m_properties_widget->add(); - mode_container.set_fixed_height(46); - mode_container.set_layout(); - auto& mode_label = mode_container.add("Draw Mode:"); - mode_label.set_text_alignment(Gfx::TextAlignment::CenterLeft); - mode_label.set_fixed_size(80, 20); - - auto& mode_radio_container = mode_container.add(); - mode_radio_container.set_layout(); - auto& pencil_mode_radio = mode_radio_container.add("Pencil"); - auto& brush_mode_radio = mode_radio_container.add("Brush"); - - pencil_mode_radio.on_checked = [&](bool) { - m_draw_mode = DrawMode::Pencil; - hardness_slider.set_enabled(false); - }; - brush_mode_radio.on_checked = [&](bool) { - m_draw_mode = DrawMode::Brush; - hardness_slider.set_enabled(true); - }; - - pencil_mode_radio.set_checked(true); - } - - return m_properties_widget.ptr(); -} - -} diff --git a/Userland/Applications/PixelPaint/EraseTool.h b/Userland/Applications/PixelPaint/EraseTool.h deleted file mode 100644 index 6fbf048326..0000000000 --- a/Userland/Applications/PixelPaint/EraseTool.h +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling - * Copyright (c) 2021, Mustafa Quraish - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - -#include "BrushTool.h" -#include -#include -#include - -namespace PixelPaint { - -class EraseTool final : public BrushTool { -public: - EraseTool(); - virtual ~EraseTool() override; - - virtual GUI::Widget* get_properties_widget() override; - -protected: - virtual Color color_for(GUI::MouseEvent const& event) override; - virtual void draw_point(Gfx::Bitmap& bitmap, Gfx::Color const& color, Gfx::IntPoint const& point) override; - -private: - RefPtr m_properties_widget; - - enum class DrawMode { - Pencil, - Brush, - }; - DrawMode m_draw_mode { DrawMode::Brush }; - bool m_use_secondary_color { false }; -}; - -} diff --git a/Userland/Applications/PixelPaint/GuideTool.cpp b/Userland/Applications/PixelPaint/GuideTool.cpp deleted file mode 100644 index 2fee6f6b74..0000000000 --- a/Userland/Applications/PixelPaint/GuideTool.cpp +++ /dev/null @@ -1,215 +0,0 @@ -/* - * Copyright (c) 2021, Tobias Christiansen - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include "GuideTool.h" -#include "EditGuideDialog.h" -#include "ImageEditor.h" -#include -#include -#include -#include -#include -#include - -namespace PixelPaint { - -GuideTool::GuideTool() -{ -} - -GuideTool::~GuideTool() -{ -} - -RefPtr GuideTool::closest_guide(const Gfx::IntPoint& point) -{ - auto guides = editor()->guides(); - Guide* closest_guide = nullptr; - int closest_guide_distance = NumericLimits::max(); - - for (auto& guide : guides) { - int relevant_position = 0; - if (guide.orientation() == Guide::Orientation::Horizontal) - relevant_position = point.y(); - else if (guide.orientation() == Guide::Orientation::Vertical) - relevant_position = point.x(); - - auto distance = abs(relevant_position - (int)guide.offset()); - - if (distance < closest_guide_distance) { - closest_guide = &guide; - closest_guide_distance = distance; - } - } - - if (closest_guide_distance < 20) - return closest_guide; - return nullptr; -} - -void GuideTool::on_mousedown(Layer*, MouseEvent& event) -{ - if (!m_editor) - return; - - auto& image_event = event.image_event(); - - if (image_event.button() != GUI::MouseButton::Left) - return; - - m_editor->set_guide_visibility(true); - - RefPtr new_guide; - if (image_event.position().x() < 0 || image_event.position().x() > editor()->image().size().width()) { - new_guide = make_ref_counted(Guide::Orientation::Vertical, image_event.position().x()); - } else if (image_event.position().y() < 0 || image_event.position().y() > editor()->image().size().height()) { - new_guide = make_ref_counted(Guide::Orientation::Horizontal, image_event.position().y()); - } - - if (new_guide) { - m_selected_guide = new_guide; - m_guide_origin = 0; - editor()->add_guide(new_guide.release_nonnull()); - return; - } - - m_event_origin = image_event.position(); - - m_selected_guide = closest_guide(image_event.position()); - - if (m_selected_guide) { - m_guide_origin = m_selected_guide->offset(); - GUI::Application::the()->show_tooltip_immediately(String::formatted("{}", m_guide_origin), GUI::Application::the()->tooltip_source_widget()); - } -} - -void GuideTool::on_mouseup(Layer*, MouseEvent&) -{ - m_guide_origin = 0; - m_event_origin = { 0, 0 }; - GUI::Application::the()->hide_tooltip(); - - if (!m_selected_guide) - return; - - if (m_selected_guide->offset() < 0 - || (m_selected_guide->orientation() == Guide::Orientation::Horizontal && m_selected_guide->offset() > editor()->image().size().height()) - || (m_selected_guide->orientation() == Guide::Orientation::Vertical && m_selected_guide->offset() > editor()->image().size().width())) { - editor()->remove_guide(*m_selected_guide); - editor()->layers_did_change(); - } - - m_selected_guide = nullptr; -} - -void GuideTool::on_mousemove(Layer*, MouseEvent& event) -{ - if (!m_selected_guide) - return; - - auto& image_event = event.image_event(); - auto delta = image_event.position() - m_event_origin; - - auto relevant_offset = 0; - if (m_selected_guide->orientation() == Guide::Orientation::Horizontal) - relevant_offset = delta.y(); - else if (m_selected_guide->orientation() == Guide::Orientation::Vertical) - relevant_offset = delta.x(); - - auto new_offset = (float)relevant_offset + m_guide_origin; - - if (image_event.shift() && m_snap_size > 0) { - float snap_size_half = m_snap_size / 2.0; - new_offset -= fmodf(new_offset + snap_size_half, m_snap_size) - snap_size_half; - } - - m_selected_guide->set_offset(new_offset); - - GUI::Application::the()->show_tooltip_immediately(String::formatted("{}", new_offset), GUI::Application::the()->tooltip_source_widget()); - - editor()->layers_did_change(); -} - -void GuideTool::on_context_menu(Layer*, GUI::ContextMenuEvent& event) -{ - if (!m_editor) - return; - - m_editor->set_guide_visibility(true); - - if (!m_context_menu) { - m_context_menu = GUI::Menu::construct(); - m_context_menu->add_action(GUI::Action::create( - "Set &Offset", Gfx::Bitmap::try_load_from_file("/res/icons/16x16/gear.png"), [this](auto&) { - if (!m_context_menu_guide) - return; - auto dialog = EditGuideDialog::construct( - editor()->window(), - String::formatted("{}", m_context_menu_guide->offset()), - m_context_menu_guide->orientation()); - if (dialog->exec() != GUI::Dialog::ExecOK) - return; - auto offset = dialog->offset_as_pixel(*editor()); - if (!offset.has_value()) - return; - m_context_menu_guide->set_offset(offset.release_value()); - m_context_menu_guide->set_orientation(dialog->orientation()); - editor()->layers_did_change(); - }, - editor())); - m_context_menu->add_action(GUI::Action::create( - "&Delete Guide", Gfx::Bitmap::try_load_from_file("/res/icons/16x16/delete.png"), [this](auto&) { - if (!m_context_menu_guide) - return; - editor()->remove_guide(*m_context_menu_guide); - m_selected_guide = nullptr; - m_guide_origin = 0; - editor()->layers_did_change(); - }, - editor())); - } - - auto image_position = editor()->editor_position_to_image_position(event.position()); - m_context_menu_guide = closest_guide({ (int)image_position.x(), (int)image_position.y() }); - if (m_context_menu_guide) - m_context_menu->popup(event.screen_position()); -} - -void GuideTool::on_tool_activation() -{ - if (m_editor) - m_editor->set_guide_visibility(true); -} - -GUI::Widget* GuideTool::get_properties_widget() -{ - if (!m_properties_widget) { - m_properties_widget = GUI::Widget::construct(); - m_properties_widget->set_layout(); - - auto& snapping_container = m_properties_widget->add(); - snapping_container.set_fixed_height(20); - snapping_container.set_layout(); - - auto& snapping_label = snapping_container.add("Snap offset:"); - snapping_label.set_text_alignment(Gfx::TextAlignment::CenterLeft); - snapping_label.set_fixed_size(80, 20); - snapping_label.set_tooltip("Press Shift to snap"); - - auto& snapping_slider = snapping_container.add(Orientation::Horizontal, "px"); - snapping_slider.set_range(0, 50); - snapping_slider.set_value(m_snap_size); - - snapping_slider.on_change = [&](int value) { - m_snap_size = value; - }; - set_primary_slider(&snapping_slider); - } - - return m_properties_widget.ptr(); -} - -} diff --git a/Userland/Applications/PixelPaint/GuideTool.h b/Userland/Applications/PixelPaint/GuideTool.h deleted file mode 100644 index eefa2a14bc..0000000000 --- a/Userland/Applications/PixelPaint/GuideTool.h +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (c) 2021, Tobias Christiansen - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - -#include "Guide.h" -#include "Tool.h" -#include - -namespace PixelPaint { - -class GuideTool final : public Tool { -public: - GuideTool(); - - virtual ~GuideTool() override; - - virtual void on_mousedown(Layer*, MouseEvent&) override; - virtual void on_mousemove(Layer*, MouseEvent&) override; - virtual void on_mouseup(Layer*, MouseEvent&) override; - virtual void on_context_menu(Layer*, GUI::ContextMenuEvent&) override; - - virtual void on_tool_activation() override; - - virtual GUI::Widget* get_properties_widget() override; - virtual Gfx::StandardCursor cursor() override { return Gfx::StandardCursor::Crosshair; } - -private: - RefPtr closest_guide(Gfx::IntPoint const&); - - RefPtr m_properties_widget; - - RefPtr m_selected_guide; - RefPtr m_context_menu_guide; - Gfx::IntPoint m_event_origin; - float m_guide_origin { 0 }; - RefPtr m_context_menu; - int m_snap_size { 10 }; -}; -} diff --git a/Userland/Applications/PixelPaint/ImageEditor.cpp b/Userland/Applications/PixelPaint/ImageEditor.cpp index 9f328f32ea..b4584ef4b4 100644 --- a/Userland/Applications/PixelPaint/ImageEditor.cpp +++ b/Userland/Applications/PixelPaint/ImageEditor.cpp @@ -10,8 +10,8 @@ #include "ImageEditor.h" #include "Image.h" #include "Layer.h" -#include "MoveTool.h" -#include "Tool.h" +#include "Tools/MoveTool.h" +#include "Tools/Tool.h" #include #include #include diff --git a/Userland/Applications/PixelPaint/LineTool.cpp b/Userland/Applications/PixelPaint/LineTool.cpp deleted file mode 100644 index 0810712652..0000000000 --- a/Userland/Applications/PixelPaint/LineTool.cpp +++ /dev/null @@ -1,153 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling - * Copyright (c) 2021, Mustafa Quraish - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include "LineTool.h" -#include "ImageEditor.h" -#include "Layer.h" -#include -#include -#include -#include -#include -#include -#include - -namespace PixelPaint { - -static Gfx::IntPoint constrain_line_angle(Gfx::IntPoint const& start_pos, Gfx::IntPoint const& end_pos, float angle_increment) -{ - float current_angle = AK::atan2(end_pos.y() - start_pos.y(), end_pos.x() - start_pos.x()) + float { M_PI * 2 }; - - float constrained_angle = ((int)((current_angle + angle_increment / 2) / angle_increment)) * angle_increment; - - auto diff = end_pos - start_pos; - float line_length = AK::hypot(diff.x(), diff.y()); - - return { start_pos.x() + (int)(AK::cos(constrained_angle) * line_length), - start_pos.y() + (int)(AK::sin(constrained_angle) * line_length) }; -} - -LineTool::LineTool() -{ -} - -LineTool::~LineTool() -{ -} - -void LineTool::on_mousedown(Layer* layer, MouseEvent& event) -{ - if (!layer) - return; - - auto& layer_event = event.layer_event(); - if (layer_event.button() != GUI::MouseButton::Left && layer_event.button() != GUI::MouseButton::Right) - return; - - if (m_drawing_button != GUI::MouseButton::None) - return; - - m_drawing_button = layer_event.button(); - - m_drag_start_position = layer_event.position(); - m_line_start_position = layer_event.position(); - m_line_end_position = layer_event.position(); - - m_editor->update(); -} - -void LineTool::on_mouseup(Layer* layer, MouseEvent& event) -{ - if (!layer) - return; - - auto& layer_event = event.layer_event(); - if (layer_event.button() == m_drawing_button) { - GUI::Painter painter(layer->bitmap()); - painter.draw_line(m_line_start_position, m_line_end_position, m_editor->color_for(m_drawing_button), m_thickness); - m_drawing_button = GUI::MouseButton::None; - layer->did_modify_bitmap(); - m_editor->update(); - m_editor->did_complete_action(); - } -} - -void LineTool::on_mousemove(Layer* layer, MouseEvent& event) -{ - if (!layer) - return; - - auto& layer_event = event.layer_event(); - if (m_drawing_button == GUI::MouseButton::None) - return; - - if (layer_event.shift()) { - constexpr auto ANGLE_STEP = M_PI / 8; - m_line_end_position = constrain_line_angle(m_drag_start_position, layer_event.position(), ANGLE_STEP); - } else { - m_line_end_position = layer_event.position(); - } - - if (layer_event.alt()) { - m_line_start_position = m_drag_start_position + (m_drag_start_position - m_line_end_position); - } else { - m_line_start_position = m_drag_start_position; - } - - m_editor->update(); -} - -void LineTool::on_second_paint(Layer const* layer, GUI::PaintEvent& event) -{ - if (!layer || m_drawing_button == GUI::MouseButton::None) - return; - - GUI::Painter painter(*m_editor); - painter.add_clip_rect(event.rect()); - auto preview_start = editor_stroke_position(m_line_start_position, m_thickness); - auto preview_end = editor_stroke_position(m_line_end_position, m_thickness); - painter.draw_line(preview_start, preview_end, m_editor->color_for(m_drawing_button), AK::max(m_thickness * m_editor->scale(), 1)); -} - -void LineTool::on_keydown(GUI::KeyEvent& event) -{ - Tool::on_keydown(event); - if (event.key() == Key_Escape && m_drawing_button != GUI::MouseButton::None) { - m_drawing_button = GUI::MouseButton::None; - m_editor->update(); - event.accept(); - } -} - -GUI::Widget* LineTool::get_properties_widget() -{ - if (!m_properties_widget) { - m_properties_widget = GUI::Widget::construct(); - m_properties_widget->set_layout(); - - auto& thickness_container = m_properties_widget->add(); - thickness_container.set_fixed_height(20); - thickness_container.set_layout(); - - auto& thickness_label = thickness_container.add("Thickness:"); - thickness_label.set_text_alignment(Gfx::TextAlignment::CenterLeft); - thickness_label.set_fixed_size(80, 20); - - auto& thickness_slider = thickness_container.add(Orientation::Horizontal, "px"); - thickness_slider.set_range(1, 10); - thickness_slider.set_value(m_thickness); - - thickness_slider.on_change = [&](int value) { - m_thickness = value; - }; - set_primary_slider(&thickness_slider); - } - - return m_properties_widget.ptr(); -} - -} diff --git a/Userland/Applications/PixelPaint/LineTool.h b/Userland/Applications/PixelPaint/LineTool.h deleted file mode 100644 index fc99d07b1b..0000000000 --- a/Userland/Applications/PixelPaint/LineTool.h +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - -#include "Tool.h" -#include -#include - -namespace PixelPaint { - -class LineTool final : public Tool { -public: - LineTool(); - virtual ~LineTool() override; - - virtual void on_mousedown(Layer*, MouseEvent&) override; - virtual void on_mousemove(Layer*, MouseEvent&) override; - virtual void on_mouseup(Layer*, MouseEvent&) override; - virtual void on_second_paint(Layer const*, GUI::PaintEvent&) override; - virtual void on_keydown(GUI::KeyEvent&) override; - virtual GUI::Widget* get_properties_widget() override; - virtual Gfx::StandardCursor cursor() override { return Gfx::StandardCursor::Crosshair; } - -private: - RefPtr m_properties_widget; - - GUI::MouseButton m_drawing_button { GUI::MouseButton::None }; - Gfx::IntPoint m_drag_start_position; - Gfx::IntPoint m_line_start_position; - Gfx::IntPoint m_line_end_position; - int m_thickness { 1 }; -}; - -} diff --git a/Userland/Applications/PixelPaint/MainWidget.h b/Userland/Applications/PixelPaint/MainWidget.h index 82e74c183b..58cf0e2f26 100644 --- a/Userland/Applications/PixelPaint/MainWidget.h +++ b/Userland/Applications/PixelPaint/MainWidget.h @@ -14,9 +14,9 @@ #include "LayerPropertiesWidget.h" #include "PaletteWidget.h" #include "ProjectLoader.h" -#include "Tool.h" #include "ToolPropertiesWidget.h" #include "ToolboxWidget.h" +#include "Tools/Tool.h" #include #include #include diff --git a/Userland/Applications/PixelPaint/MoveTool.cpp b/Userland/Applications/PixelPaint/MoveTool.cpp deleted file mode 100644 index 59bcbb578a..0000000000 --- a/Userland/Applications/PixelPaint/MoveTool.cpp +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright (c) 2020, Andreas Kling - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include "MoveTool.h" -#include "Image.h" -#include "ImageEditor.h" -#include "Layer.h" -#include -#include -#include -#include - -namespace PixelPaint { - -MoveTool::MoveTool() -{ -} - -MoveTool::~MoveTool() -{ -} - -void MoveTool::on_mousedown(Layer* layer, MouseEvent& event) -{ - if (event.image_event().button() == GUI::MouseButton::Right && !m_is_panning) { - m_is_panning = true; - m_event_origin = event.raw_event().position(); - m_saved_pan_origin = m_editor->pan_origin(); - m_editor->set_override_cursor(Gfx::StandardCursor::Drag); - return; - } - - if (!layer) - return; - - auto& layer_event = event.layer_event(); - auto& image_event = event.image_event(); - if (layer_event.button() != GUI::MouseButton::Left) - return; - if (!layer->rect().contains(layer_event.position())) - return; - m_layer_being_moved = *layer; - m_event_origin = image_event.position(); - m_layer_origin = layer->location(); -} - -void MoveTool::on_mousemove(Layer* layer, MouseEvent& event) -{ - if (m_is_panning) { - auto& raw_event = event.raw_event(); - auto delta = raw_event.position() - m_event_origin; - m_editor->set_pan_origin(m_saved_pan_origin.translated( - -delta.x(), - -delta.y())); - return; - } - - if (!layer) - return; - - auto& image_event = event.image_event(); - if (!m_layer_being_moved) - return; - auto delta = image_event.position() - m_event_origin; - m_layer_being_moved->set_location(m_layer_origin.translated(delta)); - m_editor->layers_did_change(); -} - -void MoveTool::on_mouseup(Layer* layer, MouseEvent& event) -{ - if (event.image_event().button() == GUI::MouseButton::Right && m_is_panning) { - m_is_panning = false; - m_editor->set_override_cursor(cursor()); - return; - } - - if (!layer) - return; - - auto& layer_event = event.layer_event(); - if (layer_event.button() != GUI::MouseButton::Left) - return; - m_layer_being_moved = nullptr; - m_editor->did_complete_action(); -} - -void MoveTool::on_keydown(GUI::KeyEvent& event) -{ - if (event.modifiers() != 0) - return; - - auto* layer = m_editor->active_layer(); - if (!layer) - return; - - auto new_location = layer->location(); - - switch (event.key()) { - case Key_Up: - new_location.translate_by(0, -1); - break; - case Key_Down: - new_location.translate_by(0, 1); - break; - case Key_Left: - new_location.translate_by(-1, 0); - break; - case Key_Right: - new_location.translate_by(1, 0); - break; - default: - return; - } - - layer->set_location(new_location); - m_editor->layers_did_change(); -} - -} diff --git a/Userland/Applications/PixelPaint/MoveTool.h b/Userland/Applications/PixelPaint/MoveTool.h deleted file mode 100644 index 93631ff077..0000000000 --- a/Userland/Applications/PixelPaint/MoveTool.h +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (c) 2020, Andreas Kling - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - -#include "Tool.h" - -namespace PixelPaint { - -class MoveTool final : public Tool { -public: - MoveTool(); - virtual ~MoveTool() override; - - virtual void on_mousedown(Layer*, MouseEvent&) override; - virtual void on_mousemove(Layer*, MouseEvent&) override; - virtual void on_mouseup(Layer*, MouseEvent&) override; - virtual void on_keydown(GUI::KeyEvent&) override; - virtual Gfx::StandardCursor cursor() override { return Gfx::StandardCursor::Move; } - -private: - RefPtr m_layer_being_moved; - Gfx::IntPoint m_event_origin; - Gfx::IntPoint m_layer_origin; - - bool m_is_panning { false }; - Gfx::FloatPoint m_saved_pan_origin; -}; - -} diff --git a/Userland/Applications/PixelPaint/PenTool.cpp b/Userland/Applications/PixelPaint/PenTool.cpp deleted file mode 100644 index be9cf3e7a4..0000000000 --- a/Userland/Applications/PixelPaint/PenTool.cpp +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling - * Copyright (c) 2021, Mustafa Quraish - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include "PenTool.h" -#include "ImageEditor.h" -#include "Layer.h" -#include -#include -#include -#include -#include -#include - -namespace PixelPaint { - -PenTool::PenTool() -{ - set_size(1); -} - -PenTool::~PenTool() -{ -} - -void PenTool::draw_point(Gfx::Bitmap& bitmap, Gfx::Color const& color, Gfx::IntPoint const& point) -{ - GUI::Painter painter(bitmap); - painter.draw_line(point, point, color, size()); -} - -void PenTool::draw_line(Gfx::Bitmap& bitmap, Gfx::Color const& color, Gfx::IntPoint const& start, Gfx::IntPoint const& end) -{ - GUI::Painter painter(bitmap); - painter.draw_line(start, end, color, size()); -} - -GUI::Widget* PenTool::get_properties_widget() -{ - if (!m_properties_widget) { - m_properties_widget = GUI::Widget::construct(); - m_properties_widget->set_layout(); - - auto& size_container = m_properties_widget->add(); - size_container.set_fixed_height(20); - size_container.set_layout(); - - auto& size_label = size_container.add("Thickness:"); - size_label.set_text_alignment(Gfx::TextAlignment::CenterLeft); - size_label.set_fixed_size(80, 20); - - auto& size_slider = size_container.add(Orientation::Horizontal, "px"); - size_slider.set_range(1, 20); - size_slider.set_value(size()); - - size_slider.on_change = [&](int value) { - set_size(value); - }; - set_primary_slider(&size_slider); - } - - return m_properties_widget.ptr(); -} - -} diff --git a/Userland/Applications/PixelPaint/PenTool.h b/Userland/Applications/PixelPaint/PenTool.h deleted file mode 100644 index 5ac5f23c0a..0000000000 --- a/Userland/Applications/PixelPaint/PenTool.h +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling - * Copyright (c) 2021, Mustafa Quraish - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - -#include "BrushTool.h" -#include -#include - -namespace PixelPaint { - -class PenTool final : public BrushTool { -public: - PenTool(); - virtual ~PenTool() override; - - virtual GUI::Widget* get_properties_widget() override; - -protected: - virtual void draw_point(Gfx::Bitmap& bitmap, Gfx::Color const& color, Gfx::IntPoint const& point) override; - virtual void draw_line(Gfx::Bitmap& bitmap, Gfx::Color const& color, Gfx::IntPoint const& start, Gfx::IntPoint const& end) override; - -private: - RefPtr m_properties_widget; -}; - -} diff --git a/Userland/Applications/PixelPaint/PickerTool.cpp b/Userland/Applications/PixelPaint/PickerTool.cpp deleted file mode 100644 index 1b63284521..0000000000 --- a/Userland/Applications/PixelPaint/PickerTool.cpp +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling - * Copyright (c) 2021, Mustafa Quraish - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include "PickerTool.h" -#include "ImageEditor.h" -#include "Layer.h" -#include -#include - -namespace PixelPaint { - -PickerTool::PickerTool() -{ -} - -PickerTool::~PickerTool() -{ -} - -void PickerTool::on_mousedown(Layer* layer, MouseEvent& event) -{ - auto& position = event.layer_event().position(); - - Color color; - if (m_sample_all_layers) { - color = m_editor->image().color_at(position); - } else { - if (!layer || !layer->rect().contains(position)) - return; - color = layer->bitmap().get_pixel(position); - } - - // We picked a transparent pixel, do nothing. - if (!color.alpha()) - return; - - if (event.layer_event().button() == GUI::MouseButton::Left) - m_editor->set_primary_color(color); - else if (event.layer_event().button() == GUI::MouseButton::Right) - m_editor->set_secondary_color(color); -} - -GUI::Widget* PickerTool::get_properties_widget() -{ - if (!m_properties_widget) { - m_properties_widget = GUI::Widget::construct(); - m_properties_widget->set_layout(); - - auto& sample_checkbox = m_properties_widget->add("Sample all layers"); - sample_checkbox.set_checked(m_sample_all_layers); - sample_checkbox.on_checked = [&](bool value) { - m_sample_all_layers = value; - }; - } - - return m_properties_widget.ptr(); -} - -} diff --git a/Userland/Applications/PixelPaint/PickerTool.h b/Userland/Applications/PixelPaint/PickerTool.h deleted file mode 100644 index 5a558bfbc9..0000000000 --- a/Userland/Applications/PixelPaint/PickerTool.h +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling - * Copyright (c) 2021, Mustafa Quraish - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - -#include "Tool.h" - -namespace PixelPaint { - -class PickerTool final : public Tool { -public: - PickerTool(); - virtual ~PickerTool() override; - - virtual void on_mousedown(Layer*, MouseEvent&) override; - - virtual GUI::Widget* get_properties_widget() override; - virtual Gfx::StandardCursor cursor() override { return Gfx::StandardCursor::Eyedropper; } - -private: - RefPtr m_properties_widget; - bool m_sample_all_layers { false }; -}; - -} diff --git a/Userland/Applications/PixelPaint/RectangleSelectTool.cpp b/Userland/Applications/PixelPaint/RectangleSelectTool.cpp deleted file mode 100644 index 429ff6e546..0000000000 --- a/Userland/Applications/PixelPaint/RectangleSelectTool.cpp +++ /dev/null @@ -1,212 +0,0 @@ -/* - * Copyright (c) 2021, Andreas Kling - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include "RectangleSelectTool.h" -#include "ImageEditor.h" -#include "Layer.h" -#include -#include -#include -#include -#include -#include -#include -#include - -namespace PixelPaint { - -RectangleSelectTool::RectangleSelectTool() -{ -} - -RectangleSelectTool::~RectangleSelectTool() -{ -} - -void RectangleSelectTool::on_mousedown(Layer*, MouseEvent& event) -{ - auto& image_event = event.image_event(); - if (image_event.button() != GUI::MouseButton::Left) - return; - - m_selecting = true; - m_editor->selection().begin_interactive_selection(); - - m_selection_start = image_event.position(); - m_selection_end = image_event.position(); - m_editor->update(); -} - -void RectangleSelectTool::on_mousemove(Layer*, MouseEvent& event) -{ - auto& image_event = event.image_event(); - if (!m_selecting) - return; - - if (m_moving_mode != MovingMode::None) { - auto delta = m_selection_end - image_event.position(); - if (m_moving_mode == MovingMode::MovingOrigin) - m_selection_start -= delta; - else if (m_moving_mode == MovingMode::AroundCenter) - m_selection_start += delta; - } - - m_selection_end = image_event.position(); - m_editor->update(); -} - -void RectangleSelectTool::on_mouseup(Layer*, MouseEvent& event) -{ - auto& image_event = event.image_event(); - if (!m_selecting || image_event.button() != GUI::MouseButton::Left) - return; - - m_selecting = false; - m_editor->selection().end_interactive_selection(); - - m_editor->update(); - - auto rect_in_image = Gfx::IntRect::from_two_points(m_selection_start, m_selection_end); - auto mask = Mask::full(rect_in_image); - - auto feathering = ((mask.bounding_rect().size().to_type() * .5f) * m_edge_feathering).to_type(); - - // Multiply the alpha instead of setting it to ensure corners are feathered correctly - auto multiply_alpha = [&mask](int x, int y, float alpha) { - Gfx::IntPoint point { x, y }; - point += mask.bounding_rect().top_left(); - - float old_alpha = mask.getf(point); - mask.setf(point, old_alpha * alpha); - }; - - // Horizontal feathering - for (int offset = 0; offset < feathering.width(); offset++) { - // Add 1 to offset before dividing to ensure the first pixel won't always be transparent - float alpha = (float)(offset + 1) / (float)feathering.width(); - - for (int y = 0; y < mask.bounding_rect().height(); y++) { - multiply_alpha(offset, y, alpha); - multiply_alpha(mask.bounding_rect().width() - offset - 1, y, alpha); - } - } - - // Vertical feathering - for (int offset = 0; offset < feathering.height(); offset++) { - // Add 1 to offset before dividing to ensure the first pixel won't always be transparent - float alpha = (float)(offset + 1) / (float)feathering.height(); - - for (int x = 0; x < mask.bounding_rect().width(); x++) { - multiply_alpha(x, offset, alpha); - multiply_alpha(x, mask.bounding_rect().height() - offset - 1, alpha); - } - } - - m_editor->selection().merge(mask, m_merge_mode); -} - -void RectangleSelectTool::on_keydown(GUI::KeyEvent& key_event) -{ - Tool::on_keydown(key_event); - if (key_event.key() == KeyCode::Key_Space) - m_moving_mode = MovingMode::MovingOrigin; - else if (key_event.key() == KeyCode::Key_Control) - m_moving_mode = MovingMode::AroundCenter; -} - -void RectangleSelectTool::on_keyup(GUI::KeyEvent& key_event) -{ - if (key_event.key() == KeyCode::Key_Space && m_moving_mode == MovingMode::MovingOrigin) - m_moving_mode = MovingMode::None; - else if (key_event.key() == KeyCode::Key_Control && m_moving_mode == MovingMode::AroundCenter) - m_moving_mode = MovingMode::None; -} - -void RectangleSelectTool::on_second_paint(Layer const*, GUI::PaintEvent& event) -{ - if (!m_selecting) - return; - - GUI::Painter painter(*m_editor); - painter.add_clip_rect(event.rect()); - - auto rect_in_image = Gfx::IntRect::from_two_points(m_selection_start, m_selection_end); - auto rect_in_editor = m_editor->image_rect_to_editor_rect(rect_in_image); - - m_editor->selection().draw_marching_ants(painter, rect_in_editor.to_type()); -} - -GUI::Widget* RectangleSelectTool::get_properties_widget() -{ - if (m_properties_widget) { - return m_properties_widget.ptr(); - } - - m_properties_widget = GUI::Widget::construct(); - m_properties_widget->set_layout(); - - auto& feather_container = m_properties_widget->add(); - feather_container.set_fixed_height(20); - feather_container.set_layout(); - - auto& feather_label = feather_container.add(); - feather_label.set_text("Feather:"); - feather_label.set_text_alignment(Gfx::TextAlignment::CenterLeft); - feather_label.set_fixed_size(80, 20); - - const int feather_slider_max = 100; - auto& feather_slider = feather_container.add(Orientation::Horizontal, "%"); - feather_slider.set_range(0, feather_slider_max); - feather_slider.set_value((int)floorf(m_edge_feathering * (float)feather_slider_max)); - - feather_slider.on_change = [&](int value) { - m_edge_feathering = (float)value / (float)feather_slider_max; - }; - set_primary_slider(&feather_slider); - - auto& mode_container = m_properties_widget->add(); - mode_container.set_fixed_height(20); - mode_container.set_layout(); - - auto& mode_label = mode_container.add(); - mode_label.set_text("Mode:"); - mode_label.set_text_alignment(Gfx::TextAlignment::CenterLeft); - mode_label.set_fixed_size(80, 20); - - for (int i = 0; i < (int)Selection::MergeMode::__Count; i++) { - switch ((Selection::MergeMode)i) { - case Selection::MergeMode::Set: - m_merge_mode_names.append("Set"); - break; - case Selection::MergeMode::Add: - m_merge_mode_names.append("Add"); - break; - case Selection::MergeMode::Subtract: - m_merge_mode_names.append("Subtract"); - break; - case Selection::MergeMode::Intersect: - m_merge_mode_names.append("Intersect"); - break; - default: - VERIFY_NOT_REACHED(); - } - } - - auto& mode_combo = mode_container.add(); - mode_combo.set_only_allow_values_from_model(true); - mode_combo.set_model(*GUI::ItemListModel::create(m_merge_mode_names)); - mode_combo.set_selected_index((int)m_merge_mode); - mode_combo.on_change = [this](auto&&, GUI::ModelIndex const& index) { - VERIFY(index.row() >= 0); - VERIFY(index.row() < (int)Selection::MergeMode::__Count); - - m_merge_mode = (Selection::MergeMode)index.row(); - }; - - return m_properties_widget.ptr(); -} - -} diff --git a/Userland/Applications/PixelPaint/RectangleSelectTool.h b/Userland/Applications/PixelPaint/RectangleSelectTool.h deleted file mode 100644 index 1d8fc45907..0000000000 --- a/Userland/Applications/PixelPaint/RectangleSelectTool.h +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (c) 2021, Andreas Kling - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - -#include "Selection.h" -#include "Tool.h" - -#include -#include - -namespace PixelPaint { - -class RectangleSelectTool final : public Tool { -public: - RectangleSelectTool(); - virtual ~RectangleSelectTool(); - - virtual void on_mousedown(Layer*, MouseEvent& event) override; - virtual void on_mousemove(Layer*, MouseEvent& event) override; - virtual void on_mouseup(Layer*, MouseEvent& event) override; - virtual void on_keydown(GUI::KeyEvent&) override; - virtual void on_keyup(GUI::KeyEvent&) override; - virtual void on_second_paint(Layer const*, GUI::PaintEvent&) override; - virtual GUI::Widget* get_properties_widget() override; - virtual Gfx::StandardCursor cursor() override { return Gfx::StandardCursor::Crosshair; } - -private: - enum class MovingMode { - MovingOrigin, - AroundCenter, - None, - }; - - RefPtr m_properties_widget; - Vector m_merge_mode_names {}; - Selection::MergeMode m_merge_mode { Selection::MergeMode::Set }; - float m_edge_feathering { 0.0f }; - bool m_selecting { false }; - MovingMode m_moving_mode { MovingMode::None }; - Gfx::IntPoint m_selection_start; - Gfx::IntPoint m_selection_end; -}; - -} diff --git a/Userland/Applications/PixelPaint/RectangleTool.cpp b/Userland/Applications/PixelPaint/RectangleTool.cpp deleted file mode 100644 index cec747c25c..0000000000 --- a/Userland/Applications/PixelPaint/RectangleTool.cpp +++ /dev/null @@ -1,213 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling - * Copyright (c) 2021, Mustafa Quraish - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include "RectangleTool.h" -#include "ImageEditor.h" -#include "Layer.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace PixelPaint { - -RectangleTool::RectangleTool() -{ -} - -RectangleTool::~RectangleTool() -{ -} - -void RectangleTool::draw_using(GUI::Painter& painter, Gfx::IntPoint const& start_position, Gfx::IntPoint const& end_position, int thickness) -{ - Gfx::IntRect rect; - if (m_draw_mode == DrawMode::FromCenter) { - auto delta = end_position - start_position; - rect = Gfx::IntRect::from_two_points(start_position - delta, end_position); - } else { - rect = Gfx::IntRect::from_two_points(start_position, end_position); - } - - switch (m_fill_mode) { - case FillMode::Fill: - painter.fill_rect(rect, m_editor->color_for(m_drawing_button)); - break; - case FillMode::Outline: - painter.draw_rect_with_thickness(rect, m_editor->color_for(m_drawing_button), thickness); - break; - case FillMode::Gradient: - painter.fill_rect_with_gradient(rect, m_editor->primary_color(), m_editor->secondary_color()); - break; - default: - VERIFY_NOT_REACHED(); - } -} - -void RectangleTool::on_mousedown(Layer* layer, MouseEvent& event) -{ - if (!layer) - return; - - auto& layer_event = event.layer_event(); - if (layer_event.button() != GUI::MouseButton::Left && layer_event.button() != GUI::MouseButton::Right) - return; - - if (m_drawing_button != GUI::MouseButton::None) - return; - - m_drawing_button = layer_event.button(); - m_rectangle_start_position = layer_event.position(); - m_rectangle_end_position = layer_event.position(); - m_editor->update(); -} - -void RectangleTool::on_mouseup(Layer* layer, MouseEvent& event) -{ - if (!layer) - return; - - if (event.layer_event().button() == m_drawing_button) { - GUI::Painter painter(layer->bitmap()); - draw_using(painter, m_rectangle_start_position, m_rectangle_end_position, m_thickness); - m_drawing_button = GUI::MouseButton::None; - layer->did_modify_bitmap(); - m_editor->update(); - m_editor->did_complete_action(); - } -} - -void RectangleTool::on_mousemove(Layer* layer, MouseEvent& event) -{ - if (!layer) - return; - - if (m_drawing_button == GUI::MouseButton::None) - return; - - m_draw_mode = event.layer_event().alt() ? DrawMode::FromCenter : DrawMode::FromCorner; - - if (event.layer_event().shift()) - m_rectangle_end_position = m_rectangle_start_position.end_point_for_aspect_ratio(event.layer_event().position(), 1.0); - else if (m_aspect_ratio.has_value()) - m_rectangle_end_position = m_rectangle_start_position.end_point_for_aspect_ratio(event.layer_event().position(), m_aspect_ratio.value()); - else - m_rectangle_end_position = event.layer_event().position(); - - m_editor->update(); -} - -void RectangleTool::on_second_paint(Layer const* layer, GUI::PaintEvent& event) -{ - if (!layer || m_drawing_button == GUI::MouseButton::None) - return; - - GUI::Painter painter(*m_editor); - painter.add_clip_rect(event.rect()); - auto start_position = editor_stroke_position(m_rectangle_start_position, m_thickness); - auto end_position = editor_stroke_position(m_rectangle_end_position, m_thickness); - draw_using(painter, start_position, end_position, AK::max(m_thickness * m_editor->scale(), 1)); -} - -void RectangleTool::on_keydown(GUI::KeyEvent& event) -{ - Tool::on_keydown(event); - if (event.key() == Key_Escape && m_drawing_button != GUI::MouseButton::None) { - m_drawing_button = GUI::MouseButton::None; - m_editor->update(); - event.accept(); - } -} - -GUI::Widget* RectangleTool::get_properties_widget() -{ - if (!m_properties_widget) { - m_properties_widget = GUI::Widget::construct(); - m_properties_widget->set_layout(); - - auto& thickness_container = m_properties_widget->add(); - thickness_container.set_fixed_height(20); - thickness_container.set_layout(); - - auto& thickness_label = thickness_container.add("Thickness:"); - thickness_label.set_text_alignment(Gfx::TextAlignment::CenterLeft); - thickness_label.set_fixed_size(80, 20); - - auto& thickness_slider = thickness_container.add(Orientation::Horizontal, "px"); - thickness_slider.set_range(1, 10); - thickness_slider.set_value(m_thickness); - - thickness_slider.on_change = [&](int value) { - m_thickness = value; - }; - set_primary_slider(&thickness_slider); - - auto& mode_container = m_properties_widget->add(); - mode_container.set_fixed_height(70); - mode_container.set_layout(); - auto& mode_label = mode_container.add("Mode:"); - mode_label.set_text_alignment(Gfx::TextAlignment::CenterLeft); - mode_label.set_fixed_size(80, 20); - - auto& mode_radio_container = mode_container.add(); - mode_radio_container.set_layout(); - auto& outline_mode_radio = mode_radio_container.add("Outline"); - auto& fill_mode_radio = mode_radio_container.add("Fill"); - auto& gradient_mode_radio = mode_radio_container.add("Gradient"); - - outline_mode_radio.on_checked = [&](bool) { - m_fill_mode = FillMode::Outline; - }; - fill_mode_radio.on_checked = [&](bool) { - m_fill_mode = FillMode::Fill; - }; - gradient_mode_radio.on_checked = [&](bool) { - m_fill_mode = FillMode::Gradient; - }; - - outline_mode_radio.set_checked(true); - - auto& aspect_container = m_properties_widget->add(); - aspect_container.set_fixed_height(20); - aspect_container.set_layout(); - - auto& aspect_label = aspect_container.add("Aspect Ratio:"); - aspect_label.set_text_alignment(Gfx::TextAlignment::CenterLeft); - aspect_label.set_fixed_size(80, 20); - - m_aspect_w_textbox = aspect_container.add(); - m_aspect_w_textbox->set_fixed_height(20); - m_aspect_w_textbox->set_fixed_width(25); - m_aspect_w_textbox->on_change = [&] { - auto x = m_aspect_w_textbox->text().to_int().value_or(0); - auto y = m_aspect_h_textbox->text().to_int().value_or(0); - if (x > 0 && y > 0) { - m_aspect_ratio = (float)x / (float)y; - } else { - m_aspect_ratio = {}; - } - }; - - auto& multiply_label = aspect_container.add("x"); - multiply_label.set_text_alignment(Gfx::TextAlignment::Center); - multiply_label.set_fixed_size(10, 20); - - m_aspect_h_textbox = aspect_container.add(); - m_aspect_h_textbox->set_fixed_height(20); - m_aspect_h_textbox->set_fixed_width(25); - m_aspect_h_textbox->on_change = [&] { m_aspect_w_textbox->on_change(); }; - } - - return m_properties_widget.ptr(); -} - -} diff --git a/Userland/Applications/PixelPaint/RectangleTool.h b/Userland/Applications/PixelPaint/RectangleTool.h deleted file mode 100644 index 6ff2bb1e12..0000000000 --- a/Userland/Applications/PixelPaint/RectangleTool.h +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling - * Copyright (c) 2021, Mustafa Quraish - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - -#include "Tool.h" -#include -#include - -namespace PixelPaint { - -class RectangleTool final : public Tool { -public: - RectangleTool(); - virtual ~RectangleTool() override; - - virtual void on_mousedown(Layer*, MouseEvent&) override; - virtual void on_mousemove(Layer*, MouseEvent&) override; - virtual void on_mouseup(Layer*, MouseEvent&) override; - virtual void on_second_paint(Layer const*, GUI::PaintEvent&) override; - virtual void on_keydown(GUI::KeyEvent&) override; - virtual GUI::Widget* get_properties_widget() override; - virtual Gfx::StandardCursor cursor() override { return Gfx::StandardCursor::Crosshair; } - -private: - enum class FillMode { - Outline, - Fill, - Gradient, - }; - - enum class DrawMode { - FromCenter, - FromCorner, - }; - - void draw_using(GUI::Painter&, Gfx::IntPoint const& start_position, Gfx::IntPoint const& end_position, int thickness); - - RefPtr m_properties_widget; - RefPtr m_aspect_w_textbox; - RefPtr m_aspect_h_textbox; - - GUI::MouseButton m_drawing_button { GUI::MouseButton::None }; - Gfx::IntPoint m_rectangle_start_position; - Gfx::IntPoint m_rectangle_end_position; - FillMode m_fill_mode { FillMode::Outline }; - DrawMode m_draw_mode { DrawMode::FromCorner }; - int m_thickness { 1 }; - Optional m_aspect_ratio; -}; - -} diff --git a/Userland/Applications/PixelPaint/SprayTool.cpp b/Userland/Applications/PixelPaint/SprayTool.cpp deleted file mode 100644 index 0aa431aa8b..0000000000 --- a/Userland/Applications/PixelPaint/SprayTool.cpp +++ /dev/null @@ -1,142 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include "SprayTool.h" -#include "ImageEditor.h" -#include "Layer.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace PixelPaint { - -SprayTool::SprayTool() -{ - m_timer = Core::Timer::construct(); - m_timer->on_timeout = [&]() { - paint_it(); - }; - m_timer->set_interval(200); -} - -SprayTool::~SprayTool() -{ -} - -static double nrand() -{ - return double(rand()) / double(RAND_MAX); -} - -void SprayTool::paint_it() -{ - auto* layer = m_editor->active_layer(); - if (!layer) - return; - - auto& bitmap = layer->bitmap(); - GUI::Painter painter(bitmap); - VERIFY(bitmap.bpp() == 32); - const double minimal_radius = 2; - const double base_radius = minimal_radius * m_thickness; - for (int i = 0; i < M_PI * base_radius * base_radius * (m_density / 100.0); i++) { - double radius = base_radius * nrand(); - double angle = 2 * M_PI * nrand(); - const int xpos = m_last_pos.x() + radius * AK::cos(angle); - const int ypos = m_last_pos.y() - radius * AK::sin(angle); - if (xpos < 0 || xpos >= bitmap.width()) - continue; - if (ypos < 0 || ypos >= bitmap.height()) - continue; - bitmap.set_pixel(xpos, ypos, m_color); - } - - layer->did_modify_bitmap(Gfx::IntRect::centered_on(m_last_pos, Gfx::IntSize(base_radius * 2, base_radius * 2))); -} - -void SprayTool::on_mousedown(Layer* layer, MouseEvent& event) -{ - if (!layer) - return; - - auto& layer_event = event.layer_event(); - m_color = m_editor->color_for(layer_event); - m_last_pos = layer_event.position(); - m_timer->start(); - paint_it(); -} - -void SprayTool::on_mousemove(Layer* layer, MouseEvent& event) -{ - if (!layer) - return; - - m_last_pos = event.layer_event().position(); - if (m_timer->is_active()) { - paint_it(); - m_timer->restart(m_timer->interval()); - } -} - -void SprayTool::on_mouseup(Layer*, MouseEvent&) -{ - if (m_timer->is_active()) { - m_timer->stop(); - m_editor->did_complete_action(); - } -} - -GUI::Widget* SprayTool::get_properties_widget() -{ - if (!m_properties_widget) { - m_properties_widget = GUI::Widget::construct(); - m_properties_widget->set_layout(); - - auto& size_container = m_properties_widget->add(); - size_container.set_fixed_height(20); - size_container.set_layout(); - - auto& size_label = size_container.add("Size:"); - size_label.set_text_alignment(Gfx::TextAlignment::CenterLeft); - size_label.set_fixed_size(80, 20); - - auto& size_slider = size_container.add(Orientation::Horizontal, "px"); - size_slider.set_range(1, 20); - size_slider.set_value(m_thickness); - - size_slider.on_change = [&](int value) { - m_thickness = value; - }; - set_primary_slider(&size_slider); - - auto& density_container = m_properties_widget->add(); - density_container.set_fixed_height(20); - density_container.set_layout(); - - auto& density_label = density_container.add("Density:"); - density_label.set_text_alignment(Gfx::TextAlignment::CenterLeft); - density_label.set_fixed_size(80, 20); - - auto& density_slider = density_container.add(Orientation::Horizontal, "%"); - density_slider.set_range(1, 100); - density_slider.set_value(m_density); - - density_slider.on_change = [&](int value) { - m_density = value; - }; - set_secondary_slider(&density_slider); - } - - return m_properties_widget.ptr(); -} - -} diff --git a/Userland/Applications/PixelPaint/SprayTool.h b/Userland/Applications/PixelPaint/SprayTool.h deleted file mode 100644 index 9f08bdfb44..0000000000 --- a/Userland/Applications/PixelPaint/SprayTool.h +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - -#include "Tool.h" -#include -#include -#include - -namespace PixelPaint { - -class SprayTool final : public Tool { -public: - SprayTool(); - virtual ~SprayTool() override; - - virtual void on_mousedown(Layer*, MouseEvent&) override; - virtual void on_mouseup(Layer*, MouseEvent&) override; - virtual void on_mousemove(Layer*, MouseEvent&) override; - virtual GUI::Widget* get_properties_widget() override; - virtual Gfx::StandardCursor cursor() override { return Gfx::StandardCursor::Crosshair; } - -private: - void paint_it(); - - RefPtr m_properties_widget; - RefPtr m_timer; - Gfx::IntPoint m_last_pos; - Color m_color; - int m_thickness { 10 }; - int m_density { 40 }; -}; - -} diff --git a/Userland/Applications/PixelPaint/Tool.cpp b/Userland/Applications/PixelPaint/Tool.cpp deleted file mode 100644 index 2d0dbfc92a..0000000000 --- a/Userland/Applications/PixelPaint/Tool.cpp +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling - * Copyright (c) 2021, Mustafa Quraish - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include "Tool.h" -#include "ImageEditor.h" -#include - -namespace PixelPaint { - -Tool::Tool() -{ -} - -Tool::~Tool() -{ -} - -void Tool::setup(ImageEditor& editor) -{ - m_editor = editor; -} - -void Tool::set_action(GUI::Action* action) -{ - m_action = action; -} - -void Tool::on_keydown(GUI::KeyEvent& event) -{ - switch (event.key()) { - case KeyCode::Key_LeftBracket: - if (m_primary_slider) - m_primary_slider->set_value(m_primary_slider->value() - 1); - break; - case KeyCode::Key_RightBracket: - if (m_primary_slider) - m_primary_slider->set_value(m_primary_slider->value() + 1); - break; - case KeyCode::Key_LeftBrace: - if (m_secondary_slider) - m_secondary_slider->set_value(m_secondary_slider->value() - 1); - break; - case KeyCode::Key_RightBrace: - if (m_secondary_slider) - m_secondary_slider->set_value(m_secondary_slider->value() + 1); - break; - default: - break; - } -} - -Gfx::IntPoint Tool::editor_stroke_position(Gfx::IntPoint const& pixel_coords, int stroke_thickness) const -{ - auto position = m_editor->image_position_to_editor_position(pixel_coords); - auto offset = (stroke_thickness % 2 == 0) ? m_editor->scale() : m_editor->scale() / 2; - position = position.translated(offset, offset); - return position.to_type(); -} - -} diff --git a/Userland/Applications/PixelPaint/Tool.h b/Userland/Applications/PixelPaint/Tool.h deleted file mode 100644 index 82b6508c5b..0000000000 --- a/Userland/Applications/PixelPaint/Tool.h +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling - * Copyright (c) 2021, Mustafa Quraish - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - -#include -#include -#include -#include - -namespace PixelPaint { - -class ImageEditor; -class Layer; - -class Tool { -public: - virtual ~Tool(); - - class MouseEvent { - public: - enum class Action { - MouseDown, - MouseMove, - MouseUp - }; - - MouseEvent(Action action, GUI::MouseEvent& layer_event, GUI::MouseEvent& image_event, GUI::MouseEvent& raw_event) - : m_action(action) - , m_layer_event(layer_event) - , m_image_event(image_event) - , m_raw_event(raw_event) - { - } - - Action action() const { return m_action; } - GUI::MouseEvent const& layer_event() const { return m_layer_event; } - GUI::MouseEvent const& image_event() const { return m_image_event; } - GUI::MouseEvent const& raw_event() const { return m_raw_event; } - - private: - Action m_action; - - GUI::MouseEvent& m_layer_event; - GUI::MouseEvent& m_image_event; - GUI::MouseEvent& m_raw_event; - }; - - virtual void on_mousedown(Layer*, MouseEvent&) { } - virtual void on_mousemove(Layer*, MouseEvent&) { } - virtual void on_mouseup(Layer*, MouseEvent&) { } - virtual void on_context_menu(Layer*, GUI::ContextMenuEvent&) { } - virtual void on_tool_button_contextmenu(GUI::ContextMenuEvent&) { } - virtual void on_second_paint(Layer const*, GUI::PaintEvent&) { } - virtual void on_keydown(GUI::KeyEvent&); - virtual void on_keyup(GUI::KeyEvent&) { } - virtual void on_tool_activation() { } - virtual GUI::Widget* get_properties_widget() { return nullptr; } - virtual Gfx::StandardCursor cursor() { return Gfx::StandardCursor::None; } - - void clear() { m_editor = nullptr; } - void setup(ImageEditor&); - - ImageEditor const* editor() const { return m_editor; } - ImageEditor* editor() { return m_editor; } - - GUI::Action* action() { return m_action; } - void set_action(GUI::Action*); - -protected: - Tool(); - WeakPtr m_editor; - RefPtr m_action; - - virtual Gfx::IntPoint editor_stroke_position(Gfx::IntPoint const& pixel_coords, int stroke_thickness) const; - - void set_primary_slider(GUI::ValueSlider* primary) { m_primary_slider = primary; } - void set_secondary_slider(GUI::ValueSlider* secondary) { m_secondary_slider = secondary; } - - GUI::ValueSlider* m_primary_slider { nullptr }; - GUI::ValueSlider* m_secondary_slider { nullptr }; -}; - -} diff --git a/Userland/Applications/PixelPaint/ToolPropertiesWidget.cpp b/Userland/Applications/PixelPaint/ToolPropertiesWidget.cpp index 6aa245ea62..bc8d8a8eaa 100644 --- a/Userland/Applications/PixelPaint/ToolPropertiesWidget.cpp +++ b/Userland/Applications/PixelPaint/ToolPropertiesWidget.cpp @@ -5,7 +5,7 @@ */ #include "ToolPropertiesWidget.h" -#include "Tool.h" +#include "Tools/Tool.h" #include #include diff --git a/Userland/Applications/PixelPaint/ToolboxWidget.cpp b/Userland/Applications/PixelPaint/ToolboxWidget.cpp index 5b212e7aad..4f5ffc042c 100644 --- a/Userland/Applications/PixelPaint/ToolboxWidget.cpp +++ b/Userland/Applications/PixelPaint/ToolboxWidget.cpp @@ -5,20 +5,20 @@ */ #include "ToolboxWidget.h" -#include "BrushTool.h" -#include "BucketTool.h" -#include "CloneTool.h" -#include "EllipseTool.h" -#include "EraseTool.h" -#include "GuideTool.h" -#include "LineTool.h" -#include "MoveTool.h" -#include "PenTool.h" -#include "PickerTool.h" -#include "RectangleSelectTool.h" -#include "RectangleTool.h" -#include "SprayTool.h" -#include "ZoomTool.h" +#include "Tools/BrushTool.h" +#include "Tools/BucketTool.h" +#include "Tools/CloneTool.h" +#include "Tools/EllipseTool.h" +#include "Tools/EraseTool.h" +#include "Tools/GuideTool.h" +#include "Tools/LineTool.h" +#include "Tools/MoveTool.h" +#include "Tools/PenTool.h" +#include "Tools/PickerTool.h" +#include "Tools/RectangleSelectTool.h" +#include "Tools/RectangleTool.h" +#include "Tools/SprayTool.h" +#include "Tools/ZoomTool.h" #include #include #include diff --git a/Userland/Applications/PixelPaint/Tools/BrushTool.cpp b/Userland/Applications/PixelPaint/Tools/BrushTool.cpp new file mode 100644 index 0000000000..42d702d898 --- /dev/null +++ b/Userland/Applications/PixelPaint/Tools/BrushTool.cpp @@ -0,0 +1,183 @@ +/* + * Copyright (c) 2020, Ben Jilks + * Copyright (c) 2021, Mustafa Quraish + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "BrushTool.h" +#include "../ImageEditor.h" +#include "../Layer.h" +#include +#include +#include +#include +#include +#include +#include + +namespace PixelPaint { + +BrushTool::BrushTool() +{ +} + +BrushTool::~BrushTool() +{ +} + +void BrushTool::on_mousedown(Layer* layer, MouseEvent& event) +{ + if (!layer) + return; + + auto& layer_event = event.layer_event(); + if (layer_event.button() != GUI::MouseButton::Left && layer_event.button() != GUI::MouseButton::Right) + return; + + // Shift+Click draws a line from the last position to current one. + if (layer_event.shift() && m_has_clicked) { + draw_line(layer->bitmap(), color_for(layer_event), m_last_position, layer_event.position()); + auto modified_rect = Gfx::IntRect::from_two_points(m_last_position, layer_event.position()).inflated(m_size * 2, m_size * 2); + layer->did_modify_bitmap(modified_rect); + m_last_position = layer_event.position(); + return; + } + + const int first_draw_opacity = 10; + + for (int i = 0; i < first_draw_opacity; ++i) + draw_point(layer->bitmap(), color_for(layer_event), layer_event.position()); + + layer->did_modify_bitmap(Gfx::IntRect::centered_on(layer_event.position(), Gfx::IntSize { m_size * 2, m_size * 2 })); + m_last_position = layer_event.position(); + m_has_clicked = true; +} + +void BrushTool::on_mousemove(Layer* layer, MouseEvent& event) +{ + if (!layer) + return; + + auto& layer_event = event.layer_event(); + if (!(layer_event.buttons() & GUI::MouseButton::Left || layer_event.buttons() & GUI::MouseButton::Right)) + return; + + draw_line(layer->bitmap(), color_for(layer_event), m_last_position, layer_event.position()); + + auto modified_rect = Gfx::IntRect::from_two_points(m_last_position, layer_event.position()).inflated(m_size * 2, m_size * 2); + + layer->did_modify_bitmap(modified_rect); + m_last_position = layer_event.position(); + m_was_drawing = true; +} + +void BrushTool::on_mouseup(Layer*, MouseEvent&) +{ + if (m_was_drawing) { + m_editor->did_complete_action(); + m_was_drawing = false; + } +} + +Color BrushTool::color_for(GUI::MouseEvent const& event) +{ + return m_editor->color_for(event); +} + +void BrushTool::draw_point(Gfx::Bitmap& bitmap, Gfx::Color const& color, Gfx::IntPoint const& point) +{ + for (int y = point.y() - size(); y < point.y() + size(); y++) { + for (int x = point.x() - size(); x < point.x() + size(); x++) { + auto distance = point.distance_from({ x, y }); + if (x < 0 || x >= bitmap.width() || y < 0 || y >= bitmap.height()) + continue; + if (distance >= size()) + continue; + + auto falloff = (1.0 - double { distance / size() }) * (1.0 / (100 - hardness())); + auto pixel_color = color; + pixel_color.set_alpha(falloff * 255); + bitmap.set_pixel(x, y, bitmap.get_pixel(x, y).blend(pixel_color)); + } + } +} + +void BrushTool::draw_line(Gfx::Bitmap& bitmap, Gfx::Color const& color, Gfx::IntPoint const& start, Gfx::IntPoint const& end) +{ + int length_x = end.x() - start.x(); + int length_y = end.y() - start.y(); + float y_step = length_y == 0 ? 0 : (float)(length_y) / (float)(length_x); + if (y_step > abs(length_y)) + y_step = abs(length_y); + if (y_step < -abs(length_y)) + y_step = -abs(length_y); + if (y_step == 0 && start.x() == end.x()) + return; + + int start_x = start.x(); + int end_x = end.x(); + int start_y = start.y(); + int end_y = end.y(); + if (start_x > end_x) { + swap(start_x, end_x); + swap(start_y, end_y); + } + + float y = start_y; + for (int x = start_x; x <= end_x; x++) { + int start_step_y = y; + int end_step_y = y + y_step; + if (start_step_y > end_step_y) + swap(start_step_y, end_step_y); + for (int i = start_step_y; i <= end_step_y; i++) + draw_point(bitmap, color, { x, i }); + y += y_step; + } +} + +GUI::Widget* BrushTool::get_properties_widget() +{ + if (!m_properties_widget) { + m_properties_widget = GUI::Widget::construct(); + m_properties_widget->set_layout(); + + auto& size_container = m_properties_widget->add(); + size_container.set_fixed_height(20); + size_container.set_layout(); + + auto& size_label = size_container.add("Size:"); + size_label.set_text_alignment(Gfx::TextAlignment::CenterLeft); + size_label.set_fixed_size(80, 20); + + auto& size_slider = size_container.add(Orientation::Horizontal, "px"); + size_slider.set_range(1, 100); + size_slider.set_value(m_size); + + size_slider.on_change = [&](int value) { + set_size(value); + }; + set_primary_slider(&size_slider); + + auto& hardness_container = m_properties_widget->add(); + hardness_container.set_fixed_height(20); + hardness_container.set_layout(); + + auto& hardness_label = hardness_container.add("Hardness:"); + hardness_label.set_text_alignment(Gfx::TextAlignment::CenterLeft); + hardness_label.set_fixed_size(80, 20); + + auto& hardness_slider = hardness_container.add(Orientation::Horizontal, "%"); + hardness_slider.set_range(1, 99); + hardness_slider.set_value(m_hardness); + + hardness_slider.on_change = [&](int value) { + set_hardness(value); + }; + set_secondary_slider(&hardness_slider); + } + + return m_properties_widget.ptr(); +} + +} diff --git a/Userland/Applications/PixelPaint/Tools/BrushTool.h b/Userland/Applications/PixelPaint/Tools/BrushTool.h new file mode 100644 index 0000000000..c4cca0a04c --- /dev/null +++ b/Userland/Applications/PixelPaint/Tools/BrushTool.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2020, Ben Jilks + * Copyright (c) 2021, Mustafa Quraish + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include "Tool.h" + +namespace PixelPaint { + +class BrushTool : public Tool { +public: + BrushTool(); + virtual ~BrushTool() override; + + virtual void on_mousedown(Layer*, MouseEvent&) override; + virtual void on_mousemove(Layer*, MouseEvent&) override; + virtual void on_mouseup(Layer*, MouseEvent&) override; + virtual GUI::Widget* get_properties_widget() override; + virtual Gfx::StandardCursor cursor() override { return Gfx::StandardCursor::Crosshair; } + + void set_size(int size) { m_size = size; } + int size() const { return m_size; } + + void set_hardness(int hardness) { m_hardness = hardness; } + int hardness() const { return m_hardness; } + +protected: + virtual Color color_for(GUI::MouseEvent const& event); + virtual void draw_point(Gfx::Bitmap& bitmap, Gfx::Color const& color, Gfx::IntPoint const& point); + virtual void draw_line(Gfx::Bitmap& bitmap, Gfx::Color const& color, Gfx::IntPoint const& start, Gfx::IntPoint const& end); + +private: + RefPtr m_properties_widget; + int m_size { 20 }; + int m_hardness { 80 }; + bool m_was_drawing { false }; + bool m_has_clicked { false }; + Gfx::IntPoint m_last_position; +}; + +} diff --git a/Userland/Applications/PixelPaint/Tools/BucketTool.cpp b/Userland/Applications/PixelPaint/Tools/BucketTool.cpp new file mode 100644 index 0000000000..36595055c4 --- /dev/null +++ b/Userland/Applications/PixelPaint/Tools/BucketTool.cpp @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "BucketTool.h" +#include "../ImageEditor.h" +#include "../Layer.h" +#include +#include +#include +#include +#include +#include +#include +#include + +namespace PixelPaint { + +BucketTool::BucketTool() +{ +} + +BucketTool::~BucketTool() +{ +} + +static float color_distance_squared(Gfx::Color const& lhs, Gfx::Color const& rhs) +{ + int a = rhs.red() - lhs.red(); + int b = rhs.green() - lhs.green(); + int c = rhs.blue() - lhs.blue(); + return (a * a + b * b + c * c) / (255.0f * 255.0f); +} + +static void flood_fill(Gfx::Bitmap& bitmap, Gfx::IntPoint const& start_position, Color target_color, Color fill_color, int threshold) +{ + VERIFY(bitmap.bpp() == 32); + + if (target_color == fill_color) + return; + + if (!bitmap.rect().contains(start_position)) + return; + + float threshold_normalized_squared = (threshold / 100.0f) * (threshold / 100.0f); + + Queue queue; + queue.enqueue(start_position); + HashTable visited; + while (!queue.is_empty()) { + auto position = queue.dequeue(); + if (visited.contains(position)) + continue; + visited.set(position); + + auto pixel_color = bitmap.get_pixel(position.x(), position.y()); + if (color_distance_squared(pixel_color, target_color) > threshold_normalized_squared) + continue; + + bitmap.set_pixel(position.x(), position.y(), fill_color); + + if (position.x() != 0) + queue.enqueue(position.translated(-1, 0)); + + if (position.x() != bitmap.width() - 1) + queue.enqueue(position.translated(1, 0)); + + if (position.y() != 0) + queue.enqueue(position.translated(0, -1)); + + if (position.y() != bitmap.height() - 1) + queue.enqueue(position.translated(0, 1)); + } +} + +void BucketTool::on_mousedown(Layer* layer, MouseEvent& event) +{ + if (!layer) + return; + + auto& layer_event = event.layer_event(); + if (!layer->rect().contains(layer_event.position())) + return; + + GUI::Painter painter(layer->bitmap()); + auto target_color = layer->bitmap().get_pixel(layer_event.x(), layer_event.y()); + + flood_fill(layer->bitmap(), layer_event.position(), target_color, m_editor->color_for(layer_event), m_threshold); + + layer->did_modify_bitmap(); + m_editor->did_complete_action(); +} + +GUI::Widget* BucketTool::get_properties_widget() +{ + if (!m_properties_widget) { + m_properties_widget = GUI::Widget::construct(); + m_properties_widget->set_layout(); + + auto& threshold_container = m_properties_widget->add(); + threshold_container.set_fixed_height(20); + threshold_container.set_layout(); + + auto& threshold_label = threshold_container.add("Threshold:"); + threshold_label.set_text_alignment(Gfx::TextAlignment::CenterLeft); + threshold_label.set_fixed_size(80, 20); + + auto& threshold_slider = threshold_container.add(Orientation::Horizontal, "%"); + threshold_slider.set_range(0, 100); + threshold_slider.set_value(m_threshold); + + threshold_slider.on_change = [&](int value) { + m_threshold = value; + }; + set_primary_slider(&threshold_slider); + } + + return m_properties_widget.ptr(); +} + +} diff --git a/Userland/Applications/PixelPaint/Tools/BucketTool.h b/Userland/Applications/PixelPaint/Tools/BucketTool.h new file mode 100644 index 0000000000..e9844fc146 --- /dev/null +++ b/Userland/Applications/PixelPaint/Tools/BucketTool.h @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include "Tool.h" + +namespace PixelPaint { + +class BucketTool final : public Tool { +public: + BucketTool(); + virtual ~BucketTool() override; + + virtual void on_mousedown(Layer*, MouseEvent&) override; + virtual GUI::Widget* get_properties_widget() override; + +private: + RefPtr m_properties_widget; + int m_threshold { 0 }; +}; + +} diff --git a/Userland/Applications/PixelPaint/Tools/CloneTool.cpp b/Userland/Applications/PixelPaint/Tools/CloneTool.cpp new file mode 100644 index 0000000000..3e64180c58 --- /dev/null +++ b/Userland/Applications/PixelPaint/Tools/CloneTool.cpp @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2021, Mustafa Quraish + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "CloneTool.h" +#include "../ImageEditor.h" +#include "../Layer.h" +#include +#include +#include +#include +#include +#include + +namespace PixelPaint { + +void CloneTool::draw_point(Gfx::Bitmap& bitmap, Gfx::Color const&, Gfx::IntPoint const& point) +{ + if (!m_sample_location.has_value()) + return; + + auto source_point = point - m_cursor_offset.value(); + for (int y = -size(); y < size(); y++) { + for (int x = -size(); x < size(); x++) { + auto target_x = point.x() + x; + auto target_y = point.y() + y; + auto distance = point.distance_from({ target_x, target_y }); + if (target_x < 0 || target_x >= bitmap.width() || target_y < 0 || target_y >= bitmap.height()) + continue; + if (distance >= size()) + continue; + + auto source_x = source_point.x() + x; + auto source_y = source_point.y() + y; + if (source_x < 0 || source_x >= bitmap.width() || source_y < 0 || source_y >= bitmap.height()) + continue; + + auto falloff = (1.0 - double { distance / size() }) * (1.0 / (100 - hardness())); + auto pixel_color = bitmap.get_pixel(source_x, source_y); + pixel_color.set_alpha(falloff * 255); + bitmap.set_pixel(target_x, target_y, bitmap.get_pixel(target_x, target_y).blend(pixel_color)); + } + } +} + +void CloneTool::draw_line(Gfx::Bitmap& bitmap, Gfx::Color const& color, Gfx::IntPoint const& start, Gfx::IntPoint const& end) +{ + if (!m_sample_location.has_value()) + return; + BrushTool::draw_line(bitmap, color, start, end); +} + +Gfx::StandardCursor CloneTool::cursor() +{ + if (m_is_selecting_location) + return Gfx::StandardCursor::Eyedropper; + return Gfx::StandardCursor::Crosshair; +} + +void CloneTool::on_mousemove(Layer* layer, MouseEvent& event) +{ + auto& image_event = event.image_event(); + if (image_event.alt()) + return; + + if (m_cursor_offset.has_value()) { + m_sample_location = image_event.position() - m_cursor_offset.value(); + // FIXME: This is a really inefficient way to update the marker's location + m_editor->update(); + } + + BrushTool::on_mousemove(layer, event); +} + +void CloneTool::on_mousedown(Layer* layer, MouseEvent& event) +{ + auto& image_event = event.image_event(); + if (image_event.alt()) { + m_sample_location = image_event.position(); + m_cursor_offset = {}; + // FIXME: This is a really dumb way to get the marker to show up + m_editor->update(); + return; + } + + if (!m_sample_location.has_value()) + return; + + if (!m_cursor_offset.has_value()) + m_cursor_offset = event.image_event().position() - m_sample_location.value(); + + BrushTool::on_mousedown(layer, event); +} + +void CloneTool::on_second_paint(Layer const*, GUI::PaintEvent& event) +{ + if (!m_sample_location.has_value()) + return; + + GUI::Painter painter(*m_editor); + painter.add_clip_rect(event.rect()); + + auto sample_pos = m_editor->image_position_to_editor_position(m_sample_location.value()); + // We don't want the marker to be a single pixel and hide the color. + auto offset = AK::max(2, size() / 2); + Gfx::IntRect rect = { + (int)sample_pos.x() - offset, + (int)sample_pos.y() - offset, + offset * 2, + offset * 2 + }; + painter.draw_ellipse_intersecting(rect, m_marker_color, 1); +} + +void CloneTool::on_keydown(GUI::KeyEvent& event) +{ + Tool::on_keydown(event); + if (event.key() == KeyCode::Key_Alt && !m_is_selecting_location) { + m_is_selecting_location = true; + m_editor->update_tool_cursor(); + return; + } +} + +void CloneTool::on_keyup(GUI::KeyEvent& event) +{ + if (m_is_selecting_location && event.key() == KeyCode::Key_Alt) { + m_is_selecting_location = false; + m_editor->update_tool_cursor(); + return; + } +} + +GUI::Widget* CloneTool::get_properties_widget() +{ + if (!m_properties_widget) { + m_properties_widget = GUI::Widget::construct(); + m_properties_widget->set_layout(); + + auto& size_container = m_properties_widget->add(); + size_container.set_fixed_height(20); + size_container.set_layout(); + + auto& size_label = size_container.add("Size:"); + size_label.set_text_alignment(Gfx::TextAlignment::CenterLeft); + size_label.set_fixed_size(80, 20); + + auto& size_slider = size_container.add(Orientation::Horizontal, "px"); + size_slider.set_range(1, 100); + size_slider.set_value(size()); + + size_slider.on_change = [&](int value) { + set_size(value); + }; + set_primary_slider(&size_slider); + + auto& hardness_container = m_properties_widget->add(); + hardness_container.set_fixed_height(20); + hardness_container.set_layout(); + + auto& hardness_label = hardness_container.add("Hardness:"); + hardness_label.set_text_alignment(Gfx::TextAlignment::CenterLeft); + hardness_label.set_fixed_size(80, 20); + + auto& hardness_slider = hardness_container.add(Orientation::Horizontal, "%"); + hardness_slider.set_range(1, 99); + hardness_slider.on_change = [&](int value) { + set_hardness(value); + }; + hardness_slider.set_value(99); + set_secondary_slider(&hardness_slider); + } + + return m_properties_widget.ptr(); +} + +} diff --git a/Userland/Applications/PixelPaint/Tools/CloneTool.h b/Userland/Applications/PixelPaint/Tools/CloneTool.h new file mode 100644 index 0000000000..100ac1aec9 --- /dev/null +++ b/Userland/Applications/PixelPaint/Tools/CloneTool.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2021, Mustafa Quraish + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include "BrushTool.h" + +namespace PixelPaint { + +class CloneTool : public BrushTool { +public: + CloneTool() = default; + virtual ~CloneTool() override = default; + + virtual GUI::Widget* get_properties_widget() override; + virtual Gfx::StandardCursor cursor() override; + +protected: + virtual void draw_point(Gfx::Bitmap& bitmap, Gfx::Color const& color, Gfx::IntPoint const& point) override; + virtual void draw_line(Gfx::Bitmap& bitmap, Gfx::Color const& color, Gfx::IntPoint const& start, Gfx::IntPoint const& end) override; + + virtual void on_mousedown(Layer*, MouseEvent&) override; + virtual void on_mousemove(Layer*, MouseEvent&) override; + virtual void on_second_paint(Layer const*, GUI::PaintEvent&) override; + virtual void on_keydown(GUI::KeyEvent&) override; + virtual void on_keyup(GUI::KeyEvent&) override; + +private: + RefPtr m_properties_widget; + + Optional m_sample_location; + Optional m_cursor_offset; + bool m_is_selecting_location { false }; + + Gfx::Color m_marker_color { Gfx::Color::Green }; +}; + +} diff --git a/Userland/Applications/PixelPaint/Tools/EllipseTool.cpp b/Userland/Applications/PixelPaint/Tools/EllipseTool.cpp new file mode 100644 index 0000000000..d8feefb90e --- /dev/null +++ b/Userland/Applications/PixelPaint/Tools/EllipseTool.cpp @@ -0,0 +1,203 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * Copyright (c) 2021, Mustafa Quraish + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "EllipseTool.h" +#include "../ImageEditor.h" +#include "../Layer.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace PixelPaint { + +EllipseTool::EllipseTool() +{ +} + +EllipseTool::~EllipseTool() +{ +} + +void EllipseTool::draw_using(GUI::Painter& painter, Gfx::IntPoint const& start_position, Gfx::IntPoint const& end_position, int thickness) +{ + Gfx::IntRect ellipse_intersecting_rect; + if (m_draw_mode == DrawMode::FromCenter) { + auto delta = end_position - start_position; + ellipse_intersecting_rect = Gfx::IntRect::from_two_points(start_position - delta, end_position); + } else { + ellipse_intersecting_rect = Gfx::IntRect::from_two_points(start_position, end_position); + } + + switch (m_fill_mode) { + case FillMode::Outline: + painter.draw_ellipse_intersecting(ellipse_intersecting_rect, m_editor->color_for(m_drawing_button), thickness); + break; + case FillMode::Fill: + painter.fill_ellipse(ellipse_intersecting_rect, m_editor->color_for(m_drawing_button)); + break; + default: + VERIFY_NOT_REACHED(); + } +} + +void EllipseTool::on_mousedown(Layer* layer, MouseEvent& event) +{ + if (!layer) + return; + + auto& layer_event = event.layer_event(); + if (layer_event.button() != GUI::MouseButton::Left && layer_event.button() != GUI::MouseButton::Right) + return; + + if (m_drawing_button != GUI::MouseButton::None) + return; + + m_drawing_button = layer_event.button(); + m_ellipse_start_position = layer_event.position(); + m_ellipse_end_position = layer_event.position(); + m_editor->update(); +} + +void EllipseTool::on_mouseup(Layer* layer, MouseEvent& event) +{ + if (!layer) + return; + + if (event.layer_event().button() == m_drawing_button) { + GUI::Painter painter(layer->bitmap()); + draw_using(painter, m_ellipse_start_position, m_ellipse_end_position, m_thickness); + m_drawing_button = GUI::MouseButton::None; + layer->did_modify_bitmap(); + m_editor->update(); + m_editor->did_complete_action(); + } +} + +void EllipseTool::on_mousemove(Layer*, MouseEvent& event) +{ + if (m_drawing_button == GUI::MouseButton::None) + return; + + m_draw_mode = event.layer_event().alt() ? DrawMode::FromCenter : DrawMode::FromCorner; + + if (event.layer_event().shift()) + m_ellipse_end_position = m_ellipse_start_position.end_point_for_aspect_ratio(event.layer_event().position(), 1.0); + else if (m_aspect_ratio.has_value()) + m_ellipse_end_position = m_ellipse_start_position.end_point_for_aspect_ratio(event.layer_event().position(), m_aspect_ratio.value()); + else + m_ellipse_end_position = event.layer_event().position(); + + m_editor->update(); +} + +void EllipseTool::on_second_paint(Layer const* layer, GUI::PaintEvent& event) +{ + if (!layer || m_drawing_button == GUI::MouseButton::None) + return; + + GUI::Painter painter(*m_editor); + painter.add_clip_rect(event.rect()); + auto preview_start = m_editor->layer_position_to_editor_position(*layer, m_ellipse_start_position).to_type(); + auto preview_end = m_editor->layer_position_to_editor_position(*layer, m_ellipse_end_position).to_type(); + draw_using(painter, preview_start, preview_end, AK::max(m_thickness * m_editor->scale(), 1)); +} + +void EllipseTool::on_keydown(GUI::KeyEvent& event) +{ + Tool::on_keydown(event); + if (event.key() == Key_Escape && m_drawing_button != GUI::MouseButton::None) { + m_drawing_button = GUI::MouseButton::None; + m_editor->update(); + event.accept(); + } +} + +GUI::Widget* EllipseTool::get_properties_widget() +{ + if (!m_properties_widget) { + m_properties_widget = GUI::Widget::construct(); + m_properties_widget->set_layout(); + + auto& thickness_container = m_properties_widget->add(); + thickness_container.set_fixed_height(20); + thickness_container.set_layout(); + + auto& thickness_label = thickness_container.add("Thickness:"); + thickness_label.set_text_alignment(Gfx::TextAlignment::CenterLeft); + thickness_label.set_fixed_size(80, 20); + + auto& thickness_slider = thickness_container.add(Orientation::Horizontal, "px"); + thickness_slider.set_range(1, 10); + thickness_slider.set_value(m_thickness); + + thickness_slider.on_change = [&](int value) { + m_thickness = value; + }; + set_primary_slider(&thickness_slider); + + auto& mode_container = m_properties_widget->add(); + mode_container.set_fixed_height(46); + mode_container.set_layout(); + auto& mode_label = mode_container.add("Mode:"); + mode_label.set_text_alignment(Gfx::TextAlignment::CenterLeft); + mode_label.set_fixed_size(80, 20); + + auto& mode_radio_container = mode_container.add(); + mode_radio_container.set_layout(); + auto& outline_mode_radio = mode_radio_container.add("Outline"); + auto& fill_mode_radio = mode_radio_container.add("Fill"); + + outline_mode_radio.on_checked = [&](bool) { + m_fill_mode = FillMode::Outline; + }; + fill_mode_radio.on_checked = [&](bool) { + m_fill_mode = FillMode::Fill; + }; + + outline_mode_radio.set_checked(true); + + auto& aspect_container = m_properties_widget->add(); + aspect_container.set_fixed_height(20); + aspect_container.set_layout(); + + auto& aspect_label = aspect_container.add("Aspect Ratio:"); + aspect_label.set_text_alignment(Gfx::TextAlignment::CenterLeft); + aspect_label.set_fixed_size(80, 20); + + m_aspect_w_textbox = aspect_container.add(); + m_aspect_w_textbox->set_fixed_height(20); + m_aspect_w_textbox->set_fixed_width(25); + m_aspect_w_textbox->on_change = [&] { + auto x = m_aspect_w_textbox->text().to_int().value_or(0); + auto y = m_aspect_h_textbox->text().to_int().value_or(0); + if (x > 0 && y > 0) { + m_aspect_ratio = (float)x / (float)y; + } else { + m_aspect_ratio = {}; + } + }; + + auto& multiply_label = aspect_container.add("x"); + multiply_label.set_text_alignment(Gfx::TextAlignment::Center); + multiply_label.set_fixed_size(10, 20); + + m_aspect_h_textbox = aspect_container.add(); + m_aspect_h_textbox->set_fixed_height(20); + m_aspect_h_textbox->set_fixed_width(25); + m_aspect_h_textbox->on_change = [&] { m_aspect_w_textbox->on_change(); }; + } + + return m_properties_widget.ptr(); +} + +} diff --git a/Userland/Applications/PixelPaint/Tools/EllipseTool.h b/Userland/Applications/PixelPaint/Tools/EllipseTool.h new file mode 100644 index 0000000000..885ca2e781 --- /dev/null +++ b/Userland/Applications/PixelPaint/Tools/EllipseTool.h @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * Copyright (c) 2021, Mustafa Quraish + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include "Tool.h" +#include +#include + +namespace PixelPaint { + +class EllipseTool final : public Tool { +public: + EllipseTool(); + virtual ~EllipseTool() override; + + virtual void on_mousedown(Layer*, MouseEvent&) override; + virtual void on_mousemove(Layer*, MouseEvent&) override; + virtual void on_mouseup(Layer*, MouseEvent&) override; + virtual void on_second_paint(Layer const*, GUI::PaintEvent&) override; + virtual void on_keydown(GUI::KeyEvent&) override; + virtual GUI::Widget* get_properties_widget() override; + virtual Gfx::StandardCursor cursor() override { return Gfx::StandardCursor::Crosshair; } + +private: + enum class FillMode { + Outline, + Fill, + }; + + enum class DrawMode { + FromCenter, + FromCorner, + }; + + void draw_using(GUI::Painter&, Gfx::IntPoint const& start_position, Gfx::IntPoint const& end_position, int thickness); + + RefPtr m_properties_widget; + RefPtr m_aspect_w_textbox; + RefPtr m_aspect_h_textbox; + + GUI::MouseButton m_drawing_button { GUI::MouseButton::None }; + Gfx::IntPoint m_ellipse_start_position; + Gfx::IntPoint m_ellipse_end_position; + int m_thickness { 1 }; + FillMode m_fill_mode { FillMode::Outline }; + DrawMode m_draw_mode { DrawMode::FromCorner }; + Optional m_aspect_ratio; +}; + +} diff --git a/Userland/Applications/PixelPaint/Tools/EraseTool.cpp b/Userland/Applications/PixelPaint/Tools/EraseTool.cpp new file mode 100644 index 0000000000..8ee0642729 --- /dev/null +++ b/Userland/Applications/PixelPaint/Tools/EraseTool.cpp @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * Copyright (c) 2021, Mustafa Quraish + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "EraseTool.h" +#include "../ImageEditor.h" +#include "../Layer.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace PixelPaint { + +EraseTool::EraseTool() +{ +} + +EraseTool::~EraseTool() +{ +} + +Color EraseTool::color_for(GUI::MouseEvent const&) +{ + if (m_use_secondary_color) + return m_editor->secondary_color(); + return Color(255, 255, 255, 0); +} + +void EraseTool::draw_point(Gfx::Bitmap& bitmap, Gfx::Color const& color, Gfx::IntPoint const& point) +{ + if (m_draw_mode == DrawMode::Pencil) { + int radius = size() / 2; + Gfx::IntRect rect { point.x() - radius, point.y() - radius, size(), size() }; + GUI::Painter painter(bitmap); + painter.clear_rect(rect, color); + } else { + for (int y = point.y() - size(); y < point.y() + size(); y++) { + for (int x = point.x() - size(); x < point.x() + size(); x++) { + auto distance = point.distance_from({ x, y }); + if (x < 0 || x >= bitmap.width() || y < 0 || y >= bitmap.height()) + continue; + if (distance >= size()) + continue; + auto old_color = bitmap.get_pixel(x, y); + auto falloff = (1.0 - double { distance / size() }) * (1.0 / (100 - hardness())); + auto new_color = old_color.interpolate(color, falloff); + bitmap.set_pixel(x, y, new_color); + } + } + } +} + +GUI::Widget* EraseTool::get_properties_widget() +{ + if (!m_properties_widget) { + m_properties_widget = GUI::Widget::construct(); + m_properties_widget->set_layout(); + + auto& size_container = m_properties_widget->add(); + size_container.set_fixed_height(20); + size_container.set_layout(); + + auto& size_label = size_container.add("Size:"); + size_label.set_text_alignment(Gfx::TextAlignment::CenterLeft); + size_label.set_fixed_size(80, 20); + + auto& size_slider = size_container.add(Orientation::Horizontal, "px"); + size_slider.set_range(1, 100); + size_slider.set_value(size()); + + size_slider.on_change = [&](int value) { + set_size(value); + }; + set_primary_slider(&size_slider); + + auto& hardness_container = m_properties_widget->add(); + hardness_container.set_fixed_height(20); + hardness_container.set_layout(); + + auto& hardness_label = hardness_container.add("Hardness:"); + hardness_label.set_text_alignment(Gfx::TextAlignment::CenterLeft); + hardness_label.set_fixed_size(80, 20); + + auto& hardness_slider = hardness_container.add(Orientation::Horizontal, "%"); + hardness_slider.set_range(1, 99); + hardness_slider.set_value(hardness()); + + hardness_slider.on_change = [&](int value) { + set_hardness(value); + }; + set_secondary_slider(&hardness_slider); + + auto& secondary_color_container = m_properties_widget->add(); + secondary_color_container.set_fixed_height(20); + secondary_color_container.set_layout(); + + auto& use_secondary_color_checkbox = secondary_color_container.add(); + use_secondary_color_checkbox.set_checked(m_use_secondary_color); + use_secondary_color_checkbox.set_text("Use secondary color"); + use_secondary_color_checkbox.on_checked = [&](bool checked) { + m_use_secondary_color = checked; + }; + + auto& mode_container = m_properties_widget->add(); + mode_container.set_fixed_height(46); + mode_container.set_layout(); + auto& mode_label = mode_container.add("Draw Mode:"); + mode_label.set_text_alignment(Gfx::TextAlignment::CenterLeft); + mode_label.set_fixed_size(80, 20); + + auto& mode_radio_container = mode_container.add(); + mode_radio_container.set_layout(); + auto& pencil_mode_radio = mode_radio_container.add("Pencil"); + auto& brush_mode_radio = mode_radio_container.add("Brush"); + + pencil_mode_radio.on_checked = [&](bool) { + m_draw_mode = DrawMode::Pencil; + hardness_slider.set_enabled(false); + }; + brush_mode_radio.on_checked = [&](bool) { + m_draw_mode = DrawMode::Brush; + hardness_slider.set_enabled(true); + }; + + pencil_mode_radio.set_checked(true); + } + + return m_properties_widget.ptr(); +} + +} diff --git a/Userland/Applications/PixelPaint/Tools/EraseTool.h b/Userland/Applications/PixelPaint/Tools/EraseTool.h new file mode 100644 index 0000000000..6fbf048326 --- /dev/null +++ b/Userland/Applications/PixelPaint/Tools/EraseTool.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * Copyright (c) 2021, Mustafa Quraish + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include "BrushTool.h" +#include +#include +#include + +namespace PixelPaint { + +class EraseTool final : public BrushTool { +public: + EraseTool(); + virtual ~EraseTool() override; + + virtual GUI::Widget* get_properties_widget() override; + +protected: + virtual Color color_for(GUI::MouseEvent const& event) override; + virtual void draw_point(Gfx::Bitmap& bitmap, Gfx::Color const& color, Gfx::IntPoint const& point) override; + +private: + RefPtr m_properties_widget; + + enum class DrawMode { + Pencil, + Brush, + }; + DrawMode m_draw_mode { DrawMode::Brush }; + bool m_use_secondary_color { false }; +}; + +} diff --git a/Userland/Applications/PixelPaint/Tools/GuideTool.cpp b/Userland/Applications/PixelPaint/Tools/GuideTool.cpp new file mode 100644 index 0000000000..67562651b1 --- /dev/null +++ b/Userland/Applications/PixelPaint/Tools/GuideTool.cpp @@ -0,0 +1,214 @@ +/* + * Copyright (c) 2021, Tobias Christiansen + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "GuideTool.h" +#include "../EditGuideDialog.h" +#include +#include +#include +#include +#include +#include + +namespace PixelPaint { + +GuideTool::GuideTool() +{ +} + +GuideTool::~GuideTool() +{ +} + +RefPtr GuideTool::closest_guide(const Gfx::IntPoint& point) +{ + auto guides = editor()->guides(); + Guide* closest_guide = nullptr; + int closest_guide_distance = NumericLimits::max(); + + for (auto& guide : guides) { + int relevant_position = 0; + if (guide.orientation() == Guide::Orientation::Horizontal) + relevant_position = point.y(); + else if (guide.orientation() == Guide::Orientation::Vertical) + relevant_position = point.x(); + + auto distance = abs(relevant_position - (int)guide.offset()); + + if (distance < closest_guide_distance) { + closest_guide = &guide; + closest_guide_distance = distance; + } + } + + if (closest_guide_distance < 20) + return closest_guide; + return nullptr; +} + +void GuideTool::on_mousedown(Layer*, MouseEvent& event) +{ + if (!m_editor) + return; + + auto& image_event = event.image_event(); + + if (image_event.button() != GUI::MouseButton::Left) + return; + + m_editor->set_guide_visibility(true); + + RefPtr new_guide; + if (image_event.position().x() < 0 || image_event.position().x() > editor()->image().size().width()) { + new_guide = make_ref_counted(Guide::Orientation::Vertical, image_event.position().x()); + } else if (image_event.position().y() < 0 || image_event.position().y() > editor()->image().size().height()) { + new_guide = make_ref_counted(Guide::Orientation::Horizontal, image_event.position().y()); + } + + if (new_guide) { + m_selected_guide = new_guide; + m_guide_origin = 0; + editor()->add_guide(new_guide.release_nonnull()); + return; + } + + m_event_origin = image_event.position(); + + m_selected_guide = closest_guide(image_event.position()); + + if (m_selected_guide) { + m_guide_origin = m_selected_guide->offset(); + GUI::Application::the()->show_tooltip_immediately(String::formatted("{}", m_guide_origin), GUI::Application::the()->tooltip_source_widget()); + } +} + +void GuideTool::on_mouseup(Layer*, MouseEvent&) +{ + m_guide_origin = 0; + m_event_origin = { 0, 0 }; + GUI::Application::the()->hide_tooltip(); + + if (!m_selected_guide) + return; + + if (m_selected_guide->offset() < 0 + || (m_selected_guide->orientation() == Guide::Orientation::Horizontal && m_selected_guide->offset() > editor()->image().size().height()) + || (m_selected_guide->orientation() == Guide::Orientation::Vertical && m_selected_guide->offset() > editor()->image().size().width())) { + editor()->remove_guide(*m_selected_guide); + editor()->layers_did_change(); + } + + m_selected_guide = nullptr; +} + +void GuideTool::on_mousemove(Layer*, MouseEvent& event) +{ + if (!m_selected_guide) + return; + + auto& image_event = event.image_event(); + auto delta = image_event.position() - m_event_origin; + + auto relevant_offset = 0; + if (m_selected_guide->orientation() == Guide::Orientation::Horizontal) + relevant_offset = delta.y(); + else if (m_selected_guide->orientation() == Guide::Orientation::Vertical) + relevant_offset = delta.x(); + + auto new_offset = (float)relevant_offset + m_guide_origin; + + if (image_event.shift() && m_snap_size > 0) { + float snap_size_half = m_snap_size / 2.0; + new_offset -= fmodf(new_offset + snap_size_half, m_snap_size) - snap_size_half; + } + + m_selected_guide->set_offset(new_offset); + + GUI::Application::the()->show_tooltip_immediately(String::formatted("{}", new_offset), GUI::Application::the()->tooltip_source_widget()); + + editor()->layers_did_change(); +} + +void GuideTool::on_context_menu(Layer*, GUI::ContextMenuEvent& event) +{ + if (!m_editor) + return; + + m_editor->set_guide_visibility(true); + + if (!m_context_menu) { + m_context_menu = GUI::Menu::construct(); + m_context_menu->add_action(GUI::Action::create( + "Set &Offset", Gfx::Bitmap::try_load_from_file("/res/icons/16x16/gear.png"), [this](auto&) { + if (!m_context_menu_guide) + return; + auto dialog = EditGuideDialog::construct( + editor()->window(), + String::formatted("{}", m_context_menu_guide->offset()), + m_context_menu_guide->orientation()); + if (dialog->exec() != GUI::Dialog::ExecOK) + return; + auto offset = dialog->offset_as_pixel(*editor()); + if (!offset.has_value()) + return; + m_context_menu_guide->set_offset(offset.release_value()); + m_context_menu_guide->set_orientation(dialog->orientation()); + editor()->layers_did_change(); + }, + editor())); + m_context_menu->add_action(GUI::Action::create( + "&Delete Guide", Gfx::Bitmap::try_load_from_file("/res/icons/16x16/delete.png"), [this](auto&) { + if (!m_context_menu_guide) + return; + editor()->remove_guide(*m_context_menu_guide); + m_selected_guide = nullptr; + m_guide_origin = 0; + editor()->layers_did_change(); + }, + editor())); + } + + auto image_position = editor()->editor_position_to_image_position(event.position()); + m_context_menu_guide = closest_guide({ (int)image_position.x(), (int)image_position.y() }); + if (m_context_menu_guide) + m_context_menu->popup(event.screen_position()); +} + +void GuideTool::on_tool_activation() +{ + if (m_editor) + m_editor->set_guide_visibility(true); +} + +GUI::Widget* GuideTool::get_properties_widget() +{ + if (!m_properties_widget) { + m_properties_widget = GUI::Widget::construct(); + m_properties_widget->set_layout(); + + auto& snapping_container = m_properties_widget->add(); + snapping_container.set_fixed_height(20); + snapping_container.set_layout(); + + auto& snapping_label = snapping_container.add("Snap offset:"); + snapping_label.set_text_alignment(Gfx::TextAlignment::CenterLeft); + snapping_label.set_fixed_size(80, 20); + snapping_label.set_tooltip("Press Shift to snap"); + + auto& snapping_slider = snapping_container.add(Orientation::Horizontal, "px"); + snapping_slider.set_range(0, 50); + snapping_slider.set_value(m_snap_size); + + snapping_slider.on_change = [&](int value) { + m_snap_size = value; + }; + set_primary_slider(&snapping_slider); + } + + return m_properties_widget.ptr(); +} + +} diff --git a/Userland/Applications/PixelPaint/Tools/GuideTool.h b/Userland/Applications/PixelPaint/Tools/GuideTool.h new file mode 100644 index 0000000000..33faae84a4 --- /dev/null +++ b/Userland/Applications/PixelPaint/Tools/GuideTool.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2021, Tobias Christiansen + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include "../Guide.h" +#include "Tool.h" +#include + +namespace PixelPaint { + +class GuideTool final : public Tool { +public: + GuideTool(); + + virtual ~GuideTool() override; + + virtual void on_mousedown(Layer*, MouseEvent&) override; + virtual void on_mousemove(Layer*, MouseEvent&) override; + virtual void on_mouseup(Layer*, MouseEvent&) override; + virtual void on_context_menu(Layer*, GUI::ContextMenuEvent&) override; + + virtual void on_tool_activation() override; + + virtual GUI::Widget* get_properties_widget() override; + virtual Gfx::StandardCursor cursor() override { return Gfx::StandardCursor::Crosshair; } + +private: + RefPtr closest_guide(Gfx::IntPoint const&); + + RefPtr m_properties_widget; + + RefPtr m_selected_guide; + RefPtr m_context_menu_guide; + Gfx::IntPoint m_event_origin; + float m_guide_origin { 0 }; + RefPtr m_context_menu; + int m_snap_size { 10 }; +}; +} diff --git a/Userland/Applications/PixelPaint/Tools/LineTool.cpp b/Userland/Applications/PixelPaint/Tools/LineTool.cpp new file mode 100644 index 0000000000..3ba9aeed9c --- /dev/null +++ b/Userland/Applications/PixelPaint/Tools/LineTool.cpp @@ -0,0 +1,153 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * Copyright (c) 2021, Mustafa Quraish + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "LineTool.h" +#include "../ImageEditor.h" +#include "../Layer.h" +#include +#include +#include +#include +#include +#include +#include + +namespace PixelPaint { + +static Gfx::IntPoint constrain_line_angle(Gfx::IntPoint const& start_pos, Gfx::IntPoint const& end_pos, float angle_increment) +{ + float current_angle = AK::atan2(end_pos.y() - start_pos.y(), end_pos.x() - start_pos.x()) + float { M_PI * 2 }; + + float constrained_angle = ((int)((current_angle + angle_increment / 2) / angle_increment)) * angle_increment; + + auto diff = end_pos - start_pos; + float line_length = AK::hypot(diff.x(), diff.y()); + + return { start_pos.x() + (int)(AK::cos(constrained_angle) * line_length), + start_pos.y() + (int)(AK::sin(constrained_angle) * line_length) }; +} + +LineTool::LineTool() +{ +} + +LineTool::~LineTool() +{ +} + +void LineTool::on_mousedown(Layer* layer, MouseEvent& event) +{ + if (!layer) + return; + + auto& layer_event = event.layer_event(); + if (layer_event.button() != GUI::MouseButton::Left && layer_event.button() != GUI::MouseButton::Right) + return; + + if (m_drawing_button != GUI::MouseButton::None) + return; + + m_drawing_button = layer_event.button(); + + m_drag_start_position = layer_event.position(); + m_line_start_position = layer_event.position(); + m_line_end_position = layer_event.position(); + + m_editor->update(); +} + +void LineTool::on_mouseup(Layer* layer, MouseEvent& event) +{ + if (!layer) + return; + + auto& layer_event = event.layer_event(); + if (layer_event.button() == m_drawing_button) { + GUI::Painter painter(layer->bitmap()); + painter.draw_line(m_line_start_position, m_line_end_position, m_editor->color_for(m_drawing_button), m_thickness); + m_drawing_button = GUI::MouseButton::None; + layer->did_modify_bitmap(); + m_editor->update(); + m_editor->did_complete_action(); + } +} + +void LineTool::on_mousemove(Layer* layer, MouseEvent& event) +{ + if (!layer) + return; + + auto& layer_event = event.layer_event(); + if (m_drawing_button == GUI::MouseButton::None) + return; + + if (layer_event.shift()) { + constexpr auto ANGLE_STEP = M_PI / 8; + m_line_end_position = constrain_line_angle(m_drag_start_position, layer_event.position(), ANGLE_STEP); + } else { + m_line_end_position = layer_event.position(); + } + + if (layer_event.alt()) { + m_line_start_position = m_drag_start_position + (m_drag_start_position - m_line_end_position); + } else { + m_line_start_position = m_drag_start_position; + } + + m_editor->update(); +} + +void LineTool::on_second_paint(Layer const* layer, GUI::PaintEvent& event) +{ + if (!layer || m_drawing_button == GUI::MouseButton::None) + return; + + GUI::Painter painter(*m_editor); + painter.add_clip_rect(event.rect()); + auto preview_start = editor_stroke_position(m_line_start_position, m_thickness); + auto preview_end = editor_stroke_position(m_line_end_position, m_thickness); + painter.draw_line(preview_start, preview_end, m_editor->color_for(m_drawing_button), AK::max(m_thickness * m_editor->scale(), 1)); +} + +void LineTool::on_keydown(GUI::KeyEvent& event) +{ + Tool::on_keydown(event); + if (event.key() == Key_Escape && m_drawing_button != GUI::MouseButton::None) { + m_drawing_button = GUI::MouseButton::None; + m_editor->update(); + event.accept(); + } +} + +GUI::Widget* LineTool::get_properties_widget() +{ + if (!m_properties_widget) { + m_properties_widget = GUI::Widget::construct(); + m_properties_widget->set_layout(); + + auto& thickness_container = m_properties_widget->add(); + thickness_container.set_fixed_height(20); + thickness_container.set_layout(); + + auto& thickness_label = thickness_container.add("Thickness:"); + thickness_label.set_text_alignment(Gfx::TextAlignment::CenterLeft); + thickness_label.set_fixed_size(80, 20); + + auto& thickness_slider = thickness_container.add(Orientation::Horizontal, "px"); + thickness_slider.set_range(1, 10); + thickness_slider.set_value(m_thickness); + + thickness_slider.on_change = [&](int value) { + m_thickness = value; + }; + set_primary_slider(&thickness_slider); + } + + return m_properties_widget.ptr(); +} + +} diff --git a/Userland/Applications/PixelPaint/Tools/LineTool.h b/Userland/Applications/PixelPaint/Tools/LineTool.h new file mode 100644 index 0000000000..fc99d07b1b --- /dev/null +++ b/Userland/Applications/PixelPaint/Tools/LineTool.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include "Tool.h" +#include +#include + +namespace PixelPaint { + +class LineTool final : public Tool { +public: + LineTool(); + virtual ~LineTool() override; + + virtual void on_mousedown(Layer*, MouseEvent&) override; + virtual void on_mousemove(Layer*, MouseEvent&) override; + virtual void on_mouseup(Layer*, MouseEvent&) override; + virtual void on_second_paint(Layer const*, GUI::PaintEvent&) override; + virtual void on_keydown(GUI::KeyEvent&) override; + virtual GUI::Widget* get_properties_widget() override; + virtual Gfx::StandardCursor cursor() override { return Gfx::StandardCursor::Crosshair; } + +private: + RefPtr m_properties_widget; + + GUI::MouseButton m_drawing_button { GUI::MouseButton::None }; + Gfx::IntPoint m_drag_start_position; + Gfx::IntPoint m_line_start_position; + Gfx::IntPoint m_line_end_position; + int m_thickness { 1 }; +}; + +} diff --git a/Userland/Applications/PixelPaint/Tools/MoveTool.cpp b/Userland/Applications/PixelPaint/Tools/MoveTool.cpp new file mode 100644 index 0000000000..c09172e929 --- /dev/null +++ b/Userland/Applications/PixelPaint/Tools/MoveTool.cpp @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2020, Andreas Kling + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "MoveTool.h" +#include "../Image.h" +#include "../ImageEditor.h" +#include "../Layer.h" +#include +#include +#include + +namespace PixelPaint { + +MoveTool::MoveTool() +{ +} + +MoveTool::~MoveTool() +{ +} + +void MoveTool::on_mousedown(Layer* layer, MouseEvent& event) +{ + if (event.image_event().button() == GUI::MouseButton::Right && !m_is_panning) { + m_is_panning = true; + m_event_origin = event.raw_event().position(); + m_saved_pan_origin = m_editor->pan_origin(); + m_editor->set_override_cursor(Gfx::StandardCursor::Drag); + return; + } + + if (!layer) + return; + + auto& layer_event = event.layer_event(); + auto& image_event = event.image_event(); + if (layer_event.button() != GUI::MouseButton::Left) + return; + if (!layer->rect().contains(layer_event.position())) + return; + m_layer_being_moved = *layer; + m_event_origin = image_event.position(); + m_layer_origin = layer->location(); +} + +void MoveTool::on_mousemove(Layer* layer, MouseEvent& event) +{ + if (m_is_panning) { + auto& raw_event = event.raw_event(); + auto delta = raw_event.position() - m_event_origin; + m_editor->set_pan_origin(m_saved_pan_origin.translated( + -delta.x(), + -delta.y())); + return; + } + + if (!layer) + return; + + auto& image_event = event.image_event(); + if (!m_layer_being_moved) + return; + auto delta = image_event.position() - m_event_origin; + m_layer_being_moved->set_location(m_layer_origin.translated(delta)); + m_editor->layers_did_change(); +} + +void MoveTool::on_mouseup(Layer* layer, MouseEvent& event) +{ + if (event.image_event().button() == GUI::MouseButton::Right && m_is_panning) { + m_is_panning = false; + m_editor->set_override_cursor(cursor()); + return; + } + + if (!layer) + return; + + auto& layer_event = event.layer_event(); + if (layer_event.button() != GUI::MouseButton::Left) + return; + m_layer_being_moved = nullptr; + m_editor->did_complete_action(); +} + +void MoveTool::on_keydown(GUI::KeyEvent& event) +{ + if (event.modifiers() != 0) + return; + + auto* layer = m_editor->active_layer(); + if (!layer) + return; + + auto new_location = layer->location(); + + switch (event.key()) { + case Key_Up: + new_location.translate_by(0, -1); + break; + case Key_Down: + new_location.translate_by(0, 1); + break; + case Key_Left: + new_location.translate_by(-1, 0); + break; + case Key_Right: + new_location.translate_by(1, 0); + break; + default: + return; + } + + layer->set_location(new_location); + m_editor->layers_did_change(); +} + +} diff --git a/Userland/Applications/PixelPaint/Tools/MoveTool.h b/Userland/Applications/PixelPaint/Tools/MoveTool.h new file mode 100644 index 0000000000..93631ff077 --- /dev/null +++ b/Userland/Applications/PixelPaint/Tools/MoveTool.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2020, Andreas Kling + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include "Tool.h" + +namespace PixelPaint { + +class MoveTool final : public Tool { +public: + MoveTool(); + virtual ~MoveTool() override; + + virtual void on_mousedown(Layer*, MouseEvent&) override; + virtual void on_mousemove(Layer*, MouseEvent&) override; + virtual void on_mouseup(Layer*, MouseEvent&) override; + virtual void on_keydown(GUI::KeyEvent&) override; + virtual Gfx::StandardCursor cursor() override { return Gfx::StandardCursor::Move; } + +private: + RefPtr m_layer_being_moved; + Gfx::IntPoint m_event_origin; + Gfx::IntPoint m_layer_origin; + + bool m_is_panning { false }; + Gfx::FloatPoint m_saved_pan_origin; +}; + +} diff --git a/Userland/Applications/PixelPaint/Tools/PenTool.cpp b/Userland/Applications/PixelPaint/Tools/PenTool.cpp new file mode 100644 index 0000000000..587755ae8a --- /dev/null +++ b/Userland/Applications/PixelPaint/Tools/PenTool.cpp @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * Copyright (c) 2021, Mustafa Quraish + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "PenTool.h" +#include "../ImageEditor.h" +#include "../Layer.h" +#include +#include +#include +#include +#include +#include + +namespace PixelPaint { + +PenTool::PenTool() +{ + set_size(1); +} + +PenTool::~PenTool() +{ +} + +void PenTool::draw_point(Gfx::Bitmap& bitmap, Gfx::Color const& color, Gfx::IntPoint const& point) +{ + GUI::Painter painter(bitmap); + painter.draw_line(point, point, color, size()); +} + +void PenTool::draw_line(Gfx::Bitmap& bitmap, Gfx::Color const& color, Gfx::IntPoint const& start, Gfx::IntPoint const& end) +{ + GUI::Painter painter(bitmap); + painter.draw_line(start, end, color, size()); +} + +GUI::Widget* PenTool::get_properties_widget() +{ + if (!m_properties_widget) { + m_properties_widget = GUI::Widget::construct(); + m_properties_widget->set_layout(); + + auto& size_container = m_properties_widget->add(); + size_container.set_fixed_height(20); + size_container.set_layout(); + + auto& size_label = size_container.add("Thickness:"); + size_label.set_text_alignment(Gfx::TextAlignment::CenterLeft); + size_label.set_fixed_size(80, 20); + + auto& size_slider = size_container.add(Orientation::Horizontal, "px"); + size_slider.set_range(1, 20); + size_slider.set_value(size()); + + size_slider.on_change = [&](int value) { + set_size(value); + }; + set_primary_slider(&size_slider); + } + + return m_properties_widget.ptr(); +} + +} diff --git a/Userland/Applications/PixelPaint/Tools/PenTool.h b/Userland/Applications/PixelPaint/Tools/PenTool.h new file mode 100644 index 0000000000..5ac5f23c0a --- /dev/null +++ b/Userland/Applications/PixelPaint/Tools/PenTool.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * Copyright (c) 2021, Mustafa Quraish + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include "BrushTool.h" +#include +#include + +namespace PixelPaint { + +class PenTool final : public BrushTool { +public: + PenTool(); + virtual ~PenTool() override; + + virtual GUI::Widget* get_properties_widget() override; + +protected: + virtual void draw_point(Gfx::Bitmap& bitmap, Gfx::Color const& color, Gfx::IntPoint const& point) override; + virtual void draw_line(Gfx::Bitmap& bitmap, Gfx::Color const& color, Gfx::IntPoint const& start, Gfx::IntPoint const& end) override; + +private: + RefPtr m_properties_widget; +}; + +} diff --git a/Userland/Applications/PixelPaint/Tools/PickerTool.cpp b/Userland/Applications/PixelPaint/Tools/PickerTool.cpp new file mode 100644 index 0000000000..955beb545b --- /dev/null +++ b/Userland/Applications/PixelPaint/Tools/PickerTool.cpp @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * Copyright (c) 2021, Mustafa Quraish + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "PickerTool.h" +#include "../ImageEditor.h" +#include "../Layer.h" +#include +#include + +namespace PixelPaint { + +PickerTool::PickerTool() +{ +} + +PickerTool::~PickerTool() +{ +} + +void PickerTool::on_mousedown(Layer* layer, MouseEvent& event) +{ + auto& position = event.layer_event().position(); + + Color color; + if (m_sample_all_layers) { + color = m_editor->image().color_at(position); + } else { + if (!layer || !layer->rect().contains(position)) + return; + color = layer->bitmap().get_pixel(position); + } + + // We picked a transparent pixel, do nothing. + if (!color.alpha()) + return; + + if (event.layer_event().button() == GUI::MouseButton::Left) + m_editor->set_primary_color(color); + else if (event.layer_event().button() == GUI::MouseButton::Right) + m_editor->set_secondary_color(color); +} + +GUI::Widget* PickerTool::get_properties_widget() +{ + if (!m_properties_widget) { + m_properties_widget = GUI::Widget::construct(); + m_properties_widget->set_layout(); + + auto& sample_checkbox = m_properties_widget->add("Sample all layers"); + sample_checkbox.set_checked(m_sample_all_layers); + sample_checkbox.on_checked = [&](bool value) { + m_sample_all_layers = value; + }; + } + + return m_properties_widget.ptr(); +} + +} diff --git a/Userland/Applications/PixelPaint/Tools/PickerTool.h b/Userland/Applications/PixelPaint/Tools/PickerTool.h new file mode 100644 index 0000000000..5a558bfbc9 --- /dev/null +++ b/Userland/Applications/PixelPaint/Tools/PickerTool.h @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * Copyright (c) 2021, Mustafa Quraish + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include "Tool.h" + +namespace PixelPaint { + +class PickerTool final : public Tool { +public: + PickerTool(); + virtual ~PickerTool() override; + + virtual void on_mousedown(Layer*, MouseEvent&) override; + + virtual GUI::Widget* get_properties_widget() override; + virtual Gfx::StandardCursor cursor() override { return Gfx::StandardCursor::Eyedropper; } + +private: + RefPtr m_properties_widget; + bool m_sample_all_layers { false }; +}; + +} diff --git a/Userland/Applications/PixelPaint/Tools/RectangleSelectTool.cpp b/Userland/Applications/PixelPaint/Tools/RectangleSelectTool.cpp new file mode 100644 index 0000000000..ace0316239 --- /dev/null +++ b/Userland/Applications/PixelPaint/Tools/RectangleSelectTool.cpp @@ -0,0 +1,212 @@ +/* + * Copyright (c) 2021, Andreas Kling + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "RectangleSelectTool.h" +#include "../ImageEditor.h" +#include "../Layer.h" +#include +#include +#include +#include +#include +#include +#include +#include + +namespace PixelPaint { + +RectangleSelectTool::RectangleSelectTool() +{ +} + +RectangleSelectTool::~RectangleSelectTool() +{ +} + +void RectangleSelectTool::on_mousedown(Layer*, MouseEvent& event) +{ + auto& image_event = event.image_event(); + if (image_event.button() != GUI::MouseButton::Left) + return; + + m_selecting = true; + m_editor->selection().begin_interactive_selection(); + + m_selection_start = image_event.position(); + m_selection_end = image_event.position(); + m_editor->update(); +} + +void RectangleSelectTool::on_mousemove(Layer*, MouseEvent& event) +{ + auto& image_event = event.image_event(); + if (!m_selecting) + return; + + if (m_moving_mode != MovingMode::None) { + auto delta = m_selection_end - image_event.position(); + if (m_moving_mode == MovingMode::MovingOrigin) + m_selection_start -= delta; + else if (m_moving_mode == MovingMode::AroundCenter) + m_selection_start += delta; + } + + m_selection_end = image_event.position(); + m_editor->update(); +} + +void RectangleSelectTool::on_mouseup(Layer*, MouseEvent& event) +{ + auto& image_event = event.image_event(); + if (!m_selecting || image_event.button() != GUI::MouseButton::Left) + return; + + m_selecting = false; + m_editor->selection().end_interactive_selection(); + + m_editor->update(); + + auto rect_in_image = Gfx::IntRect::from_two_points(m_selection_start, m_selection_end); + auto mask = Mask::full(rect_in_image); + + auto feathering = ((mask.bounding_rect().size().to_type() * .5f) * m_edge_feathering).to_type(); + + // Multiply the alpha instead of setting it to ensure corners are feathered correctly + auto multiply_alpha = [&mask](int x, int y, float alpha) { + Gfx::IntPoint point { x, y }; + point += mask.bounding_rect().top_left(); + + float old_alpha = mask.getf(point); + mask.setf(point, old_alpha * alpha); + }; + + // Horizontal feathering + for (int offset = 0; offset < feathering.width(); offset++) { + // Add 1 to offset before dividing to ensure the first pixel won't always be transparent + float alpha = (float)(offset + 1) / (float)feathering.width(); + + for (int y = 0; y < mask.bounding_rect().height(); y++) { + multiply_alpha(offset, y, alpha); + multiply_alpha(mask.bounding_rect().width() - offset - 1, y, alpha); + } + } + + // Vertical feathering + for (int offset = 0; offset < feathering.height(); offset++) { + // Add 1 to offset before dividing to ensure the first pixel won't always be transparent + float alpha = (float)(offset + 1) / (float)feathering.height(); + + for (int x = 0; x < mask.bounding_rect().width(); x++) { + multiply_alpha(x, offset, alpha); + multiply_alpha(x, mask.bounding_rect().height() - offset - 1, alpha); + } + } + + m_editor->selection().merge(mask, m_merge_mode); +} + +void RectangleSelectTool::on_keydown(GUI::KeyEvent& key_event) +{ + Tool::on_keydown(key_event); + if (key_event.key() == KeyCode::Key_Space) + m_moving_mode = MovingMode::MovingOrigin; + else if (key_event.key() == KeyCode::Key_Control) + m_moving_mode = MovingMode::AroundCenter; +} + +void RectangleSelectTool::on_keyup(GUI::KeyEvent& key_event) +{ + if (key_event.key() == KeyCode::Key_Space && m_moving_mode == MovingMode::MovingOrigin) + m_moving_mode = MovingMode::None; + else if (key_event.key() == KeyCode::Key_Control && m_moving_mode == MovingMode::AroundCenter) + m_moving_mode = MovingMode::None; +} + +void RectangleSelectTool::on_second_paint(Layer const*, GUI::PaintEvent& event) +{ + if (!m_selecting) + return; + + GUI::Painter painter(*m_editor); + painter.add_clip_rect(event.rect()); + + auto rect_in_image = Gfx::IntRect::from_two_points(m_selection_start, m_selection_end); + auto rect_in_editor = m_editor->image_rect_to_editor_rect(rect_in_image); + + m_editor->selection().draw_marching_ants(painter, rect_in_editor.to_type()); +} + +GUI::Widget* RectangleSelectTool::get_properties_widget() +{ + if (m_properties_widget) { + return m_properties_widget.ptr(); + } + + m_properties_widget = GUI::Widget::construct(); + m_properties_widget->set_layout(); + + auto& feather_container = m_properties_widget->add(); + feather_container.set_fixed_height(20); + feather_container.set_layout(); + + auto& feather_label = feather_container.add(); + feather_label.set_text("Feather:"); + feather_label.set_text_alignment(Gfx::TextAlignment::CenterLeft); + feather_label.set_fixed_size(80, 20); + + const int feather_slider_max = 100; + auto& feather_slider = feather_container.add(Orientation::Horizontal, "%"); + feather_slider.set_range(0, feather_slider_max); + feather_slider.set_value((int)floorf(m_edge_feathering * (float)feather_slider_max)); + + feather_slider.on_change = [&](int value) { + m_edge_feathering = (float)value / (float)feather_slider_max; + }; + set_primary_slider(&feather_slider); + + auto& mode_container = m_properties_widget->add(); + mode_container.set_fixed_height(20); + mode_container.set_layout(); + + auto& mode_label = mode_container.add(); + mode_label.set_text("Mode:"); + mode_label.set_text_alignment(Gfx::TextAlignment::CenterLeft); + mode_label.set_fixed_size(80, 20); + + for (int i = 0; i < (int)Selection::MergeMode::__Count; i++) { + switch ((Selection::MergeMode)i) { + case Selection::MergeMode::Set: + m_merge_mode_names.append("Set"); + break; + case Selection::MergeMode::Add: + m_merge_mode_names.append("Add"); + break; + case Selection::MergeMode::Subtract: + m_merge_mode_names.append("Subtract"); + break; + case Selection::MergeMode::Intersect: + m_merge_mode_names.append("Intersect"); + break; + default: + VERIFY_NOT_REACHED(); + } + } + + auto& mode_combo = mode_container.add(); + mode_combo.set_only_allow_values_from_model(true); + mode_combo.set_model(*GUI::ItemListModel::create(m_merge_mode_names)); + mode_combo.set_selected_index((int)m_merge_mode); + mode_combo.on_change = [this](auto&&, GUI::ModelIndex const& index) { + VERIFY(index.row() >= 0); + VERIFY(index.row() < (int)Selection::MergeMode::__Count); + + m_merge_mode = (Selection::MergeMode)index.row(); + }; + + return m_properties_widget.ptr(); +} + +} diff --git a/Userland/Applications/PixelPaint/Tools/RectangleSelectTool.h b/Userland/Applications/PixelPaint/Tools/RectangleSelectTool.h new file mode 100644 index 0000000000..94705d7b7b --- /dev/null +++ b/Userland/Applications/PixelPaint/Tools/RectangleSelectTool.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2021, Andreas Kling + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include "../Selection.h" +#include "Tool.h" + +#include +#include + +namespace PixelPaint { + +class RectangleSelectTool final : public Tool { +public: + RectangleSelectTool(); + virtual ~RectangleSelectTool(); + + virtual void on_mousedown(Layer*, MouseEvent& event) override; + virtual void on_mousemove(Layer*, MouseEvent& event) override; + virtual void on_mouseup(Layer*, MouseEvent& event) override; + virtual void on_keydown(GUI::KeyEvent&) override; + virtual void on_keyup(GUI::KeyEvent&) override; + virtual void on_second_paint(Layer const*, GUI::PaintEvent&) override; + virtual GUI::Widget* get_properties_widget() override; + virtual Gfx::StandardCursor cursor() override { return Gfx::StandardCursor::Crosshair; } + +private: + enum class MovingMode { + MovingOrigin, + AroundCenter, + None, + }; + + RefPtr m_properties_widget; + Vector m_merge_mode_names {}; + Selection::MergeMode m_merge_mode { Selection::MergeMode::Set }; + float m_edge_feathering { 0.0f }; + bool m_selecting { false }; + MovingMode m_moving_mode { MovingMode::None }; + Gfx::IntPoint m_selection_start; + Gfx::IntPoint m_selection_end; +}; + +} diff --git a/Userland/Applications/PixelPaint/Tools/RectangleTool.cpp b/Userland/Applications/PixelPaint/Tools/RectangleTool.cpp new file mode 100644 index 0000000000..b5b638aeaf --- /dev/null +++ b/Userland/Applications/PixelPaint/Tools/RectangleTool.cpp @@ -0,0 +1,213 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * Copyright (c) 2021, Mustafa Quraish + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "RectangleTool.h" +#include "../ImageEditor.h" +#include "../Layer.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace PixelPaint { + +RectangleTool::RectangleTool() +{ +} + +RectangleTool::~RectangleTool() +{ +} + +void RectangleTool::draw_using(GUI::Painter& painter, Gfx::IntPoint const& start_position, Gfx::IntPoint const& end_position, int thickness) +{ + Gfx::IntRect rect; + if (m_draw_mode == DrawMode::FromCenter) { + auto delta = end_position - start_position; + rect = Gfx::IntRect::from_two_points(start_position - delta, end_position); + } else { + rect = Gfx::IntRect::from_two_points(start_position, end_position); + } + + switch (m_fill_mode) { + case FillMode::Fill: + painter.fill_rect(rect, m_editor->color_for(m_drawing_button)); + break; + case FillMode::Outline: + painter.draw_rect_with_thickness(rect, m_editor->color_for(m_drawing_button), thickness); + break; + case FillMode::Gradient: + painter.fill_rect_with_gradient(rect, m_editor->primary_color(), m_editor->secondary_color()); + break; + default: + VERIFY_NOT_REACHED(); + } +} + +void RectangleTool::on_mousedown(Layer* layer, MouseEvent& event) +{ + if (!layer) + return; + + auto& layer_event = event.layer_event(); + if (layer_event.button() != GUI::MouseButton::Left && layer_event.button() != GUI::MouseButton::Right) + return; + + if (m_drawing_button != GUI::MouseButton::None) + return; + + m_drawing_button = layer_event.button(); + m_rectangle_start_position = layer_event.position(); + m_rectangle_end_position = layer_event.position(); + m_editor->update(); +} + +void RectangleTool::on_mouseup(Layer* layer, MouseEvent& event) +{ + if (!layer) + return; + + if (event.layer_event().button() == m_drawing_button) { + GUI::Painter painter(layer->bitmap()); + draw_using(painter, m_rectangle_start_position, m_rectangle_end_position, m_thickness); + m_drawing_button = GUI::MouseButton::None; + layer->did_modify_bitmap(); + m_editor->update(); + m_editor->did_complete_action(); + } +} + +void RectangleTool::on_mousemove(Layer* layer, MouseEvent& event) +{ + if (!layer) + return; + + if (m_drawing_button == GUI::MouseButton::None) + return; + + m_draw_mode = event.layer_event().alt() ? DrawMode::FromCenter : DrawMode::FromCorner; + + if (event.layer_event().shift()) + m_rectangle_end_position = m_rectangle_start_position.end_point_for_aspect_ratio(event.layer_event().position(), 1.0); + else if (m_aspect_ratio.has_value()) + m_rectangle_end_position = m_rectangle_start_position.end_point_for_aspect_ratio(event.layer_event().position(), m_aspect_ratio.value()); + else + m_rectangle_end_position = event.layer_event().position(); + + m_editor->update(); +} + +void RectangleTool::on_second_paint(Layer const* layer, GUI::PaintEvent& event) +{ + if (!layer || m_drawing_button == GUI::MouseButton::None) + return; + + GUI::Painter painter(*m_editor); + painter.add_clip_rect(event.rect()); + auto start_position = editor_stroke_position(m_rectangle_start_position, m_thickness); + auto end_position = editor_stroke_position(m_rectangle_end_position, m_thickness); + draw_using(painter, start_position, end_position, AK::max(m_thickness * m_editor->scale(), 1)); +} + +void RectangleTool::on_keydown(GUI::KeyEvent& event) +{ + Tool::on_keydown(event); + if (event.key() == Key_Escape && m_drawing_button != GUI::MouseButton::None) { + m_drawing_button = GUI::MouseButton::None; + m_editor->update(); + event.accept(); + } +} + +GUI::Widget* RectangleTool::get_properties_widget() +{ + if (!m_properties_widget) { + m_properties_widget = GUI::Widget::construct(); + m_properties_widget->set_layout(); + + auto& thickness_container = m_properties_widget->add(); + thickness_container.set_fixed_height(20); + thickness_container.set_layout(); + + auto& thickness_label = thickness_container.add("Thickness:"); + thickness_label.set_text_alignment(Gfx::TextAlignment::CenterLeft); + thickness_label.set_fixed_size(80, 20); + + auto& thickness_slider = thickness_container.add(Orientation::Horizontal, "px"); + thickness_slider.set_range(1, 10); + thickness_slider.set_value(m_thickness); + + thickness_slider.on_change = [&](int value) { + m_thickness = value; + }; + set_primary_slider(&thickness_slider); + + auto& mode_container = m_properties_widget->add(); + mode_container.set_fixed_height(70); + mode_container.set_layout(); + auto& mode_label = mode_container.add("Mode:"); + mode_label.set_text_alignment(Gfx::TextAlignment::CenterLeft); + mode_label.set_fixed_size(80, 20); + + auto& mode_radio_container = mode_container.add(); + mode_radio_container.set_layout(); + auto& outline_mode_radio = mode_radio_container.add("Outline"); + auto& fill_mode_radio = mode_radio_container.add("Fill"); + auto& gradient_mode_radio = mode_radio_container.add("Gradient"); + + outline_mode_radio.on_checked = [&](bool) { + m_fill_mode = FillMode::Outline; + }; + fill_mode_radio.on_checked = [&](bool) { + m_fill_mode = FillMode::Fill; + }; + gradient_mode_radio.on_checked = [&](bool) { + m_fill_mode = FillMode::Gradient; + }; + + outline_mode_radio.set_checked(true); + + auto& aspect_container = m_properties_widget->add(); + aspect_container.set_fixed_height(20); + aspect_container.set_layout(); + + auto& aspect_label = aspect_container.add("Aspect Ratio:"); + aspect_label.set_text_alignment(Gfx::TextAlignment::CenterLeft); + aspect_label.set_fixed_size(80, 20); + + m_aspect_w_textbox = aspect_container.add(); + m_aspect_w_textbox->set_fixed_height(20); + m_aspect_w_textbox->set_fixed_width(25); + m_aspect_w_textbox->on_change = [&] { + auto x = m_aspect_w_textbox->text().to_int().value_or(0); + auto y = m_aspect_h_textbox->text().to_int().value_or(0); + if (x > 0 && y > 0) { + m_aspect_ratio = (float)x / (float)y; + } else { + m_aspect_ratio = {}; + } + }; + + auto& multiply_label = aspect_container.add("x"); + multiply_label.set_text_alignment(Gfx::TextAlignment::Center); + multiply_label.set_fixed_size(10, 20); + + m_aspect_h_textbox = aspect_container.add(); + m_aspect_h_textbox->set_fixed_height(20); + m_aspect_h_textbox->set_fixed_width(25); + m_aspect_h_textbox->on_change = [&] { m_aspect_w_textbox->on_change(); }; + } + + return m_properties_widget.ptr(); +} + +} diff --git a/Userland/Applications/PixelPaint/Tools/RectangleTool.h b/Userland/Applications/PixelPaint/Tools/RectangleTool.h new file mode 100644 index 0000000000..6ff2bb1e12 --- /dev/null +++ b/Userland/Applications/PixelPaint/Tools/RectangleTool.h @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * Copyright (c) 2021, Mustafa Quraish + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include "Tool.h" +#include +#include + +namespace PixelPaint { + +class RectangleTool final : public Tool { +public: + RectangleTool(); + virtual ~RectangleTool() override; + + virtual void on_mousedown(Layer*, MouseEvent&) override; + virtual void on_mousemove(Layer*, MouseEvent&) override; + virtual void on_mouseup(Layer*, MouseEvent&) override; + virtual void on_second_paint(Layer const*, GUI::PaintEvent&) override; + virtual void on_keydown(GUI::KeyEvent&) override; + virtual GUI::Widget* get_properties_widget() override; + virtual Gfx::StandardCursor cursor() override { return Gfx::StandardCursor::Crosshair; } + +private: + enum class FillMode { + Outline, + Fill, + Gradient, + }; + + enum class DrawMode { + FromCenter, + FromCorner, + }; + + void draw_using(GUI::Painter&, Gfx::IntPoint const& start_position, Gfx::IntPoint const& end_position, int thickness); + + RefPtr m_properties_widget; + RefPtr m_aspect_w_textbox; + RefPtr m_aspect_h_textbox; + + GUI::MouseButton m_drawing_button { GUI::MouseButton::None }; + Gfx::IntPoint m_rectangle_start_position; + Gfx::IntPoint m_rectangle_end_position; + FillMode m_fill_mode { FillMode::Outline }; + DrawMode m_draw_mode { DrawMode::FromCorner }; + int m_thickness { 1 }; + Optional m_aspect_ratio; +}; + +} diff --git a/Userland/Applications/PixelPaint/Tools/SprayTool.cpp b/Userland/Applications/PixelPaint/Tools/SprayTool.cpp new file mode 100644 index 0000000000..711ce0d3db --- /dev/null +++ b/Userland/Applications/PixelPaint/Tools/SprayTool.cpp @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "SprayTool.h" +#include "../ImageEditor.h" +#include "../Layer.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace PixelPaint { + +SprayTool::SprayTool() +{ + m_timer = Core::Timer::construct(); + m_timer->on_timeout = [&]() { + paint_it(); + }; + m_timer->set_interval(200); +} + +SprayTool::~SprayTool() +{ +} + +static double nrand() +{ + return double(rand()) / double(RAND_MAX); +} + +void SprayTool::paint_it() +{ + auto* layer = m_editor->active_layer(); + if (!layer) + return; + + auto& bitmap = layer->bitmap(); + GUI::Painter painter(bitmap); + VERIFY(bitmap.bpp() == 32); + const double minimal_radius = 2; + const double base_radius = minimal_radius * m_thickness; + for (int i = 0; i < M_PI * base_radius * base_radius * (m_density / 100.0); i++) { + double radius = base_radius * nrand(); + double angle = 2 * M_PI * nrand(); + const int xpos = m_last_pos.x() + radius * AK::cos(angle); + const int ypos = m_last_pos.y() - radius * AK::sin(angle); + if (xpos < 0 || xpos >= bitmap.width()) + continue; + if (ypos < 0 || ypos >= bitmap.height()) + continue; + bitmap.set_pixel(xpos, ypos, m_color); + } + + layer->did_modify_bitmap(Gfx::IntRect::centered_on(m_last_pos, Gfx::IntSize(base_radius * 2, base_radius * 2))); +} + +void SprayTool::on_mousedown(Layer* layer, MouseEvent& event) +{ + if (!layer) + return; + + auto& layer_event = event.layer_event(); + m_color = m_editor->color_for(layer_event); + m_last_pos = layer_event.position(); + m_timer->start(); + paint_it(); +} + +void SprayTool::on_mousemove(Layer* layer, MouseEvent& event) +{ + if (!layer) + return; + + m_last_pos = event.layer_event().position(); + if (m_timer->is_active()) { + paint_it(); + m_timer->restart(m_timer->interval()); + } +} + +void SprayTool::on_mouseup(Layer*, MouseEvent&) +{ + if (m_timer->is_active()) { + m_timer->stop(); + m_editor->did_complete_action(); + } +} + +GUI::Widget* SprayTool::get_properties_widget() +{ + if (!m_properties_widget) { + m_properties_widget = GUI::Widget::construct(); + m_properties_widget->set_layout(); + + auto& size_container = m_properties_widget->add(); + size_container.set_fixed_height(20); + size_container.set_layout(); + + auto& size_label = size_container.add("Size:"); + size_label.set_text_alignment(Gfx::TextAlignment::CenterLeft); + size_label.set_fixed_size(80, 20); + + auto& size_slider = size_container.add(Orientation::Horizontal, "px"); + size_slider.set_range(1, 20); + size_slider.set_value(m_thickness); + + size_slider.on_change = [&](int value) { + m_thickness = value; + }; + set_primary_slider(&size_slider); + + auto& density_container = m_properties_widget->add(); + density_container.set_fixed_height(20); + density_container.set_layout(); + + auto& density_label = density_container.add("Density:"); + density_label.set_text_alignment(Gfx::TextAlignment::CenterLeft); + density_label.set_fixed_size(80, 20); + + auto& density_slider = density_container.add(Orientation::Horizontal, "%"); + density_slider.set_range(1, 100); + density_slider.set_value(m_density); + + density_slider.on_change = [&](int value) { + m_density = value; + }; + set_secondary_slider(&density_slider); + } + + return m_properties_widget.ptr(); +} + +} diff --git a/Userland/Applications/PixelPaint/Tools/SprayTool.h b/Userland/Applications/PixelPaint/Tools/SprayTool.h new file mode 100644 index 0000000000..9f08bdfb44 --- /dev/null +++ b/Userland/Applications/PixelPaint/Tools/SprayTool.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include "Tool.h" +#include +#include +#include + +namespace PixelPaint { + +class SprayTool final : public Tool { +public: + SprayTool(); + virtual ~SprayTool() override; + + virtual void on_mousedown(Layer*, MouseEvent&) override; + virtual void on_mouseup(Layer*, MouseEvent&) override; + virtual void on_mousemove(Layer*, MouseEvent&) override; + virtual GUI::Widget* get_properties_widget() override; + virtual Gfx::StandardCursor cursor() override { return Gfx::StandardCursor::Crosshair; } + +private: + void paint_it(); + + RefPtr m_properties_widget; + RefPtr m_timer; + Gfx::IntPoint m_last_pos; + Color m_color; + int m_thickness { 10 }; + int m_density { 40 }; +}; + +} diff --git a/Userland/Applications/PixelPaint/Tools/Tool.cpp b/Userland/Applications/PixelPaint/Tools/Tool.cpp new file mode 100644 index 0000000000..27694b9650 --- /dev/null +++ b/Userland/Applications/PixelPaint/Tools/Tool.cpp @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * Copyright (c) 2021, Mustafa Quraish + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "Tool.h" +#include "../ImageEditor.h" +#include + +namespace PixelPaint { + +Tool::Tool() +{ +} + +Tool::~Tool() +{ +} + +void Tool::setup(ImageEditor& editor) +{ + m_editor = editor; +} + +void Tool::set_action(GUI::Action* action) +{ + m_action = action; +} + +void Tool::on_keydown(GUI::KeyEvent& event) +{ + switch (event.key()) { + case KeyCode::Key_LeftBracket: + if (m_primary_slider) + m_primary_slider->set_value(m_primary_slider->value() - 1); + break; + case KeyCode::Key_RightBracket: + if (m_primary_slider) + m_primary_slider->set_value(m_primary_slider->value() + 1); + break; + case KeyCode::Key_LeftBrace: + if (m_secondary_slider) + m_secondary_slider->set_value(m_secondary_slider->value() - 1); + break; + case KeyCode::Key_RightBrace: + if (m_secondary_slider) + m_secondary_slider->set_value(m_secondary_slider->value() + 1); + break; + default: + break; + } +} + +Gfx::IntPoint Tool::editor_stroke_position(Gfx::IntPoint const& pixel_coords, int stroke_thickness) const +{ + auto position = m_editor->image_position_to_editor_position(pixel_coords); + auto offset = (stroke_thickness % 2 == 0) ? m_editor->scale() : m_editor->scale() / 2; + position = position.translated(offset, offset); + return position.to_type(); +} + +} diff --git a/Userland/Applications/PixelPaint/Tools/Tool.h b/Userland/Applications/PixelPaint/Tools/Tool.h new file mode 100644 index 0000000000..82b6508c5b --- /dev/null +++ b/Userland/Applications/PixelPaint/Tools/Tool.h @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * Copyright (c) 2021, Mustafa Quraish + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include + +namespace PixelPaint { + +class ImageEditor; +class Layer; + +class Tool { +public: + virtual ~Tool(); + + class MouseEvent { + public: + enum class Action { + MouseDown, + MouseMove, + MouseUp + }; + + MouseEvent(Action action, GUI::MouseEvent& layer_event, GUI::MouseEvent& image_event, GUI::MouseEvent& raw_event) + : m_action(action) + , m_layer_event(layer_event) + , m_image_event(image_event) + , m_raw_event(raw_event) + { + } + + Action action() const { return m_action; } + GUI::MouseEvent const& layer_event() const { return m_layer_event; } + GUI::MouseEvent const& image_event() const { return m_image_event; } + GUI::MouseEvent const& raw_event() const { return m_raw_event; } + + private: + Action m_action; + + GUI::MouseEvent& m_layer_event; + GUI::MouseEvent& m_image_event; + GUI::MouseEvent& m_raw_event; + }; + + virtual void on_mousedown(Layer*, MouseEvent&) { } + virtual void on_mousemove(Layer*, MouseEvent&) { } + virtual void on_mouseup(Layer*, MouseEvent&) { } + virtual void on_context_menu(Layer*, GUI::ContextMenuEvent&) { } + virtual void on_tool_button_contextmenu(GUI::ContextMenuEvent&) { } + virtual void on_second_paint(Layer const*, GUI::PaintEvent&) { } + virtual void on_keydown(GUI::KeyEvent&); + virtual void on_keyup(GUI::KeyEvent&) { } + virtual void on_tool_activation() { } + virtual GUI::Widget* get_properties_widget() { return nullptr; } + virtual Gfx::StandardCursor cursor() { return Gfx::StandardCursor::None; } + + void clear() { m_editor = nullptr; } + void setup(ImageEditor&); + + ImageEditor const* editor() const { return m_editor; } + ImageEditor* editor() { return m_editor; } + + GUI::Action* action() { return m_action; } + void set_action(GUI::Action*); + +protected: + Tool(); + WeakPtr m_editor; + RefPtr m_action; + + virtual Gfx::IntPoint editor_stroke_position(Gfx::IntPoint const& pixel_coords, int stroke_thickness) const; + + void set_primary_slider(GUI::ValueSlider* primary) { m_primary_slider = primary; } + void set_secondary_slider(GUI::ValueSlider* secondary) { m_secondary_slider = secondary; } + + GUI::ValueSlider* m_primary_slider { nullptr }; + GUI::ValueSlider* m_secondary_slider { nullptr }; +}; + +} diff --git a/Userland/Applications/PixelPaint/Tools/ZoomTool.cpp b/Userland/Applications/PixelPaint/Tools/ZoomTool.cpp new file mode 100644 index 0000000000..5bc7769cab --- /dev/null +++ b/Userland/Applications/PixelPaint/Tools/ZoomTool.cpp @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2021, the SerenityOS developers. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "ZoomTool.h" +#include "../ImageEditor.h" +#include +#include +#include + +namespace PixelPaint { + +ZoomTool::ZoomTool() +{ +} + +ZoomTool::~ZoomTool() +{ +} + +void ZoomTool::on_mousedown(Layer*, MouseEvent& event) +{ + auto& raw_event = event.raw_event(); + if (raw_event.button() != GUI::MouseButton::Left && raw_event.button() != GUI::MouseButton::Right) + return; + + auto scale_factor = (raw_event.button() == GUI::MouseButton::Left) ? m_sensitivity : -m_sensitivity; + m_editor->scale_centered_on_position(raw_event.position(), scale_factor); +} + +GUI::Widget* ZoomTool::get_properties_widget() +{ + if (!m_properties_widget) { + m_properties_widget = GUI::Widget::construct(); + m_properties_widget->set_layout(); + + auto& sensitivity_container = m_properties_widget->add(); + sensitivity_container.set_fixed_height(20); + sensitivity_container.set_layout(); + + auto& sensitivity_label = sensitivity_container.add("Sensitivity:"); + sensitivity_label.set_text_alignment(Gfx::TextAlignment::CenterLeft); + sensitivity_label.set_fixed_size(80, 20); + + auto& sensitivity_slider = sensitivity_container.add(Orientation::Horizontal, "%"); + sensitivity_slider.set_range(1, 100); + sensitivity_slider.set_value(100 * m_sensitivity); + + sensitivity_slider.on_change = [&](int value) { + m_sensitivity = (double)value / 100.0; + }; + set_primary_slider(&sensitivity_slider); + } + + return m_properties_widget.ptr(); +} + +} diff --git a/Userland/Applications/PixelPaint/Tools/ZoomTool.h b/Userland/Applications/PixelPaint/Tools/ZoomTool.h new file mode 100644 index 0000000000..c9ca019aae --- /dev/null +++ b/Userland/Applications/PixelPaint/Tools/ZoomTool.h @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2021, the SerenityOS developers. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include "Tool.h" +#include +#include + +namespace PixelPaint { + +class ZoomTool final : public Tool { +public: + ZoomTool(); + virtual ~ZoomTool() override; + + virtual void on_mousedown(Layer*, MouseEvent&) override; + virtual GUI::Widget* get_properties_widget() override; + virtual Gfx::StandardCursor cursor() override { return Gfx::StandardCursor::Zoom; } + +private: + RefPtr m_properties_widget; + double m_sensitivity { 0.5 }; +}; + +} diff --git a/Userland/Applications/PixelPaint/ZoomTool.cpp b/Userland/Applications/PixelPaint/ZoomTool.cpp deleted file mode 100644 index 94a50484e9..0000000000 --- a/Userland/Applications/PixelPaint/ZoomTool.cpp +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (c) 2021, the SerenityOS developers. - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include "ZoomTool.h" -#include "ImageEditor.h" -#include -#include -#include - -namespace PixelPaint { - -ZoomTool::ZoomTool() -{ -} - -ZoomTool::~ZoomTool() -{ -} - -void ZoomTool::on_mousedown(Layer*, MouseEvent& event) -{ - auto& raw_event = event.raw_event(); - if (raw_event.button() != GUI::MouseButton::Left && raw_event.button() != GUI::MouseButton::Right) - return; - - auto scale_factor = (raw_event.button() == GUI::MouseButton::Left) ? m_sensitivity : -m_sensitivity; - m_editor->scale_centered_on_position(raw_event.position(), scale_factor); -} - -GUI::Widget* ZoomTool::get_properties_widget() -{ - if (!m_properties_widget) { - m_properties_widget = GUI::Widget::construct(); - m_properties_widget->set_layout(); - - auto& sensitivity_container = m_properties_widget->add(); - sensitivity_container.set_fixed_height(20); - sensitivity_container.set_layout(); - - auto& sensitivity_label = sensitivity_container.add("Sensitivity:"); - sensitivity_label.set_text_alignment(Gfx::TextAlignment::CenterLeft); - sensitivity_label.set_fixed_size(80, 20); - - auto& sensitivity_slider = sensitivity_container.add(Orientation::Horizontal, "%"); - sensitivity_slider.set_range(1, 100); - sensitivity_slider.set_value(100 * m_sensitivity); - - sensitivity_slider.on_change = [&](int value) { - m_sensitivity = (double)value / 100.0; - }; - set_primary_slider(&sensitivity_slider); - } - - return m_properties_widget.ptr(); -} - -} diff --git a/Userland/Applications/PixelPaint/ZoomTool.h b/Userland/Applications/PixelPaint/ZoomTool.h deleted file mode 100644 index c9ca019aae..0000000000 --- a/Userland/Applications/PixelPaint/ZoomTool.h +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (c) 2021, the SerenityOS developers. - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - -#include "Tool.h" -#include -#include - -namespace PixelPaint { - -class ZoomTool final : public Tool { -public: - ZoomTool(); - virtual ~ZoomTool() override; - - virtual void on_mousedown(Layer*, MouseEvent&) override; - virtual GUI::Widget* get_properties_widget() override; - virtual Gfx::StandardCursor cursor() override { return Gfx::StandardCursor::Zoom; } - -private: - RefPtr m_properties_widget; - double m_sensitivity { 0.5 }; -}; - -} -- cgit v1.2.3