Serenity Operating System
at master 138 lines 4.3 kB view raw
1/* 2 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org> 3 * Copyright (c) 2022, the SerenityOS developers. 4 * 5 * SPDX-License-Identifier: BSD-2-Clause 6 */ 7 8#include "SprayTool.h" 9#include "../ImageEditor.h" 10#include "../Layer.h" 11#include <AK/Math.h> 12#include <AK/Queue.h> 13#include <LibGUI/Action.h> 14#include <LibGUI/BoxLayout.h> 15#include <LibGUI/Label.h> 16#include <LibGUI/Menu.h> 17#include <LibGUI/Painter.h> 18#include <LibGUI/ValueSlider.h> 19#include <LibGfx/Bitmap.h> 20 21namespace PixelPaint { 22 23SprayTool::SprayTool() 24{ 25 m_timer = Core::Timer::create_repeating(200, [&]() { 26 paint_it(); 27 }).release_value_but_fixme_should_propagate_errors(); 28} 29 30static double nrand() 31{ 32 return double(rand()) / double(RAND_MAX); 33} 34 35void SprayTool::paint_it() 36{ 37 auto* layer = m_editor->active_layer(); 38 if (!layer) 39 return; 40 41 auto& bitmap = layer->get_scratch_edited_bitmap(); 42 GUI::Painter painter(bitmap); 43 VERIFY(bitmap.bpp() == 32); 44 double const minimal_radius = 2; 45 double const base_radius = minimal_radius * m_thickness; 46 for (int i = 0; i < M_PI * base_radius * base_radius * (m_density / 100.0); i++) { 47 double radius = base_radius * nrand(); 48 double angle = 2 * M_PI * nrand(); 49 int const xpos = m_last_pos.x() + radius * AK::cos(angle); 50 int const ypos = m_last_pos.y() - radius * AK::sin(angle); 51 if (xpos < 0 || xpos >= bitmap.width()) 52 continue; 53 if (ypos < 0 || ypos >= bitmap.height()) 54 continue; 55 bitmap.set_pixel<Gfx::StorageFormat::BGRA8888>(xpos, ypos, m_color); 56 } 57 58 layer->did_modify_bitmap(Gfx::IntRect::centered_on(m_last_pos, Gfx::IntSize(base_radius * 2, base_radius * 2))); 59} 60 61void SprayTool::on_mousedown(Layer* layer, MouseEvent& event) 62{ 63 if (!layer) 64 return; 65 66 auto& layer_event = event.layer_event(); 67 m_color = m_editor->color_for(layer_event); 68 m_last_pos = layer_event.position(); 69 m_timer->start(); 70 paint_it(); 71} 72 73void SprayTool::on_mousemove(Layer* layer, MouseEvent& event) 74{ 75 if (!layer) 76 return; 77 78 m_last_pos = event.layer_event().position(); 79 if (m_timer->is_active()) { 80 paint_it(); 81 m_timer->restart(m_timer->interval()); 82 } 83} 84 85void SprayTool::on_mouseup(Layer*, MouseEvent&) 86{ 87 if (m_timer->is_active()) { 88 m_timer->stop(); 89 m_editor->did_complete_action(tool_name()); 90 } 91} 92 93ErrorOr<GUI::Widget*> SprayTool::get_properties_widget() 94{ 95 if (!m_properties_widget) { 96 auto properties_widget = TRY(GUI::Widget::try_create()); 97 (void)TRY(properties_widget->try_set_layout<GUI::VerticalBoxLayout>()); 98 99 auto size_container = TRY(properties_widget->try_add<GUI::Widget>()); 100 size_container->set_fixed_height(20); 101 (void)TRY(size_container->try_set_layout<GUI::HorizontalBoxLayout>()); 102 103 auto size_label = TRY(size_container->try_add<GUI::Label>("Size:")); 104 size_label->set_text_alignment(Gfx::TextAlignment::CenterLeft); 105 size_label->set_fixed_size(80, 20); 106 107 auto size_slider = TRY(size_container->try_add<GUI::ValueSlider>(Orientation::Horizontal, "px"_short_string)); 108 size_slider->set_range(1, 20); 109 size_slider->set_value(m_thickness); 110 111 size_slider->on_change = [this](int value) { 112 m_thickness = value; 113 }; 114 set_primary_slider(size_slider); 115 116 auto density_container = TRY(properties_widget->try_add<GUI::Widget>()); 117 density_container->set_fixed_height(20); 118 (void)TRY(density_container->try_set_layout<GUI::HorizontalBoxLayout>()); 119 120 auto density_label = TRY(density_container->try_add<GUI::Label>("Density:")); 121 density_label->set_text_alignment(Gfx::TextAlignment::CenterLeft); 122 density_label->set_fixed_size(80, 20); 123 124 auto density_slider = TRY(density_container->try_add<GUI::ValueSlider>(Orientation::Horizontal, "%"_short_string)); 125 density_slider->set_range(1, 100); 126 density_slider->set_value(m_density); 127 128 density_slider->on_change = [this](int value) { 129 m_density = value; 130 }; 131 set_secondary_slider(density_slider); 132 m_properties_widget = properties_widget; 133 } 134 135 return m_properties_widget.ptr(); 136} 137 138}