From 22b78226b3b29cb8f6e50528112182b6b052833c Mon Sep 17 00:00:00 2001 From: Mustafa Quraish Date: Sun, 12 Sep 2021 16:03:33 -0400 Subject: [PATCH] PixelPaint: Add Clone Tool :^) There's still a lot to be desired in terms of functionality and usability, but this is a start. When using the clone tool, you can press Alt to sample a location, and then the brush will clone the color from there. --- .../Applications/PixelPaint/CMakeLists.txt | 1 + .../Applications/PixelPaint/CloneTool.cpp | 133 ++++++++++++++++++ Userland/Applications/PixelPaint/CloneTool.h | 36 +++++ 3 files changed, 170 insertions(+) create mode 100644 Userland/Applications/PixelPaint/CloneTool.cpp create mode 100644 Userland/Applications/PixelPaint/CloneTool.h diff --git a/Userland/Applications/PixelPaint/CMakeLists.txt b/Userland/Applications/PixelPaint/CMakeLists.txt index 27618d1ed0..2e77fbc740 100644 --- a/Userland/Applications/PixelPaint/CMakeLists.txt +++ b/Userland/Applications/PixelPaint/CMakeLists.txt @@ -11,6 +11,7 @@ compile_gml(EditGuideDialog.gml EditGuideDialogGML.h edit_guide_dialog_gml) set(SOURCES BrushTool.cpp BucketTool.cpp + CloneTool.cpp CreateNewImageDialog.cpp CreateNewLayerDialog.cpp EditGuideDialog.cpp diff --git a/Userland/Applications/PixelPaint/CloneTool.cpp b/Userland/Applications/PixelPaint/CloneTool.cpp new file mode 100644 index 0000000000..43915c6541 --- /dev/null +++ b/Userland/Applications/PixelPaint/CloneTool.cpp @@ -0,0 +1,133 @@ +/* + * 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); +} + +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 = {}; + 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_keydown(GUI::KeyEvent& event) +{ + Tool::on_keydown(event); + if (event.key() == KeyCode::Key_Alt && !m_is_selecting_location) { + m_is_selecting_location = true; + return; + } +} + +void CloneTool::on_keyup(GUI::KeyEvent& event) +{ + if (m_is_selecting_location && event.key() == KeyCode::Key_Alt) { + m_is_selecting_location = false; + 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 new file mode 100644 index 0000000000..7e00eaa47f --- /dev/null +++ b/Userland/Applications/PixelPaint/CloneTool.h @@ -0,0 +1,36 @@ +/* + * 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; + +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_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 }; +}; + +}