diff options
author | Torstennator <engelTorsten@gmx.de> | 2023-04-25 17:48:21 +0200 |
---|---|---|
committer | Sam Atkins <atkinssj@gmail.com> | 2023-04-29 11:55:31 +0100 |
commit | cd33f271b16fda7213a35b3ae1bad04ff270b7eb (patch) | |
tree | bc00b472e5043b2c23b23630e1750186c658afb7 /Userland/Applications | |
parent | abcfaca3da4cde641894e724cb6ab1541c6d93e6 (diff) | |
download | serenity-cd33f271b16fda7213a35b3ae1bad04ff270b7eb.zip |
PixelPaint: Add radial gradient support
This patch adds radial gradients to the gradients tool.
Diffstat (limited to 'Userland/Applications')
-rw-r--r-- | Userland/Applications/PixelPaint/Tools/GradientTool.cpp | 231 | ||||
-rw-r--r-- | Userland/Applications/PixelPaint/Tools/GradientTool.h | 31 |
2 files changed, 221 insertions, 41 deletions
diff --git a/Userland/Applications/PixelPaint/Tools/GradientTool.cpp b/Userland/Applications/PixelPaint/Tools/GradientTool.cpp index d5b3e17007..2d89f7b335 100644 --- a/Userland/Applications/PixelPaint/Tools/GradientTool.cpp +++ b/Userland/Applications/PixelPaint/Tools/GradientTool.cpp @@ -13,6 +13,8 @@ #include <LibGUI/BoxLayout.h> #include <LibGUI/Button.h> #include <LibGUI/CheckBox.h> +#include <LibGUI/ComboBox.h> +#include <LibGUI/ItemListModel.h> #include <LibGUI/Label.h> #include <LibGUI/OpacitySlider.h> #include <LibGUI/Painter.h> @@ -26,7 +28,7 @@ namespace PixelPaint { Variant<Gfx::StandardCursor, NonnullRefPtr<Gfx::Bitmap const>> GradientTool::cursor() { - if (m_hover_over_drag_handle || m_hover_over_start_handle || m_hover_over_end_handle) + if (m_hover_over_drag_handle || m_hover_over_start_handle || m_hover_over_end_handle || m_hover_over_transversal_a_handle || m_hover_over_transversal_b_handle) return Gfx::StandardCursor::Hand; if (m_button_pressed) @@ -45,11 +47,11 @@ void GradientTool::on_mousedown(Layer* layer, MouseEvent& event) return; m_button_pressed = true; - if (!m_hover_over_start_handle && !m_hover_over_end_handle) { - if (has_gradient_start_end()) { + if (!m_hover_over_start_handle && !m_hover_over_end_handle && !m_hover_over_transversal_a_handle && !m_hover_over_transversal_b_handle) { + if (has_gradient_data()) { Gfx::IntPoint movement_delta = layer_event.position() - m_gradient_center.value(); m_gradient_center = layer_event.position(); - translate_gradient_start_end(movement_delta, false); + move_gradient_position(movement_delta); calculate_gradient_lines(); } else { m_gradient_center = layer_event.position(); @@ -64,7 +66,7 @@ void GradientTool::on_mousedown(Layer* layer, MouseEvent& event) void GradientTool::on_mousemove(Layer* layer, MouseEvent& event) { // Check if user is hovering over a handle - if (layer && m_editor && !m_button_pressed && has_gradient_start_end()) { + if (layer && m_editor && !m_button_pressed && has_gradient_data()) { auto set_hover_flag = [&](bool& flag, Optional<Gfx::IntPoint> const p) { auto handle_offset = m_editor->content_to_frame_position(layer->location()); float scale = m_editor->scale(); @@ -80,6 +82,11 @@ void GradientTool::on_mousemove(Layer* layer, MouseEvent& event) set_hover_flag(m_hover_over_start_handle, m_gradient_start.value()); set_hover_flag(m_hover_over_drag_handle, m_gradient_center.value()); set_hover_flag(m_hover_over_end_handle, m_gradient_end.value()); + + if (m_mode == GradientMode::Radial) { + set_hover_flag(m_hover_over_transversal_a_handle, m_gradient_transversal_a.value()); + set_hover_flag(m_hover_over_transversal_b_handle, m_gradient_transversal_b.value()); + } } if (!layer || !m_button_pressed) @@ -88,20 +95,26 @@ void GradientTool::on_mousemove(Layer* layer, MouseEvent& event) auto& layer_event = event.layer_event(); if (!m_hover_over_drag_handle && (m_hover_over_start_handle || m_hover_over_end_handle)) { auto movement_delta = m_hover_over_start_handle ? layer_event.position() - m_gradient_start.value() : layer_event.position() - m_gradient_end.value(); - translate_gradient_start_end(m_hover_over_start_handle ? movement_delta.scaled({ -1, -1 }) : movement_delta); + rotate_gradient_points(m_hover_over_start_handle ? movement_delta.scaled({ -1, -1 }) : movement_delta); + } + + if (!m_hover_over_drag_handle && (m_hover_over_transversal_a_handle || m_hover_over_transversal_b_handle)) { + auto distance_to_center = layer_event.position().distance_from(m_gradient_center.value()); + auto new_left_right_distance_fraction = distance_to_center / m_gradient_center.value().distance_from(m_gradient_start.value()); + calculate_transversal_points(new_left_right_distance_fraction); } if (m_hover_over_drag_handle) { auto movement_delta = layer_event.position() - m_gradient_center.value(); m_gradient_center.value().translate_by(movement_delta); - translate_gradient_start_end(movement_delta, false); + move_gradient_position(movement_delta); } - if (!(m_hover_over_drag_handle || m_hover_over_start_handle || m_hover_over_end_handle)) - update_gradient_end_and_derive_start(layer_event.position()); + if (!(m_hover_over_drag_handle || m_hover_over_start_handle || m_hover_over_end_handle || m_hover_over_transversal_a_handle || m_hover_over_transversal_b_handle)) + update_gradient_with_initial_values(layer_event.position()); // If Shift is pressed, align the gradient horizontally or vertically - if (m_shift_pressed && has_gradient_start_end()) { + if (m_shift_pressed && has_gradient_data() && m_mode == GradientMode::Linear) { auto delta = m_gradient_center.value() - m_gradient_end.value(); if (AK::abs(delta.x()) < AK::abs(delta.y())) { m_gradient_start.value().set_x(m_gradient_center.value().x()); @@ -158,7 +171,7 @@ void GradientTool::on_keyup(GUI::KeyEvent& event) void GradientTool::on_second_paint(Layer const* layer, GUI::PaintEvent& event) { - if (!layer || !has_gradient_start_end()) + if (!layer || !has_gradient_data()) return; GUI::Painter painter(*m_editor); @@ -168,13 +181,13 @@ void GradientTool::on_second_paint(Layer const* layer, GUI::PaintEvent& event) void GradientTool::on_primary_color_change(Color) { - if (m_gradient_end.has_value()) + if (has_gradient_data()) m_editor->update(); } void GradientTool::on_secondary_color_change(Color) { - if (m_gradient_end.has_value()) + if (has_gradient_data()) m_editor->update(); } @@ -189,6 +202,35 @@ ErrorOr<GUI::Widget*> GradientTool::get_properties_widget() auto properties_widget = TRY(GUI::Widget::try_create()); (void)TRY(properties_widget->try_set_layout<GUI::VerticalBoxLayout>()); + auto mode_container = TRY(properties_widget->try_add<GUI::Widget>()); + mode_container->set_fixed_height(20); + (void)TRY(mode_container->try_set_layout<GUI::HorizontalBoxLayout>()); + auto mode_label = TRY(mode_container->try_add<GUI::Label>("Gradient Type:")); + mode_label->set_text_alignment(Gfx::TextAlignment::CenterLeft); + mode_label->set_fixed_size(80, 20); + + static constexpr auto s_mode_names = [] { + Array<StringView, (int)GradientMode::__Count> names; + for (size_t i = 0; i < names.size(); i++) { + switch ((GradientMode)i) { + case GradientMode::Linear: + names[i] = "Linear"sv; + break; + case GradientMode::Radial: + names[i] = "Radial"sv; + break; + default: + break; + } + } + return names; + }(); + + auto mode_combobox = TRY(mode_container->try_add<GUI::ComboBox>()); + mode_combobox->set_only_allow_values_from_model(true); + mode_combobox->set_model(*GUI::ItemListModel<StringView, decltype(s_mode_names)>::create(s_mode_names)); + mode_combobox->set_selected_index((int)m_mode, GUI::AllowCallback::No); + auto size_container = TRY(properties_widget->try_add<GUI::Widget>()); size_container->set_fixed_height(20); (void)TRY(size_container->try_set_layout<GUI::HorizontalBoxLayout>()); @@ -208,6 +250,39 @@ ErrorOr<GUI::Widget*> GradientTool::get_properties_widget() set_primary_slider(opacity_slider); + auto hardness_container = TRY(properties_widget->try_add<GUI::Widget>()); + (void)TRY(hardness_container->try_set_layout<GUI::HorizontalBoxLayout>()); + hardness_container->set_fixed_height(20); + hardness_container->set_visible(m_mode == GradientMode::Radial); + + mode_combobox->on_change = [this, hardness_container](auto&, auto& model_index) { + VERIFY(model_index.row() >= 0); + VERIFY(model_index.row() < (int)GradientMode::__Count); + + GradientMode selected_mode = model_index.row() == 0 ? GradientMode::Linear : GradientMode::Radial; + + if (m_mode != selected_mode) { + m_mode = selected_mode; + reset(); + } + + hardness_container->set_visible(m_mode == GradientMode::Radial); + }; + + auto hardness_label = TRY(hardness_container->try_add<GUI::Label>("Hardness:")); + hardness_label->set_text_alignment(Gfx::TextAlignment::CenterLeft); + + auto hardness_slider = TRY(hardness_container->try_add<GUI::HorizontalOpacitySlider>()); + hardness_slider->set_range(1, 99); + hardness_slider->set_value(m_hardness); + hardness_slider->on_change = [this](int value) { + if (m_mode == GradientMode::Radial && m_editor) { + m_hardness = value; + m_editor->update(); + } + }; + set_secondary_slider(hardness_slider); + auto use_secondary_color_checkbox = TRY(properties_widget->try_add<GUI::CheckBox>(TRY("Use secondary color"_string))); use_secondary_color_checkbox->on_checked = [this](bool checked) { m_use_secondary_color = checked; @@ -231,10 +306,8 @@ ErrorOr<GUI::Widget*> GradientTool::get_properties_widget() void GradientTool::rasterize_gradient() { - if (!has_gradient_start_end()) - return; auto layer = m_editor->active_layer(); - if (!layer) + if (!layer || !has_gradient_data()) return; GUI::Painter painter(layer->get_scratch_edited_bitmap()); @@ -278,7 +351,7 @@ void GradientTool::draw_gradient(GUI::Painter& painter, bool with_guidelines, co int height = m_editor->active_layer()->rect().height() * scale; float rotation_radians = atan2f(t_gradient_begin_line.a().y() - t_gradient_end_line.a().y(), t_gradient_begin_line.a().x() - t_gradient_end_line.a().x()); - float rotation_degrees = ((rotation_radians * 180) / static_cast<float>(M_PI)) - 90; + float rotation_degrees = (rotation_radians * 180) / AK::Pi<float>; auto determine_required_side_length = [&](int center, int side_length) { if (center < 0) @@ -289,10 +362,12 @@ void GradientTool::draw_gradient(GUI::Painter& painter, bool with_guidelines, co return 2 * (AK::max(center + side_length, side_length - center)); }; + auto scaled_gradient_center = m_gradient_center.value().to_type<float>().scaled(scale, scale).to_type<int>(); auto gradient_rect_height = determine_required_side_length(t_gradient_center.y(), height); auto gradient_rect_width = determine_required_side_length(t_gradient_center.x(), width); - auto gradient_rect = Gfx::IntRect::centered_at(t_gradient_center, { gradient_rect_width, gradient_rect_height }); - float overall_gradient_length_in_rect = Gfx::calculate_gradient_length(gradient_rect.size(), rotation_degrees); + auto gradient_max_side_length = AK::max(gradient_rect_height, gradient_rect_width); + auto gradient_rect = Gfx::IntRect::centered_at(t_gradient_center, { gradient_max_side_length, gradient_max_side_length }); + float overall_gradient_length_in_rect = Gfx::calculate_gradient_length(gradient_rect.size(), rotation_degrees - 90); if (m_gradient_half_length == 0 || overall_gradient_length_in_rect == 0 || isnan(overall_gradient_length_in_rect)) return; @@ -313,32 +388,83 @@ void GradientTool::draw_gradient(GUI::Painter& painter, bool with_guidelines, co Gfx::PainterStateSaver saver(painter); if (gradient_clip.has_value()) painter.add_clip_rect(*gradient_clip); - painter.fill_rect_with_linear_gradient(gradient_rect, Array { Gfx::ColorStop { start_color, 0.5f - gradient_half_width_percentage_offset }, Gfx::ColorStop { end_color, 0.5f + gradient_half_width_percentage_offset } }, rotation_degrees); + + switch (m_mode) { + case GradientMode::__Count: + break; + case GradientMode::Linear: + painter.fill_rect_with_linear_gradient(gradient_rect, Array { Gfx::ColorStop { start_color, 0.5f - gradient_half_width_percentage_offset }, Gfx::ColorStop { end_color, 0.5f + gradient_half_width_percentage_offset } }, rotation_degrees - 90); + break; + case GradientMode::Radial: + + auto t_gradient_longitudinal = m_gradient_start.value().to_type<float>().scaled(scale, scale).translated(drawing_offset).to_type<int>(); + auto t_gradient_transversal = m_gradient_transversal_a.value().to_type<float>().scaled(scale, scale).translated(drawing_offset).to_type<int>(); + auto radial_size = Gfx::IntSize((AK::abs(t_gradient_center.distance_from(t_gradient_longitudinal))), (AK::abs(t_gradient_center.distance_from(t_gradient_transversal)))); + + AK::Array<Gfx::ColorStop, 3> colors = { + Gfx::ColorStop { .color = start_color, .position = 0.0f }, + Gfx::ColorStop { .color = start_color, .position = m_hardness / 100.0f }, + Gfx::ColorStop { .color = end_color, .position = 1.0f }, + }; + + painter.fill_rect_with_radial_gradient(Gfx::IntRect(drawing_offset, { width, height }), colors, scaled_gradient_center, radial_size, {}, 180 - rotation_degrees); + break; + } } if (with_guidelines) { Gfx::AntiAliasingPainter aa_painter = Gfx::AntiAliasingPainter(painter); - aa_painter.draw_line(t_gradient_begin_line, Color::LightGray); - aa_painter.draw_line(t_gradient_center_line, Color::MidGray); - aa_painter.draw_line(t_gradient_end_line, Color::Black); - Gfx::FloatLine icon_line1_rotated_offset = Gfx::FloatLine({ -2, -4 }, { -2, 4 }).rotated(rotation_radians); Gfx::FloatLine icon_line2_rotated_offset = Gfx::FloatLine({ 2, -4 }, { 2, 4 }).rotated(rotation_radians); + Gfx::FloatLine icon_line3_rotated_offset = Gfx::FloatLine({ -3, -2 }, { -3, 2 }).rotated(rotation_radians); + Gfx::FloatLine icon_line4_rotated_offset = Gfx::FloatLine({ 3, -2 }, { 3, 2 }).rotated(rotation_radians); + Gfx::FloatLine icon_line5_rotated_offset = Gfx::FloatLine({ 0, -5 }, { 0, 5 }).rotated(rotation_radians); - auto draw_handle = [&](Gfx::IntPoint p, bool is_hovered, bool with_icon) { + auto draw_handle = [&](Gfx::IntPoint p, bool is_hovered, IconStyle with_icon) { auto alpha = is_hovered ? 255 : 100; auto translated_p = p.to_type<float>().scaled(scale, scale).translated(drawing_offset); aa_painter.fill_circle(translated_p.to_type<int>(), 10, Color(Color::MidGray).with_alpha(alpha)); aa_painter.fill_circle(translated_p.to_type<int>(), 8, Color(Color::LightGray).with_alpha(alpha)); - if (with_icon) { + if (with_icon == IconStyle::ChangeWidthAndAngle) { aa_painter.draw_line(icon_line1_rotated_offset.translated(translated_p), Color(Color::MidGray).with_alpha(alpha), 2); aa_painter.draw_line(icon_line2_rotated_offset.translated(translated_p), Color(Color::MidGray).with_alpha(alpha), 2); } + if (with_icon == IconStyle::RadialWidth) { + auto make_triangle_path = [&](Gfx::FloatPoint p1, Gfx::FloatPoint p2, Gfx::FloatPoint p3) { + Gfx::Path triangle; + triangle.move_to(p1.translated(translated_p)); + triangle.line_to(p2.translated(translated_p)); + triangle.line_to(p3.translated(translated_p)); + triangle.close(); + return triangle; + }; + + aa_painter.fill_path(make_triangle_path( + icon_line3_rotated_offset.a(), + icon_line4_rotated_offset.a(), + icon_line5_rotated_offset.a()), + Color(Color::MidGray).with_alpha(alpha), Gfx::Painter::WindingRule::EvenOdd); + aa_painter.fill_path(make_triangle_path( + icon_line3_rotated_offset.b(), + icon_line4_rotated_offset.b(), + icon_line5_rotated_offset.b()), + Color(Color::MidGray).with_alpha(alpha), Gfx::Painter::WindingRule::EvenOdd); + } }; - draw_handle(m_gradient_start.value(), m_hover_over_start_handle, true); - draw_handle(m_gradient_center.value(), m_hover_over_drag_handle, false); - draw_handle(m_gradient_end.value(), m_hover_over_end_handle, true); + + if (m_mode == GradientMode::Linear) { + aa_painter.draw_line(t_gradient_begin_line, Color::Black); + aa_painter.draw_line(t_gradient_center_line, Color::MidGray); + aa_painter.draw_line(t_gradient_end_line, Color::LightGray); + } else { + draw_handle(m_gradient_transversal_a.value(), m_hover_over_transversal_a_handle, IconStyle::RadialWidth); + draw_handle(m_gradient_transversal_b.value(), m_hover_over_transversal_b_handle, IconStyle::RadialWidth); + } + + draw_handle(m_gradient_start.value(), m_hover_over_start_handle, IconStyle::ChangeWidthAndAngle); + draw_handle(m_gradient_center.value(), m_hover_over_drag_handle, IconStyle::None); + draw_handle(m_gradient_end.value(), m_hover_over_end_handle, IconStyle::ChangeWidthAndAngle); } } @@ -347,11 +473,15 @@ void GradientTool::reset() m_gradient_start = {}; m_gradient_center = {}; m_gradient_end = {}; + m_gradient_transversal_a = {}; + m_gradient_transversal_b = {}; m_gradient_half_length = 0; m_physical_diagonal_layer_length = 0; m_hover_over_drag_handle = false; m_hover_over_start_handle = false; m_hover_over_end_handle = false; + m_hover_over_transversal_a_handle = false; + m_hover_over_transversal_b_handle = false; if (m_editor) { m_editor->update(); @@ -359,20 +489,49 @@ void GradientTool::reset() } } -void GradientTool::update_gradient_end_and_derive_start(Gfx::IntPoint const new_end_point) +void GradientTool::update_gradient_with_initial_values(Gfx::IntPoint const new_end_point) { VERIFY(m_gradient_center.has_value()); m_gradient_end = new_end_point; - m_gradient_start = m_gradient_center.value() - (m_gradient_end.value() - m_gradient_center.value()); + auto deltaCenter = m_gradient_end.value() - m_gradient_center.value(); + m_gradient_start = m_gradient_center.value() - deltaCenter; + + if (m_mode == GradientMode::Radial) { + Gfx::IntPoint perpendicularDeltaCenter = { -deltaCenter.y(), deltaCenter.x() }; + m_gradient_transversal_a = m_gradient_center.value() + perpendicularDeltaCenter; + m_gradient_transversal_b = m_gradient_center.value() - perpendicularDeltaCenter; + } } -void GradientTool::translate_gradient_start_end(Gfx::IntPoint const delta, bool update_start_counterwise) +void GradientTool::move_gradient_position(Gfx::IntPoint const movement_delta) +{ + m_gradient_end.value().translate_by(movement_delta); + m_gradient_start.value().translate_by(movement_delta); + + if (m_mode == GradientMode::Radial) { + m_gradient_transversal_a.value().translate_by(movement_delta); + m_gradient_transversal_b.value().translate_by(movement_delta); + } +} + +void GradientTool::rotate_gradient_points(Gfx::IntPoint const delta) { m_gradient_end.value().translate_by(delta); - if (update_start_counterwise) - m_gradient_start.value().translate_by(delta.scaled(-1, -1)); - else - m_gradient_start.value().translate_by(delta); + auto translation_distance_to_center = m_gradient_center.value().distance_from(m_gradient_end.value()) - m_gradient_center.value().distance_from(m_gradient_start.value()); + m_gradient_start.value().translate_by(delta.scaled(-1, -1)); + + if (m_mode == GradientMode::Radial) { + auto new_horizontal_distance_fraction = (translation_distance_to_center + m_gradient_center.value().distance_from(m_gradient_transversal_a.value())) / m_gradient_center.value().distance_from(m_gradient_start.value()); + calculate_transversal_points(new_horizontal_distance_fraction); + } +} + +void GradientTool::calculate_transversal_points(float scale_fraction) +{ + m_gradient_transversal_a = Gfx::IntPoint(m_gradient_center.value().x() + (scale_fraction * (m_gradient_center.value().x() - m_perpendicular_point.x())), + m_gradient_center.value().y() + (scale_fraction * (m_gradient_center.value().y() - m_perpendicular_point.y()))); + m_gradient_transversal_b = Gfx::IntPoint(m_gradient_center.value().x() + (-scale_fraction * (m_gradient_center.value().x() - m_perpendicular_point.x())), + m_gradient_center.value().y() + (-scale_fraction * (m_gradient_center.value().y() - m_perpendicular_point.y()))); } } diff --git a/Userland/Applications/PixelPaint/Tools/GradientTool.h b/Userland/Applications/PixelPaint/Tools/GradientTool.h index 0ced524a2a..43c38cdbac 100644 --- a/Userland/Applications/PixelPaint/Tools/GradientTool.h +++ b/Userland/Applications/PixelPaint/Tools/GradientTool.h @@ -32,12 +32,29 @@ protected: virtual StringView tool_name() const override { return "Gradient Tool"sv; } private: + enum class GradientMode { + Linear, + Radial, + __Count, + }; + + enum class IconStyle { + None, + ChangeWidthAndAngle, + RadialWidth, + }; + RefPtr<GUI::Widget> m_properties_widget; Optional<Gfx::IntPoint> m_gradient_start; Optional<Gfx::IntPoint> m_gradient_center; Optional<Gfx::IntPoint> m_gradient_end; + Optional<Gfx::IntPoint> m_gradient_transversal_a; + Optional<Gfx::IntPoint> m_gradient_transversal_b; Gfx::IntPoint m_perpendicular_point; + GradientMode m_mode = GradientMode::Linear; + int m_hardness = 25; + float m_gradient_half_length = 0; float m_physical_diagonal_layer_length = 0; bool m_button_pressed = false; @@ -45,19 +62,23 @@ private: bool m_hover_over_drag_handle = false; bool m_hover_over_start_handle = false; bool m_hover_over_end_handle = false; + bool m_hover_over_transversal_a_handle = false; + bool m_hover_over_transversal_b_handle = false; int m_opacity = 100; bool m_use_secondary_color { false }; Gfx::FloatLine m_gradient_begin_line; Gfx::FloatLine m_gradient_center_line; Gfx::FloatLine m_gradient_end_line; - void reset(); + void calculate_gradient_lines(); + void calculate_transversal_points(float scale_fraction); void draw_gradient(GUI::Painter&, bool with_guidelines = false, const Gfx::FloatPoint drawing_offset = { 0.0f, 0.0f }, float scale = 1, Optional<Gfx::IntRect const&> gradient_clip = {}); + bool has_gradient_data() { return m_gradient_center.has_value() && m_gradient_end.has_value() && m_gradient_start.has_value(); } + void move_gradient_position(Gfx::IntPoint const movement_delta); void rasterize_gradient(); - void calculate_gradient_lines(); - void update_gradient_end_and_derive_start(Gfx::IntPoint const); - void translate_gradient_start_end(Gfx::IntPoint const delta, bool update_start_counterwise = true); - bool has_gradient_start_end() { return m_gradient_center.has_value() && m_gradient_end.has_value() && m_gradient_start.has_value(); } + void reset(); + void rotate_gradient_points(Gfx::IntPoint const delta); + void update_gradient_with_initial_values(Gfx::IntPoint const); }; } |